Upgrade to Pro — share decks privately, control downloads, hide ads and more …

スクレイピング用 Gem を作った話

スクレイピング用 Gem を作った話

nyamadori

August 01, 2017
Tweet

More Decks by nyamadori

Other Decks in Programming

Transcript

  1. 単純なら問題ない <div id="ires"> <div class="g"> <h3> タイトル</h3> <!-- 取りたい -->

    <div class="kv"> <cite>http://example.com/</cite> <!-- 取りたい --> </div> </div> </div> # Nokogiri なら { title: doc.css('#ires .g h3').text, url: doc.css('#ires .g .kv > cite').text }
  2. 取得する項目増やしましょう <div id="ires"> <div class="g"> <h3> タイトル</h3> <!-- 取りたい -->

    <div class="kv"> <cite>http://example.com/</cite> <!-- 取りたい --> </div> <div class="st"> 説明文 </div> <!-- 取りたい。 ただしインデントの空白は削除する --> </div> </div> { title: doc.css('#ires .g h3').text, url: doc.css('#ires .g .kv > cite').text, description: doc.css('#ires .g .st').text.strip }
  3. 相手サイトの仕様変わったよ <div id="ires"> <div class="item"> <!-- クラス名が変わった! --> <h3> タイトル</h3>

    <div class="kv"> <cite>http://example.com/</cite> </div> <div class="st"> 説明文 </div> </div> </div> # 項目数だけセレクタを変える { title: doc.css('#ires .item h3').text, url: doc.css('#ires .item .kv > cite').text, description: doc.css('#ires .item .st').text.strip }
  4. リファクタ CONTAINER = '#ires .item' # 項目数だけセレクタを変える { title: doc.css("#{CONTAINER}

    h3").text, url: doc.css("#{CONTAINER} .kv > cite").text, description: doc.css("#{CONTAINER} .st").text.strip } これでセレクタが変わっても問題ないが #{CONTAINER} がダサい
  5. 構造を考えてリファクタする container = doc.css('#ires .item') # `container#css` は container 内の子孫要素を検索する

    { title: container.css("h3").text, url: container.css(".kv > cite").text, description: container.css(".st").text.strip } # container の子要素を取得したい # となっても、 この方法ならセレクタを二度書きせずに済む child = container.css('.child') { child: { child_item_1: child.css('.child-item-1').text, child_item_2: child.css('.child-item-2').text, } }
  6. 完成形 def scrape_item(context) container = context.css('#ires .item') { title: context.css("h3").text,

    url: context.css(".kv > cite").text, description: context.css(".st").text.strip, child: scrape_child(container) } end def scrape_child(context) child = container.css('.child') { child_item_1: child.css('.child-item-1').text, child_item_2: child.css('.child-item-2').text, } end scrape_item(doc) #### ただし、 行数は最初の3 倍!
  7. 例: Google 検索 リクエストに必要な情報と スクレイピングする HTML ノー ドを DSL で記述する

    class GoogleSearchAgent < Juknife::Agent request do # リクエストに必要な情報 get 'https://www.google.co.jp/search' user_agent 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6. query { { q: params[:query] } } end scraping do # スクレイピングする HTML ノー ド items :results, '#ires .g' do item :title, 'h3' # ペー ジタイトル item :url, '.kv > cite' # ペー ジ URL end end end
  8. 例: Google 検索 agent = GoogleSearchAgent.new agent.scrape(query: 'test') # =>

    # {:results=> # [ # {:title=>"test の意味・ 用例| 英辞郎 on the WEB: アルク", # :url=>"https://eow.alc.co.jp/search?q=test", # :description=> # "test 【1 自動】 試験[ 検査] を受ける 《 医・ 化学》 検査[ 分析] を行 # " 届けする進化するオンライン英和・ 和英辞書デー タベー ス。 一般的な単語 # " イディオム、 専門用語、 スラングまで幅広く収録。"} # } # ] # }
  9. Juknife DSL Request DSL get / post / put /

    delete user_agent query body Scraping DSL item / items scope
  10. Juknife DSL Request DSL get / post / put /

    delete user_agent query body Scraping DSL <= 今日はこれだけ item / items scope
  11. Scraping DSL 基本的な考え方 ブロックの構造 ≒ 取得結果 (Hash) の構造 セレクタで指定された要素を親要素として、 Ruby

    ブロック内の HTML 要素が検索される scraping do items :results, '#ires .g' do # この中は、'#ires .g' を親要素として、 以下の要素を検索する item :title, 'h3' # ≒ #ires .g h3 item :url, '.kv > cite' # ≒ #ires .g .kv > cite end end
  12. Scraping DSL scraping do items :results, '#ires .g' do #

    items: `#ires .g` で得られる各要素を # ブロック内で処理して、 結果を配列として返す item :title, 'h3' # ≒ #ires .g h3 item :url, '.kv > cite' # ≒ #ires .g .kv > cite end end # => # {:results=> # [ # {:title=>"test の意味・ 用例| 英辞郎 on the WEB: アルク", # :url=>"https://eow.alc.co.jp/search?q=test", # :description=> "test 【1 自動】 試験[ 検査]..."}, ... # } # ] # }
  13. 展望 取得結果の DB 格納も含め、 できるようにしたい 取得に失敗したら再取得できるようにしたい ヘッドレスブラウザで取得できるようにしたい スクレイピングのために、 リクエストに必要な デー

    タを作るのめんどくさいので、 Rails プラグインとして実装したい Active Record とか Active Job とかあって、 上記 2 つが楽に実装できそう https://github.com/nyamadori/juknife