Lチカ開発ブログ

https://l-chika.com/の開発ブログ

Mechanizeでリンクをスクレイピング

Mechanize を利用したときのメモ。

Mechanize を試した理由

  • ちょっと前は anemone を利用していたが、最近はメンテしてないみたい。
  • 内部でNokogiri を利用しているので、学習・導入コストが個人的に低かった。

よく利用する関数と戻り値の型をメモ

> mechanize = Mechanize.new
> search_page = mechanize.get(url)

> search_page.class
=> Mechanize::Page
> search_page.links.first.class
=> Mechanize::Page::Link
> search_page.links.second.uri
=> #<URI::Generic /a/b.html>
> search_page.links.second.resolved_uri
=> #<URI::HTTP http://hoge.jp/a/b.html>


> div = search_page.search('.hoge')
> div.class
=> Nokogiri::XML::NodeSet
> div.first.class
=> Nokogiri::XML::Element

利用例

links 関数でドキュメント内の a タグを簡単に取得できる。

Mechanizeをラッパーしたリンク取集クラス

class Scraper
  attr_accessor :mechanize

  def initialize
    @mechanize = Mechanize.new
  end

  def scraping_links(url:, links_pattern:, sleep: 1)
    result_links = {}

    loop do
      Rails.logger.debug(url)
      search_page = mechanize.get(url)
      target_links = search_page.links.select { |link| link.uri.to_s =~ links_pattern }
      links = target_links.map { |link| [link.resolved_uri.to_s, link] }.to_h
      result_links.merge!(links)

      next_link = block_given? ? yield(search_page) : nil
      break if next_link.blank?
      url = next_link.resolved_uri.to_s
      sleep(sleep)
    end

    result_links
  end

  def search_document(url)
    search_page = mechanize.get(url)
    yield(search_page)
  end
end

呼び出し例

# http://hoge.jp ページ内の /foo/ のリンクを全て取得したい場合
links = Scraper.new.scraping_links(url: 'http://hoge.jp', links_pattern: /foo/)

# http://hoge.jp ページ内の /foo/ のリンクをページングして全て取得したい場合。この場合は「次へ」のリンクがなくなるまでクロール
links = Scraper.new.scraping_links(url: 'https://hoge.co.jp/foos/?page=1',
                                  links_pattern: /foos\/[A-Z]/) do |search_page|
             search_page.links.find { |link| link.text == '次へ' }
            end

おすすめ本

Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例

Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例

データを集める技術 最速で作るスクレイピング&クローラー (Informatics&IDEA)

データを集める技術 最速で作るスクレイピング&クローラー (Informatics&IDEA)

PythonによるWebスクレイピング

PythonによるWebスクレイピング

ransackのinをカンマ区切りでする

やりたいこと

ransack を利用して、 in の検索をカンマ区切りで指定できるようにする。

手順

Controller

class HogeController < ApplicationController
...
  def index
    @q = Hoge.ransack(ransack_queries)
    @hoges = @q.result.page(params[:page])
  end

...

  def ransack_queries
    queries = params[:q]
    if queries.present? && queries[:id_in].present?
      # ページネーション等で2回目のアクセスが来た場合には既にarrayになっているので return。
      return queries if queries[:id_in].is_a?(Array)

      queries[:id_in] = queries[:id_in].split(',').map(&:strip)
    end
    queries
  end
...

View

= search_form_for(@q) do |f|
...
  = f.search_field(:id_in, class: 'form-control', value: params[:q].try(:fetch, :id_in, nil).try(:join, ','))

参考

319ring.net

関連する書籍

Ruby on Rails 5 超入門

Ruby on Rails 5 超入門

gretel+bootstra4でパンくず

Railsgretel をパンくずを作る。

やりたいこと

bootstrap4 の breadcrumb でパンくずをつくる。

課題

<% breadcrumb :issue, @issue %> ヘルパーのオプションでは li タグにclassを指定ができない。

解決方法

manually で対応する

手順

Gemfile

gem 'gretel'

インストール

$ bundle install --path=vendor/bundle
$ ./bin/rails g gretel:install
      create  config/breadcrumbs.rb

パンくずを設定

$ vim config/breadcrumbs.rb

view

...
<% breadcrumbs.tap do |links| %>
  <% if links.any? %>
    <div class='container'>
      <ol class='breadcrumb'>
          <% links.each do |link| %>
            <li class='breadcrumb-item<%= ' active' if link.current? %>'>
              <%= link_to_unless(link.current?, link.text, link.url) %>
            </li>
          <% end %>
      </ol>
    </div>
  <% end %>
<% end %>
...

メモ

<% breadcrumbs do |links| %> と実装すると、[Gretel] Callingbreadcrumbswith a block has been deprecated and will be removed in Gretel version 4.0. Please usetapinstead. Example: とログが出力されたので、tap で対応。

完成

f:id:l-chika:20170213235531p:plain

werckerでpushが正常にフックされなかった時の対応。

werckerを利用していて、github等からフックして自動ビルドさせているが、時々フックされずrunの一覧にも追加されない時がある。

