第35回「スクレイピング(7)」JavaScriptで動くページとSeleniumの基本

こんにちは、小澤です。

前回は、スクレイピングを定期実行して自動化する方法について紹介しました。cronやWindowsタスクスケジューラを使えば、毎日・毎時といった決まったタイミングでスクリプトを実行できます。また、ログを残すこと、エラー時の内容を確認できるようにすること、アクセス間隔や利用規約に配慮することも重要だということを説明しました。

ここまで説明してきた処理では、HTMLを取得し、BeautifulSoupで解析し、複数ページを巡回し、CSVやExcelに保存し、定期実行するところまで見てきました。かなり実務に近い流れになってきましたが、実際のWebサイトを扱っていると、もうひとつ大きな壁に出会うことがあります。それが、JavaScriptで動的に生成されるページです。

今回は、requestsとBeautifulSoupだけでは取得しにくいページがなぜ存在するのかを整理し、ブラウザを自動操作するSeleniumの基本的な考え方を紹介します。

requestsで取れるHTMLと、ブラウザで見える画面は違う

これまでのスクレイピング処理では、requests.get()でWebページのHTMLを取得し、そのHTMLをBeautifulSoupで解析してきました。この方法は、HTMLの中に最初から欲しい情報が含まれている場合には非常に有効です。

しかし、最近のWebサイトでは、最初に返ってくるHTMLには最低限の枠だけがあり、商品一覧や検索結果などの中身は、あとからJavaScriptで読み込まれることがあります。ブラウザで開くと情報が見えているのに、requestsで取得したHTMLを見ると肝心のデータが見つからない、という状況です。

このとき、BeautifulSoupの使い方が間違っているとは限りません。そもそも、取得したHTMLの中に目的の情報が含まれていない可能性があります。まずはここを切り分けることが大切です。

動的ページとは何か

動的ページとは、ブラウザ上でJavaScriptが実行されることで、あとから表示内容が作られるページのことです。たとえば、ページを開いたあとに商品一覧が読み込まれる、スクロールすると次のデータが追加される、ボタンを押すと一覧が切り替わる、といったページです。

人間がブラウザで見ていると自然に表示されるため気づきにくいのですが、裏側では、HTMLの取得、JavaScriptの実行、追加データの取得、画面への反映という処理が行われています。requestsは基本的にHTMLを取得するだけなので、ブラウザのようにJavaScriptを実行して画面を完成させることはできません。

そのため、動的ページを扱うときは、次のどちらかを考えます。

  1. JavaScriptが裏側で呼び出しているAPIを確認して、そのAPIからデータを取得する
  2. ブラウザを自動操作して、実際に画面を表示してからデータを取得する

前者のほうが安定していて軽量なことが多いですが、APIが分かりにくい場合や、ログイン、クリック、スクロールなどの操作が必要な場合は、後者の方法を使うことがあります。そこで登場するのがSeleniumです。

Seleniumとは

Seleniumは、ブラウザをプログラムから操作するためのツールです。ChromeやEdgeなどのブラウザを起動し、ページを開く、ボタンをクリックする、入力欄に文字を入れる、表示された要素を取得する、といった操作をPythonコードを使って実行することができます。

BeautifulSoupは、取得済みのHTMLを解析する道具でした。一方、Seleniumはブラウザそのものを動かす道具です。そのため、JavaScriptが実行された後のページ状態を扱える点が大きな違いです。

ただし、Seleniumは便利な反面、requestsよりも処理が重くなるといったデメリットもあります。ブラウザを実際に起動するため、実行に時間がかかり、環境によっては設定も必要です。まずはrequestsとBeautifulSoupで取得できるかを確認し、必要な場合にSeleniumを使う、という順番で考えるとよいでしょう。

Seleniumの基本コード

まずは、Seleniumでページを開き、表示された要素を取得する最小限の例を見てみましょう。ここでも架空サイトとして https://example.com を使います。

from selenium import webdriver
from selenium.webdriver.common.by import By
 
 
driver = webdriver.Chrome()
driver.get("https://example.com")
 
title = driver.find_element(By.TAG_NAME, "h1")
print(title.text)
 
driver.quit()

このコードでは、Chromeを起動し、指定したURLを開き、h1タグのテキストを取得しています。find_element()は、条件に一致する要素を1つ取得するメソッドです。By.TAG_NAMEのほかに、By.CSS_SELECTORやBy.IDなどを使って要素を指定できます。

Seleniumでは、最後にdriver.quit()でブラウザを閉じることを忘れないようにします。これを忘れると、実行後もブラウザや関連プロセスが残ってしまうことがあります。

