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

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

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

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

Avatar for nyamadori

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