python+seleniumで動画検索

seleniumっていうのはブラウジングを自動化するツールで、pythonとかのプログラミング言語と組み合わせれば動的なサイトでもちゃんと動くスクレイパーがサクッと書けます

実は私はseleniumの使用歴が数日程度のただの初心者ですが、それなりに使えている(つもり)ので扱いやすくできてるツールなのでしょう
それでも、ハマったポイントがいくつかあったので備忘録として残すことにしました

なんでseleniumを使いだしたかというと・・・

利用規約に抵触する可能性があるので名前を伏せますが、とある大手動画投稿サイトはWeb APIを公開しているのですが、利用申請しても一日当たりの利用可能なクエリ数上限が小さくて動画検索とかに使うとあっという間に使い切ってしまいます
割り当てを増やすこともできますが、割り当て追加の理由とかを英語で書いて提出して審査を受けないといけないので面倒です

ということでWeb APIの使用を極力減らすためにseleniumで検索をするスクリプトを書いてみたわけです

seleniumとWebDriverのインストール

python、selenium、WebDriverのインストールの仕方は他所でもいっぱい書かれているのでここでは言及しません
よくseleniumをpipで入れろっていう解説サイトが多いですが、anacondaを使っている人はcondaで入れましょう

今回はWebDriverとしてchromedriverをD:\Application\chromedriver.exeへインストールしました

簡単なサンプル

とりあえず、一例としてyoutubeを開いて、そのページの内容を抽出するコードを書いてみましょう

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.expected_conditions import presence_of_element_located
import time

driver = webdriver.Chrome(r"D:\Application\chromedriver.exe")
wait = WebDriverWait(driver, 20)

url = "https://www.youtube.com/"
driver.get(url)

# "ytd-video-renderer"タグが現れるまで待機
tag_name = "ytd-rich-grid-video-renderer"
wait.until(presence_of_element_located((By.TAG_NAME, tag_name)))

# 各要素を表示
for elem in driver.find_elements_by_tag_name(tag_name):
   print(elem.text)
time.sleep(5)
driver.quit()

これだとページを開いたときに表示されている分だけしか取得されないですが

body = driver.find_element_by_tag_name("body")
body.send_keys(Keys.END)

とかを適当なところにいれると、Endキーを押したときのようにページの最後までスクロールさせてもう少し読み込ませたりできます

elem.textじゃなくて、htmlのソースが欲しいなら以下のように書けます

elem.get_attribute("innerHTML")

これでとりあえず動くのですが、実はブラウザがサーバーから受け取った情報が全部htmlに反映されているわけではないので、さらに情報が欲しいときはもう少し頑張らないといけません

Ajaxの通信内容を覗く

javascriptを使ってちょっとずつ情報を読み込むサイトはかなり多いですが、そういうサイトでブラウザがサーバーから受け取った情報をそのままプログラムに渡せたら嬉しいですね
こういうのは、webdriverの機能としてついているもんだと思っていたのですが、調べた限りではそういう情報はみつからず、代わりにBrowsermob-Proxyというのを使ってHAR (HTTP Archive) formatのデータを抽出するのが一般的なようです
Browsermob-Proxyはjavaで書かれた通信ログを取り出せるプロクシサーバーで以下のサイトから入手できます
https://bmp.lightbody.net/

また、pythonからBrowsermob-Proxyを扱えるようにするラッパーは以下のサイトから入手できます
https://github.com/AutomatedTester/browsermob-proxy-py

インストールできたら、ちょっとスクリプトを書いてみましょう
Browsermob-ProxyはD:\Application\browsermob-proxy-2.1.4にインストールしました

from browsermobproxy import Server
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time

server = Server(r"D:\Application\browsermob-proxy-2.1.4\bin/browsermob-proxy.bat")
server.start()
proxy = server.create_proxy()

chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument("--proxy-server={0}".format(proxy.proxy))

driver = webdriver.Chrome(r"D:\Application\chromedriver.exe", chrome_options=chrome_options)
proxy.new_har("youtube", options={'captureHeaders': True}) 

driver.get("https://www.youtube.com/")
time.sleep(5)
body = driver.find_element_by_tag_name("body")
body.send_keys(Keys.END)
time.sleep(5)
print(proxy.har)

これをそのまま実行するとchromeが警告を出してきてページが開けません
これは、browsermob-proxyのインストールフォルダにあるssl-supportフォルダ内のca-certificate-rsa.cerをブラウザの証明書に追加すれば解決するようです
具体的な手順でいうと、スクリプトをデバッグモードで実行し、ブラウザが起動したあとくらいにブレークポイントを仕掛けてスクリプトを止めておいて、ブラウザの設定>セキュリティ>証明書の管理>信頼されたルート証明機関>インポートで先ほどのファイルを選択すればOKです
一回やっておけば次からはしなくて大丈夫です

あと、プロクシのポート番号を指定したい場合はこう

server = Server(r"D:\Application\browsermob-proxy-2.1.4\bin/browsermob-proxy.bat", options={"port": port_number})

キャプチャ内容がもっと欲しい場合は proxy.new_har の options を{'captureHeaders': True, 'captureContent': True, 'captureBinaryContent': True} とかすればより多くの情報が保存されますが、けっこうなサイズになるので必要に応じてオプションを指定してください

これでデータが拾えるようになったのですが、まだハマりポイントがありました

base64エンコードされたjsonに関して

harの中に、例えば以下のようなデータがあったら

"content": {
  "size": 21194,
  "mimeType": "application/json;charset=utf-8",
  "text": "R0AREABC8CAAAcEAAAAB/wAB/IAPSA0BBVZpcmVvBTIuNS4zQQSHKP//..."
  "encoding": "base64",
  "comment": ""
},

とりあえず text の内容を base64 デコードすればjsonのデータが得られそうな気がするのですが、base64デコードするとバイナリデータが返ってきて結構悩みました

実はレスポンスのヘッダー部分に

{
  "name": "content-encoding",
  "value": "br"
},

と書いてあって、これはBrotliというアルゴリズムで圧縮されているという意味のようです
gzipとかなら圧縮してるんだなとすぐ分ったでしょうけど、Brotliは最近使われだした圧縮フォーマットらしくて全く知りませんでした

Brotliのライブラリはcondaでうまくインストールできなかったのですが、以下のサイトからpythonのバージョンに合わせて適切なwhlファイルを入手してきて、アーカイバで開いてスクリプトのフォルダの下に解凍しました

base64デコードした後で、Brotliでdecompressするとちゃんとjsonフォーマットのデータが得られました

import brotli

brotli.decompress(base64.b64decode(text)).decode(encoding="utf_8")





この記事が気に入ったらサポートをしてみませんか?