CSSセレクタで要素を取得する

実務では、タグ名だけで目的の要素を取得できることは多くありません。BeautifulSoupのときと同じように、classやidを手がかりにします。Seleniumでは、CSSセレクタを使って次のように書けます。

items = driver.find_elements(By.CSS_SELECTOR, ".item")
 
for item in items:
    name = item.find_element(By.CSS_SELECTOR, ".name").text
    price = item.find_element(By.CSS_SELECTOR, ".price").text
    print(name, price)

find_elements()は、条件に一致する要素を複数取得します。商品一覧や記事一覧のように、同じ構造が繰り返されているページでは、この形がよく使われます。ここでも、まずitemというまとまりを取得し、その中からnameやpriceを探すという考え方は、BeautifulSoupのときと同じです。

表示を待つ必要がある

動的ページで注意したいのは、ページを開いた直後には、まだ目的の要素が表示されていない場合があることです。JavaScriptによる読み込みが終わる前に要素を探すと、見つからずにエラーになることがあります。

そのため、Seleniumでは「要素が表示されるまで待つ」という処理を入れることがあります。単純にtime.sleep()で数秒待つ方法もありますが、実務では、目的の要素が現れるまで待つ書き方のほうが無駄が少なくなります。

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
 
 
wait = WebDriverWait(driver, 10)
item = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, ".item"))
)

この例では、最大10秒まで待ち、.itemという要素が見つかったら次の処理に進みます。動的ページでは、この「待つ」処理がとても重要です。

Seleniumを使うときの注意点

Seleniumを使うと、人間がブラウザで操作するのに近い動きを再現できます。しかし、だからといって、どのサイトでも自由にアクセスして自動操作してよいわけではありません。実在のサイトを対象にする場合は、必ず利用規約やrobots.txtを確認し、ログインが必要なページではアカウントの利用条件にも注意します。

また、Seleniumは処理が重いため、大量ページの巡回には向かないことがあります。JavaScriptの裏側でAPIが呼ばれているなら、そのAPIを確認したほうが安定する場合もあります。ブラウザ操作が必要な場面だけSeleniumを使う、という考え方も実務では大切です。

さらに、画面構造の変更に弱い点にも注意が必要です。ボタンのclass名やHTML構造が変わると、昨日まで動いていたコードが急に動かなくなることがあります。ログを残し、エラー時に原因を確認できるようにしておく点は、前回の定期実行と同じ注意点です。

Seleniumを定期実行に組み込む場合は、実行環境にも注意します。手元のパソコンで試すだけならブラウザ画面が表示されても問題ありませんが、サーバーや自動実行環境では画面を表示しないヘッドレスモードを使うことがあります。ヘッドレスモードでは、見た目の確認がしにくくなるため、エラー時にスクリーンショットを保存しておくと原因を追いやすくなります。

また、Seleniumで取得したデータも、最終的にはこれまでと同じように辞書やリストに整理し、pandasでCSVやExcelに保存します。つまり、Seleniumはあくまで「ページを表示してデータを取り出す手段」のひとつです。取得後の整形、保存、ログ出力、重複確認といった流れは、requestsとBeautifulSoupを使う場合と大きく変わりません。

このように考えると、スクレイピングの道具を切り替えても、全体の設計は同じです。どのタイミングで取得し、どの形で保存し、失敗したときにどう気づくかを決めておくことが、安定した運用につながります。

今回のまとめ

今回は、JavaScriptで動的に生成されるページと、Seleniumの基本について紹介しました。ポイントは次のとおりです。

  • requestsで取得したHTMLと、ブラウザで表示される画面は違う場合がある
  • JavaScriptであとから表示される情報は、BeautifulSoupだけでは取得できないことがある
  • Seleniumを使うと、ブラウザを自動操作して表示後の要素を取得できる
  • 動的ページでは、要素が表示されるまで待つ処理が重要になる
  • Seleniumは便利だが重いため、必要な場面に絞って使う

スクレイピングでは、対象ページの作られ方を見極めることが重要です。最初からHTMLに情報が入っているならrequestsとBeautifulSoupで十分です。一方、JavaScriptであとから表示されるページでは、APIの確認やSeleniumの利用を検討します。

次回は、ここまでの内容を踏まえて、スクレイピングで取得したデータを分析や可視化につなげる考え方を紹介します。取得したデータを、pandasで集計したり、グラフにしたりすることで、単なる収集からデータ活用へ進めていきましょう。次回もお楽しみに。

PAGE TOP