Rubyと筋肉とギターとわたし

エンジニア二年目の雑魚です。プログラミング・ギター・筋トレのことをメインにブログを書いていきます。

【Ruby】スクレイピングに精度を求めるならNokogiriではなくSeleniumだ

どうもてぃです。

とある巨大ECサイト(通称熱帯雨林)をスクレイピングして遊んでいます。

今回は nokogiri で問題が発生したので記事にしました。

nokogiriのスクレイピング精度はあんまり良くないのを実体験で痛感しましたね。

environment

issue

ページネーション要素の取得です。

Nokogiriだと4〜6割の確率でページネーションを取得できない場合があります。

問題なのが、Seleniumだとページを読み込み終わるまで待つSelenium::WebDriver::Waitがあるのに対して、Nokogiriにはwaitが存在しません。

あと、NokogiriではJS等で動的に表示しているページは上手く取得できないことがあるとのこと。

だからSeleniumブラウジングしてスクレイピングする方が成功確率は高いと。

最初からSelenium使えばよかったですわ。PythonSeleniumだし。

solution

試しに、一番お手軽なスクレイピングサイト、巨大ECサイト熱帯雨林を使ってみます。

ここだと、ページネーションもありますので。

最近switchのスマブラが販売されて転売ヤーさんが乱立しているGCコンをスクレイピングしてみます。

require 'selenium-webdriver'
ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36"

# headlessモードのoptionをつける
options = Selenium::WebDriver::Chrome::Options.new
options.add_argument('--headless')
options.add_argument('--disable-gpu')
options.add_argument("--user-agent=#{ua}")

driver = Selenium::WebDriver.for(:chrome, options: options)
driver.get('https://www.amazon.co.jp/gp/offer-listing/B07HC2F97Q/ref=dp_olp_new?ie=UTF8&condition=new')

# ページ読み込みのwaitを設定
wait = Selenium::WebDriver::Wait.new(timeout: 10)

# find_elementで単数取得
wait.until { driver.find_elements(:xpath, "//h3[@class='a-spacing-none olpSellerName']/span/a") }
seller_info = driver.find_elements(:xpath, "//h3[@class='a-spacing-none olpSellerName']/span/a")
wait.until { driver.find_elements(:xpath, "//ul[@class='a-pagination']/li/a").map { |el| el.attribute(:href) } }
pagination = driver.find_elements(:xpath, "//ul[@class='a-pagination']/li/a").map { |el| el.attribute(:href) }

# ページネーションの最後までクリックする
loop.with_index(1) do |_, i|
  puts "==================================#{i}回目==================================="
  puts driver.find_elements(:xpath, "//h3[@class='a-spacing-none olpSellerName']").map(&:text)

  break if driver.find_elements(:xpath, "//ul[@class='a-pagination']/li/a").last[-1] == '#'
  driver.find_elements(:xpath, "//ul[@class='a-pagination']/li/a").last.click
end

# 二回目のget urlも問題ない
driver.get('https://www.amazon.co.jp/sp?_encoding=UTF8&seller=A2W8UAZHGIMLV7')

driver.close

上記ソースを超簡単に説明。

# headlessモードのoptionをつける
options = Selenium::WebDriver::Chrome::Options.new
options.add_argument('--headless')
options.add_argument('--disable-gpu')
options.add_argument("--user-agent=#{ua}")

Selenium::WebDriver::Chrome::Options.newSeleniumのオプションを設定するインスタンスを生成します。

AWSGCPで動かすことも想定にいれて、ヘッダレスでブラウザを起動しないようなオプションをつけています。

ついでにユーザーエージェントも設定してます。

# ページ読み込みのwaitを設定
wait = Selenium::WebDriver::Wait.new(timeout: 10)

スクレイピングをした際にページが全て表示されるまで待つ必要が有ります。

その設定を上記でやっています。使い方は

wait.until { driver.find_elements(:xpath, "//h3[@class='a-spacing-none olpSellerName']/span/a") }
seller_info = driver.find_elements(:xpath, "//h3[@class='a-spacing-none olpSellerName']/span/a")
wait.until { driver.find_elements(:xpath, "//ul[@class='a-pagination']/li/a").map { |el| el.attribute(:href) } }
pagination = driver.find_elements(:xpath, "//ul[@class='a-pagination']/li/a").map { |el| el.attribute(:href) }

のように、要素を取得する前にwait.utilを設定します。簡単です。

残りは要素の取得の処理がメインですね。

loopの部分でページネーションのリンクをクリックし続ける処理をやっています。そんな大したことはやってないです。

最後に

やはり、困った時はrubydocが役に立ちます。

Class: Selenium::WebDriver::Driver — Documentation for selenium-webdriver (0.0.28)

是非参考にしてみてください。

なにかあればコメント欄で〜。

ではでは。