前提環境・準備
使用するOS:Windows 10
使用するPythonのバージョン:Python3.8
使用するブラウザ:Chrome78
使用する関数:
def web_driver(headless):
if headless:
options = Options()
options.add_argument('--headless')
return webdriver.Chrome(ChromeDriverManager().install(), chrome_options=options)
else:
return webdriver.Chrome(ChromeDriverManager().install())
※webDriver関数は中略とします。
※webdriver_managerにサンプルを切り替えました(2020.06.07)
外部リンク(問題)
問題
Seleniumを使用する理由
SeleniumであればJavaScriptによる動的なページの情報を取得することもでき、使用するライブラリもSelenium単体で済みます。API集も充実していてrequests + BS4のように複数のライブラリをまたがせることなくSelenium単体でスクレイピングとクローリングに対応できます。Driverの設定だけ若干手間な点とrequestsよりも速度が劣る点はデメリットですが、どれか1つだけに絞って使う場合にはSeleniumが使いやすく学習のコストも低くなると思われます。
なお、Seleniumでソースを取得してBS4を使う手法であればrequestsによる速度のメリットは得られませんが、BS4で情報を抜き出すことも可能です。BS4の方がコードが短くなる傾向があるような気がします。
API:https://www.seleniumqref.com/api/webdriver_gyaku.html
問題
1. [初級] QiitaアドベントカレンダーのURL一覧を取得する
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
# 中略
def main():
driver = webDriver(True)
driver.get("https://qiita.com/advent-calendar/2016/crawler")
items = driver.find_elements_by_class_name("adventCalendarItem")
for item in items:
author = item.find_element_by_class_name("adventCalendarItem_author").text
title = item.find_elements_by_tag_name("a")[-1]
print(title.get_attribute('href'),title.text, author, sep="\t")
driver.close()
if __name__ == "__main__":
main()
問題+ボーナスにも取り組みました。JavaScriptによる動的なページ生成がされている様子でもないのでRequests + BS4の方が早く処理ができます。
ページ下部を見ると必要なデータの一覧がテーブル形式で表示されており、それぞれのアイテムが
ITEMSをfor文にかけると個別の要素を取得できるので、それをさらにadventCalendarItem_authorクラスで要素を取得することで(2)著者情報の要素を取得しています。(3)はaタグが2つあり最後から1つ目が必ず記事のタイトルになっているのでfind_elements_by_tag_name("a")でリンクをまとめて取得してスライスで最後の要素のみを抜き出しています。要素に.textを付けるとテキストを取得でき、.get_attribute('href')でhref内の値を取得できます。
2. [初級] みずほ銀行の外貨普通預金を取得する(表のスクレイピング)
リンク先のページが見つからなかったため別ページを代用することにしました。
https://www.smbc.co.jp/kojin/kinri/gaika.html
パーソナル外貨定期預金(10万米ドル相当額未満の標準金利)の2つの表を取得して出力する処理をします。ついでにボーナスのPandasも使ってみましょう。
import pandas as pd
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
# 中略
def main():
driver = webDriver(True)
driver.get("https://www.smbc.co.jp/kojin/kinri/gaika.html")
tabElm = driver.find_elements_by_tag_name("table")
dfs = [pd.read_html(tab.get_attribute('outerHTML'))[0] for tab in tabElm]
df = pd.merge(dfs[0], dfs[1])
print(df)
if __name__ == "__main__":
main()
スクレイピング自体は1番目の問題より簡単でテーブルを取得しただけです。どちらかというとPandasの処理の問題ですが表の構造がほぼ同じなのでシンプルにマージの処理ができました。
3. [中級] 明日の天気を取得する(APIによるデータ取得)
import json
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
# 中略
def main():
driver = webDriver(True)
driver.get("http://weather.livedoor.com/forecast/webservice/json/v1?city=280010")
res = json.loads(driver.find_element_by_tag_name("pre").text)
forecast = res["forecasts"][1]
print("%sの明日の天気は%s、最高気温は%s℃です。" % (
res['location']['city']
,forecast['telop']
,forecast['temperature']['max']['celsius']
)
)
if __name__ == "__main__":
main()
中級らしいですがデータの取得自体は簡単です。Seleniumだと直接jsonのレスポンスを受け取ることができず、jsonモジュールをインポートして処理をしています。テキストベースのjsonをjson.loadsでjson化させると辞書型のようにデータを扱うことができます。
4. [中級] iOSの人気アプリのアイコンを収集する(画像収集)
import os
import time
import requests
from selenium import webdriver
from selenium.webdriver.common.alert import Alert
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
# 中略
def main():
driver = webDriver(False)
driver.get("https://theappstore.org/search.php?search=%E3%82%B2%E3%83%BC%E3%83%A0&platform=software")
time.sleep(2)
try:
Alert(driver).accept()
except:
pass
if not os.path.isdir("img\\"):
os.mkdir("img\\")
items = driver.find_elements_by_class_name("appmain")
for num, item in enumerate(items, start=1):
img = item.find_element_by_tag_name("img").get_attribute("src")
title = item.find_element_by_class_name("apptitle")
r = requests.get(img)
with open("img\\%s-%s.png" % (num, title.text), 'wb') as f:
f.write(r.content)
if __name__ == "__main__":
main()
https://theappstore.org/search.php?search=%E3%82%B2%E3%83%BC%E3%83%A0&platform=software
課題のURLがまたしてもご臨終のため類似のもので代用しました。処理には結局requestsを使いました。Seleniumでも画像をダウンロードする方法もあるようですがコードが長くなりそうでrequestsを使う方がコストが低いと判断しました。Selenium単体であればelement.screenshot("ファイルパス")で特定の要素のみをスクリーンショットを撮り保存する方法もありますが若干速度が遅いのと、スクリーンショットがうまくいかないケースがあるのとでrequestsを使いました。何か良い方法があったら教えて下さい...
[上級] 技術評論社の電子書籍情報の収集(複数のページのクローリング)
import re
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
# 中略
def main():
driver = webDriver(False)
driver.get("https://gihyo.jp/dp")
time.sleep(1)
\# URLの取得
book_container = driver.find_element_by_id("listBook")
books = book_container.find_elements_by_tag_name("li")
urlList = []
for book in books[2:]:
link = book.find_element_by_tag_name("a")
if
urlList += [link.get_attribute("href")]
\# 各URLにアクセスして情報を取得する
for url in urlList:
driver.get(url)
dic = {
'url': url,
'title': driver.find_element_by_id("bookTitle").text,
'price': driver.find_element_by_class_name("buy").text,
'content': [chap.text.replace(" ", " ") for chap in driver.find_elements_by_xpath('//section[2]/h3')]
}
print(dic)
time.sleep(1)
driver.close()
if __name__ == "__main__":
main()
スクレイピングもクローリングも少し手の込んできたものになってきました。複数のページをクローリングする際にはURLをどのように取得し、どのような順番でアクセスすると必要な情報にたどり着けるのか整理するとやりやすいかもしれません。商品のブロックはnewクラスで囲われているため、まとめて複数要素を取得。そのあとでaタグを取得しhref内のURLをリストに格納。あとは各URLにアクセスして必要な情報を取得する流れです。ページのボリュームが増えると別の方法をとった方がよいケースも出てくるかもしれませんがURLの量が4桁程度のものなら特に問題はないと思います。
サーバーに負担をかける可能性などもあるため複数のページに多数アクセスする場合には1秒以上の間隔をあけてアクセスするようにしましょう。
6. [上級] Amazon.co.jp注文履歴の取得(Webページへのログイン)
要素.send_key(値)や要素.click()の組み合わせでログインの処理はできると思います。しかしながら、スクレイピングが禁止されているため割愛します。
まとめ
Seleniumをメインにスクレイピングをしていきました。コードを通じてSeleniumの良い点も悪い点も浮き彫りになったと思いますが、クローリングのしやすさと要素の取得のしやすさ、今回は課題になかったのですがJavascriptを扱うページでもスクレイピングが可能な点やAlertの処理もできる点にメリットがあります。半面で画像のダウンロードやJSONレスポンスの取り扱い、今回上げられなかったのですがレスポンスコードの取得ができない点などデメリットがあります。
Pythonの場合にはスクレイピングの手段が多くあり用途に合わせてライブラリを選べるとよいと思います。クローリングを重視したりとりあえずいろいろなページからテキスト情報を抜き出す処理をしたい方にはSeleniumをまずお勧めしていきたいです。