www.wercker.com

API

devcenter.wercker.com

トークン

ここ からトークンを発行。

curl

$ curl -i -X POST \
   -H "Content-Type:application/json" \
   -H "Authorization:Bearer {自分のトークン}" \
   -d \
'{
  "branch":"ブランチ",
  "pipelineId": "起動したいパイプラインのID"
}' \
 'https://app.wercker.com/api/v3/runs/'

enumerizeで検索select box

ransack で検索フォームを作成する場合のenumerize を利用したselect boxの作成について。

github.com

github.com

課題

enumerize で検索用のselect boxを作成したいが options メソッドだと、

> Product.kind.options
=> [["キット", "kit"], ["おもちゃ", "toy"]]

となってしまう、やりたいことは enumerizein で指定したvalueを利用したい。

理想は、

[["キット", 1], ["おもちゃ", 2]]

としたい。

解決方法

locale

ja:
  enumerize:
    product:
      kind:
        kit: キット
        toy: おもちゃ

モデル

class Product < ApplicationRecord
  extend Enumerize

  enumerize :kind, in: { kit: 1, toy: 2 }, predicates: { prefix: true }, scope: true
end

ヘルパー

module ProductsHelper
  def select_kinds
    Product.kind.find_values(*Product.kind.values.map(&:to_sym)).map { |kind| [kind.text, kind.value] }
  end
end

view

<%= f.select(:kind_eq, select_kinds) %>
<select name="q[kind_eq]" id="q_kind_eq">
<option value="1">キット</option>
<option value="2">おもちゃ</option>
</select>

となる。

最後に

enumerize :kind, in: { kit: 1, toy: 2 } の in で指定しているhashを定数にするという案もあるが、localeのこととかを考えると、この実装が良いと感じた。

デザイン知識がないプログラマーが「見つけやすさのデザイン」を考える

ここまでRailsアプリケーション構築のフロントエンドの設定まで完了。ここからはWebアプリケーションのサイト構成とページ構成を検討する。

l-chika.hatenablog.com

検討にあたっては、サイト・ページ構成をーから構築した経験がないので、取っ掛かりとして 情報アーキテクチャ 第4版 ―見つけやすく理解しやすい情報設計 の内容を参考に進めてみた。

最初のサイト・ページ構成案 (「シンプルすぎる」情報モデル)

ザックリ前提・要件

  • だれ向け:電子工作に興味がある人が訪れるサイト
  • どんなサイト:電子工作にまつわる製品が網羅できるページがある

サイト構成

f:id:l-chika:20170207095248p:plain

ページ

Bootstrapを学習するついでに、ワイヤーフレームではなく、いきなりviewを作成。

メイン

f:id:l-chika:20170207010452p:plain


製品一覧

f:id:l-chika:20170207010515p:plain


製品詳細

f:id:l-chika:20170207010533p:plain


と、これでは全然イケてないので改修を試みる。

ユーザの「情報ニーズ」を考える

「ユーザーが何を求めているのか?」を考え、その特性を分類し整理してみる。

それにより、

ユーザーの主要情報ニーズと情報探索行動の典型を知ること。ユーザがシステムに何を求めているかの理解。

を体感。

本書から引用。

  • 既知情報検索:探しているものが何か、誰に聞けばいいか、どこで探せばいいのかを知っている
  • 探求探索:自分が本当に探しているものを把握してない。ユーザは検索やブラウジングのプロセスで自分が何を求めているかを知る。「正しい」答えに対する明確な期待もなければ、探しているものをはっきりと知る必要もない。ユーザはいくつかよい結果を得られれば満足で、それを踏み台として次の検索を繰り返す
  • 全数探索:「あらゆる物を手に入れたい」。ユーザーは特定のトピックに関する何もかも、一つ残らず探している。ユーザーは多様な言い方で探しているものを表現でき、さまざまな用語を駆使して忍耐強く検索を続ける。他の情報ニーズに比べてより多くの検索結果を辛抱強く渡り歩く
  • 再検索:これまでに見つけた便利な情報の断片を再検索する。「あとで読む」サービスを使って、もっと時間のある時にページに戻る

で、そこから自分なりに解釈。

分類 ザックリと表現 必要な機能
既知情報検索 能動的な人 フリーワード検索
探求探索 目的がふんわり 選択肢や何かの括りでの検索
全数探索 とにかく見る ???
再検索 再訪者 お気に入り

当初の自分が想定したユーザーとその動きを考えると、「探求探索」が該当するユーザーのような気がしてきた。 本来は全分類について検討するのだろうが、まずは「探求探索」に対してどうすれば良いかを考える。

そこで、

  1. オーガニック検索で「電子工作 キット 入門」のようなワードで流入
  2. メイン or 製品一覧にランディング。
  3. 興味が湧く製品詳細に導く

というアウトラインを決めて、「探求探索」のユーザーの目的(ゴール)を設定する。

