🤖

10分で分かるPythonによるデータ収集の自動化

2022/03/20に公開

背景

Pythonでサイトを解析するスクレイピングを試した見たので備忘録。
くれぐれも解析する対象は気をつけてください。

スクレイピング

「ホームページを開いて、必要な情報を持ってくる」を自動的に行う。
特定の検索結果とか、更新情報などを定時的に持ってくる時に利用する。
また、Seleniumを利用すると、実際のブラウザを制御してデータを取得するため、Javascriptで非同期で持ってくるデータも収集できる。

利用方法

インストール

install

Pythonがインストールされたパソコンを用意します。
下記のコマンドで、Seleniumをインストール。

pip install selenium

ブラウザを制御するため、ブラウザ毎のドライバを用意します。
私はChromeを利用していますので、Chromeベースで説明します。

Chrome Driver

下記にまとまってあるのでダウンロードしてください。

https://chromedriver.storage.googleapis.com/index.html

大量にバージョンが存在しますがChromeもバージョンと近いものを選択してください。
Chromeのバージョンは、利用しているChromeの右上からGoogle Chromeについてを選択してバージョンを確認してください。

利用方法
コード上で利用する場合は、いろいろな方法がありますが、今回はコード上で利用します。
自動化する場合は、考えたほうがいいかもしれないです。

from selenium import webdriver
driver = webdriver.Chrome(executable_path="./chromedriver")

参考

<インストール>
https://www.selenium.dev/ja/documentation/webdriver/getting_started/install_library/
<ドライバインストール>
https://www.selenium.dev/ja/documentation/webdriver/getting_started/install_drivers/
<利用方法>
https://www.selenium.dev/ja/documentation/webdriver/getting_started/install_drivers/#3-ハードコードされた場所

ソースコード

下記のような形で利用する。

from selenium import webdriver
import time

driver = webdriver.Chrome(executable_path="./chromedriver")
driver.get("{{target url}}")
time.sleep(2)
ret=[]
ret.append(["title","company"])

for j in driver.find_elements_by_xpath("//div[@class='card']"):
    r=[]
    r.append(j.find_element_by_xpath(".//h2/span").text)
    r.append(j.find_element_by_xpath(".//span[@class='companyName']").text)
    ret.append(list( map(lambda x: '"'+x.replace('"',"'").replace("\n"," ").replace(",",".")+'"',r)))

print("\n".join(map(lambda x: ','.join(x),ret)))

利用時の注意点

time.sleepの重要性

通常のブラウザと同様で一瞬で全てが読み取られません。
任意の時間、Sleepを入れて読み取り後に実行されるように調整しましょう。
何度も実行する場合や定期的に実行される場合は少し余裕を見て多めに時間を設定したほうが良いです。

driver.get("{{target url}}")
time.sleep(2)

要素の検索

find_element系の関数で、HTML内の要素を検索します。

find_element_by_id
find_element_by_name
find_element_by_xpath
find_element_by_link_text
find_element_by_partial_link_text
find_element_by_tag_name
find_element_by_class_name
find_element_by_css_selector

ちなみに、複数検索する場合はfind_elementsと複数形になるため注意。

find_elements_by_name
find_elements_by_xpath
find_elements_by_link_text
find_elements_by_partial_link_text
find_elements_by_tag_name
find_elements_by_class_name
find_elements_by_css_selector

xpath

xpathはHTMLやXMLを検索するSQLみたいな役割をする構文。
これを使うと複雑なHTMLでも特定の要素だけ抽出できる。

階層

通常のファイルシステムと同じで/で階層を表現できる。
//で任意の階層(階層を指定しない)とか./で今いる階層とかを表現できる。

上記のソースの例でもわかるように、要素を取得してその要素内で検索する場合は今いる階層で検索する必要があるため注意。
下記の部分では、複数個の要素を持ってきて、要素内でh2/spanという要素を検索している。

for j in driver.find_elements_by_xpath("//div[@class='card']"):
    r=[]
    r.append(j.find_element_by_xpath(".//h2/span").text)

要素名

//divという形で要素名を指定できる。
この場合はdivという要素のみ選択される。

属性

下記のような形でHTML内の属性を指定できる。

//div[@class="row"]

この例では@class="row"でクラス名がrowを含む要素を指定している。

構文の検証

個人的にはChromeのデベロッパーツールを利用すると簡単。
Chromeを選択した状態で下記のコマンドのキーで開きます。

Mac     : Option + Command + I
Windows : Ctrl + Shift + I 

開いたら(省略されていたら中央付近の>>をクリック後に)ElementをクリックしてHTMLを表示してください。

開いたら、この画面で検索ボックスを表示するため、下記のキーで開きます。

Mac     : Command + F
Windows : Ctrl + F 

画面下部に検索ボックスが表示されます。
ここに、試したいxpathを入力すると、対象のページで検索にヒットする要素が確認できます。

自動化時(ウィンドウシステムがない場合)の注意点

GithubActionなどで自動化する場合、ウィンドウシステムが存在しないため下記の3点に注意が必要。

1. 画面表示を行わないようにする

通常起動しようとするとウィンドウを表示しようとして異常終了する場合がある。
なので、オプションを利用して画面非表示でDriverを初期化して利用する。

options = Options()
options.add_argument("--headless")
driver = webdriver.Chrome(executable_path="./chromedriver",options=options)

参考
https://stackoverflow.com/questions/60542932/is-there-a-way-to-hide-the-browser-while-running-selenium-in-python

2. 画面サイズを規定する

非表示にすると、どの画面サイズで起動しているか確認できず、想定した大きさではない場合に表示自体が異なるさいとが沢山ある。
そのため、想定できる大きさを規定して表示させるようにする。

options = Options()
options.add_argument("--headless")
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(executable_path="./chromedriver",options=options)

参考
https://stackoverflow.com/questions/23381324/how-can-i-control-chromedriver-open-window-size

3. 待機時間を長くする。

ローカルで行っている場合、高スペックなマシンな場合が多いがクラウド実行時はスペックが限られる場合が多い。
その結果、想定の2〜3倍の時間がかかることが多い。
個人的にはSleepを別関数化しておいて、待機時間を簡単に調整できるようにしておいたほうが良い。

def sleep(t):
    time.sleep(t*2)

最後に

ご存知だと思うがAmazonなどのサイトでは、スクレイピングなどを利用規約で禁止しているサイトも複数存在する。
利用規約をよく確認して、利用してください。

https://www.amazon.co.jp/gp/help/customer/display.html?nodeId=201909000

Discussion