環境構築からWEBアプリ開発・スマホアプリ開発まで。ときには動画制作やゲームも。

supilog
すぴろぐ

【PythonでWEBスクレイピング】JavaScriptで表示される情報を取得する(selenium)

【PythonでWEBスクレイピング】JavaScriptで表示される情報を取得する(selenium)

本日の目標

https://cube.supisupi.com

こちらは、先日わたしが作成したルービックキューブのタイマーです。下の図の赤線で囲った部分に書いてある文字列を今回は取得してみようと思います。(以下、スクランブル文字列と呼びます)

前回までの要領で取得

早速ですが、前回までの要領で取得してみます。

from bs4 import BeautifulSoup
import requests

headers = {
  'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'
}

url="https://cube.supisupi.com/"
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")

scrambles = soup.select("#scramble")
print(scrambles[0].text)
#print(scrambles[0])

何も取得できませんでした。print文の”.text”を外して、要素ごと取得してみます。(コメントアウトしているprint文)

<div class="sm:flex sm:justify-center text-4xl" id="scramble"> </div>

要素は取得できてはいるものの、中身が空です。

値が取得できない原因

値が取得できない原因は、文字列の表示方法にあります。この文字列は、最初にページが表示された際にはまだ表示されておらず、load後にJavaScriptで表示処理を行っているところです。

さて、PythonのスクリプトでWEBページのHTMLを取得した際に、ページを構成しているCSSやJavaScriptの情報は取得できたとしても動作することはありません。ブラウザに読み込まれて初めて、CSSやJavaScriptは動作するのです。それ故、JavaScriptが動作しないので、スクランブル文字列は表示されていないのです。

ではどうすれば取得できるのか

ブラウザから閲覧しないと欲しい値が表示されません。それなら、Pythonスクリプトからの実行であっても、ブラウザを経由して情報を取得してしまえば良いではないかと。

Seleniumで情報を取得する

seleniumインストール

pip install selenium

前回までと合わせて、「beautifulsoup4」「requests」「selenium」がインストールされていればOK。

chromedriverインストール

https://googlechromelabs.github.io/chrome-for-testing

ここからdriverをダウンロードします。わたしの場合は、mac-arm64版。URLをブラウザに入力してあげれば問題なくダウンロードできると思います。

ダウンロードしたファイルを解凍して、お好きな場所に設置します。今回はここにしました。

/Users/supilog/tools/chromedriver-mac-arm64/

ディレクトリ配下にドライバがあるので、ドライバへのパスは以下の通り。

/Users/supilog/tools/chromedriver-mac-arm64/chromedriver

スクレイピング

driverを使っているので、書き方が前回までと多少違います。

from bs4 import BeautifulSoup
import requests
from selenium import webdriver
from selenium.webdriver.chrome.service import Service


chromedriver_path = '/Users/supilog/tools/chromedriver-mac-arm64/chromedriver'
options = webdriver.ChromeOptions()
options.add_argument('--headless')
service = Service(executable_path=chromedriver_path)
driver = webdriver.Chrome(service=service,options=options)

url="https://cube.supisupi.com/"
driver.get(url)
html = driver.page_source

soup = BeautifulSoup(html, "html.parser")
titles = soup.select('#scramble')[0]

print(titles.text)

【9行目】options.add_argument(‘–headless’)

これは、ヘッドレスブラウザを使用する宣言です。ヘッドレスブラウザというのは、GUI(グラフィカルユーザーインターフェイス)のないブラウザで、スクリプトから動作させるのによく用います。

【11行目】

ブラウザを起動。

気になる取得結果は

R2 D B U2 F2 U' B' D2 R2 F U B2 L' D2 U B R' U2 F' L

このスクランブル文字列は、load時にランダム生成しているので、数回実行してみる。

L' R F2 U' R D' F B2 D' R2 L' D2 U L F2 U2 L2 F2 D2
R2 B' R D2 U' B' D' B' L' F' L' F2 D2 R' F' U D R' U D F D U2
R' U B' D' F2 R2 U2 F L' U2 D L F L U F U' B' L U R2 D2

無事に毎回異なるスクランブル文字列が取得できました。

ブラウザの起動は時間がかかるよ

前回までと違って、今回はヘッドレスブラウザを利用した情報の取得を行いました。とても便利ですが、ブラウザを起動させている分、処理に時間がかかります。どのくらいの時間差があるか、少しやってみる。本ブログのタイトルを取得してみよう。

ブラウザを使わない場合

from bs4 import BeautifulSoup
import requests
import time

start = time.time()

headers = {
  'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'
}

for i in range(10):
  url="https://supilog.supisupi.com/"
  response = requests.get(url, headers=headers)
  soup = BeautifulSoup(response.text, "html.parser")
  scrambles = soup.select(".header-title")
  print(scrambles[0].text)
end = time.time()
print(end - start)
3.132404088973999

ブラウザを使う場合

from bs4 import BeautifulSoup
import requests
import time
from selenium import webdriver
from selenium.webdriver.chrome.service import Service

start = time.time()

chromedriver_path = '/Users/supilog/tools/chromedriver-mac-arm64/chromedriver'
options = webdriver.ChromeOptions()
options.add_argument('--headless')
service = Service(executable_path=chromedriver_path)
driver = webdriver.Chrome(service=service,options=options)

for i in range(10):
  url="https://supilog.supisupi.com/"
  driver.get(url)
  html = driver.page_source
  soup = BeautifulSoup(html, "html.parser")
  scrambles = soup.select(".header-title")
  print(scrambles[0].text)
end = time.time()
print(end - start)
6.405471086502075

2倍程度の時間がかかった。挙動を見ていると、単純にChromeの起動に時間がかかっている。今回は起動処理を1回しか行っていないが、何度も起動するような実装をしてしまうとかかる時間も膨大になるので注意だ。

まとめ

seleniumを利用したスクレイピングを実行してみました。いかがでしたか。

今回は運良く、起動して取得したら値がとれましたが、JavaScriptの処理が遅くて取得できない場合には、待機処理が必要になるケースもありそう(な予感)。sleepとかimplicitly_waitなどを利用することになるのでしょうか。それはまた別のおはなし

閲覧していただき、ありがとうございました。ではまた。