そうした時に、現状の構成では、製品に興味をもってもらい、サイトを回遊するたのフックが弱いと感じた。 そこで製品の「括り」のような取っ掛かりが良い気がした。

改変後

製品を「特徴」で分類し、分類での絞込ができるようにしてみる。

  • メインに「特徴」導線を追加。特徴での絞込結果に遷移
  • 製品一覧に「特徴」での検索を追加

サイト構成

f:id:l-chika:20170207095322p:plain

ページ

メイン

f:id:l-chika:20170207182122p:plain


製品一覧

f:id:l-chika:20170207182137p:plain


という、大したことない改修ですが、なんか漠然と導線を作ったのではなく、体系的に自分で考えて実装をしたことで「 構成を真剣に考える 」良い経験になった気がする。

最後に

ユーザーの主要情報ニーズと情報探索行動の典型を知ること。ユーザがシステムに何を求めているかの理解。

とりあえず、情報アーキテクチャ 第4版 ―見つけやすく理解しやすい情報設計 からヒントをえて、気づいた点から対応。 これから更に読み進めて

  • 情報の組織化
  • ラベリング
  • ナビゲーション
  • 検索

という要素を検討していく。

本書は若干内容が発散的にも感じる部分もあるが、情報アーキテクチャという観点から、サイトを構成する前提、要素整理やアプローチの方法等のアイデアを得るには良い本だと思った。

こういった事に疎かった自分にとっては新たな気づきが得られた。

近い内容で 情報アーキテクチャ 第4版 ―見つけやすく理解しやすい情報設計 も参考になりそう。

関連する本

情報アーキテクチャ 第4版 ―見つけやすく理解しやすい情報設計

情報アーキテクチャ 第4版 ―見つけやすく理解しやすい情報設計

IAシンキング Web制作者・担当者のためのIA思考術

IAシンキング Web制作者・担当者のためのIA思考術

rails generateコマンドで忘れがちな事「rails g -h」

rails generate コマンドで、よく使うけど忘れてしまいがちな以下をメモ。

  • generateが可能な事・オプション。Rails標準でgenerateできるものは大体理解しているが、追加した gem のgenerate方法
  • 生成されるものを制限するskipオプションについて
  • モデルを作成するgenerateで指定する型
  • limit の長さでRDBの型が異なる場合の長さの指定

generate

Rails標準に加え、generateを提供しているgemを調べるとき。 モデルを生成した後からrspecfactory_girl のgemを追加して、対応するモデルのspec・factory_girlのファイルをそれぞれ追加生成する場合に、いちいち自分でいちから生成するのでなく generate で生成する場合の調べ方。

以下、rspecfactory_girl をインストールして、rails g -h した結果。

$ ./bin/rails g -h
-- 中略 --
Rails:
  assets
  channel
  controller
  generator
  helper
  integration_test
  jbuilder
  job
  mailer
  migration
  model
  resource
  scaffold
  scaffold_controller
  task

FactoryGirl:
  factory_girl:model

Js:
  js:assets

Rspec:
  rspec:controller
  rspec:feature
  rspec:helper
  rspec:install
  rspec:integration
  rspec:job
  rspec:mailer
  rspec:model
  rspec:observer
  rspec:request
  rspec:scaffold
  rspec:view

TestUnit:
  test_unit:controller
  test_unit:generator
  test_unit:helper
  test_unit:integration
  test_unit:job
  test_unit:mailer
  test_unit:model
  test_unit:plugin
  test_unit:scaffold

例えば、Userモデルのfactory_girlを生成する場合

./bin/rails g factory_girl:model user

さらにgenerate ごとの詳細を知りたければ、そのヘルプを見ることが可能。

$ ./bin/rails g scaffold -h

skip

scaffoldで assetshelper を生成したくないとき。

$ ./bin/rails g scaffold product name:string url:string --skip-assets --skip-helper
      invoke  active_record
      create    db/migrate/20170202010817_create_products.rb
      create    app/models/product.rb
      invoke  resource_route
       route    resources :products
      invoke  scaffold_controller
      create    app/controllers/products_controller.rb
      invoke    erb
      create      app/views/products
      create      app/views/products/index.html.erb
      create      app/views/products/edit.html.erb
      create      app/views/products/show.html.erb
      create      app/views/products/new.html.erb
      create      app/views/products/_form.html.erb
      invoke    jbuilder
      create      app/views/products/index.json.jbuilder
      create      app/views/products/show.json.jbuilder
      create      app/views/products/_product.json.jbuilder

qiita.com

利用可能な型

rails generate model NAME [field[:type][:index] field[:type][:index]] [options]

等で、カラムのtypeを指定する場合の利用可能な型

$ ./bin/rails g model -h
- 中略 -
    You can use the following types:

        integer
        primary_key
        decimal
        float
        boolean
        binary
        string
        text
        date
        time
        datetime

qiita.com

limitの長さ

integerとtextは limit オプションの長さによってRDBの型が変わる

qiita.com

関連するおすすめ本

Ruby on Rails 5 超入門

Ruby on Rails 5 超入門