文書の過去の版を表示しています。
目次
1. 概要
- selnium は,web のテストフレームワーク.
- ブラウザを外部から叩きつつ,テスト仕様通りに動作しているかを確認できる.
- chrome の場合,webdriver が daemon として動作しつつ,ブラウザとは独立したプログラムである devtool を叩くことで chrome を動作させている模様.
- 詳細は https://chromedriver.chromium.org/home ここら辺を読むときっといっぱい書いてあるに違いない.W3C でも標準化されてるっぽい.
- ブラウザを起動しているので,XmlHttpRequest(AJAX) などで動的に追加された要素など,HTMLのパースだけでは取得できないデータの取得が可能.ただし,グラフなどの canvas に描画されてしまったものは,多分取得できない.
Xvfb と併用することで,サーバにおいてブラウザを立ち上げて,スクレイピングしたりスクリーンショットをとったりしやすい.2018 年くらいには -headless モードが実装されており,Xvfb と併用しなくてサーバ内で動作が完結するようになっていた模様.
2. インストール
2.1 ブラウザ環境
- firefox または Chrome を ports で入れておく.
2.2 python からの利用 [2023-09-07] [2022-08-17]
特に特別なライブラリを入れずとも動作する.- pip で selenium が入っているか確認する.もし入ってなかったら,
# pip list | grep selenium # 入っているか確認 # pip install selenium # うまく動作した. # portmaster -D www/py-selenium # うまく動作してなかったかもしれない.
2.3 ruby binding 環境での構築 [2014年頃]
- 本当は,rvm 環境とかを使う方が良いのだけど,FreeBSD での利用方法を良く知らないので,system wide に入れちゃう.
- devel/ruby-gems を ports で入れておく.
- 以下の gem を入れる.
% sudo gem install selenium-webdriver % sudo gem install watir-webdriver
- 以下参照しながら,適当にサンプルを.
- ruby bindings 専用記事: http://code.google.com/p/selenium/wiki/RubyBindings
- 本ページと似たような説明文章(日本語): http://yakinikunotare.boo.jp/orebase2/ruby/web_browser_control
2.4 tips
2.4.1 selenium (bot) 検知への対応について
特定のサイトでは,selenium でのアクセスを禁止している.UA などのアクセス情報から selenium で動作していることを検知している.
2.4.1.1 selenium detection の解説サイト
undetected chromedriver [2023-09-07]
selenium であることの検知は,主に chromedriver (webdriver) と chrome の通信の中に痕跡が残り,アクセス時にその痕跡がサーバ側に通知されてしまうことで発生する模様.
これを解決する為,通常の chromedriver に少し手を加えるツールが存在している.[2023-09-07] 時点で試したのは,undetected-chromedriver.
undetected-chromedriver は内部的に,以下のような動作になっている. * chrome のバージョンが指定されてなかったら,chrome のバージョンを取得する * そのバージョンと同じ chromedriver をダウンロードしてくる.Windows, Mac, Linux のバイナリに対応している. * chromedriver そのものにパッチをあてる. * python で webdriver.Chrome() として chromedriver→chrome と呼び出すところを,undetected_chromedriver.Chrome() でラップし,UA 書き換えなどを行なった状態で,chromedriver→chrome と呼び出すようにする
UA について
- headless モードで動作させると,UA に headless であることが記載されてしまう.
- 不都合が生じる場合は,chrome の起動オプションで UA の変更ができるので,headless ではない記載をすると良い.UA のリストはググルといっぱい出てくる.
/tmp に作成される cache の場所を変更したい.[2022-08-17] [2014年頃]
chrome 一般 [2022-08-17]
- chrome が screenshot をとる際,デフォルトでは /tmp を利用する.
- 環境変数 TMPDIR を指定すれば screenshot の一時保存先の変更が可能.
% export TMPDIR=/exp/tmp
- [2022-08-17] headless 環境では,–disable-gpu をつけることで,動作が安定した.もしかしたら,–no-sandbox,–disable-dev-shm-usage も効いているかもしれないが,詳細は実験していない.
ruby の場合 [2014 年頃]
- webdriver が利用する cache が Dir.mktmpdir で作成されるため,/tmp に必ず作成される.大量のページをスクレイプする場合には,/tmp を使いきってしまう可能性がある.
- /usr/local/lib/ruby/gems/1.8/gems/selenium-webdriver-2.27.2/lib/selenium/webdriver/firefox/profile.rb の Dir.mktmpdir を以下のように書き換えて,とりあえずしのぐ.
profile_dir = @model ? create_tmp_copy(@model) : Dir.mktmpdir("webdriver-profile") ↓ profile_dir = @model ? create_tmp_copy(@model) : Dir.mktmpdir("webdriver-profile", "/exp/tmp")
freebsd における tips
undetected-chromedriver の動作設定 [2023-09-07]
undetected-chromedriver がダウンロードできるバイナリは上述の通りで,FreeBSD で動作させる為には,以下のどちらかの対応かと考えた.
- Linux emulation で chromedriver を動かす
- FreeBSD のバイナリにパッチを当てられるようにする.
こちらを見ると,chromedriver のみ linux emulation で動かし,chrome を FreeBSD バイナリというのは成功している模様だったので,1 を試したところ成功したので,FreeBSD バイナリへのパッチあては試していない.
linux emulation 設定
python ライブラリの準備
# pip install undetected-chromedriver
[2023-09-07] 時点での最新版は 3.5.3.
python ライブラリの FreeBSD 対応
(もっときれいなやりかたをした方が良いと思う)
/usr/local/lib/python3.9/site-packages/undetected_chromedriver 以下に,下記のファイルが存在している.
__init__.py devtool.py options.py reactor.py cdp.py dprocess.py patcher.py webelement.py
patcher.py が OS の分岐,ダウンロードバイナリの選定,パッチあて,などを行なっている.OS の分岐の部分で,Linux に振っているところに FreeBSD を入れる.また,unzip したあとに,executable にならないことがあった為,chmod コマンドで実行権をつけた.以下にパッチを示す.
< import subprocess 25c24 < IS_POSIX = sys.platform.startswith(("darwin", "cygwin", "linux", "linux2","freebsd")) --- > IS_POSIX = sys.platform.startswith(("darwin", "cygwin", "linux", "linux2")) 37c36 < elif platform.startswith(("linux", "linux2", "freebsd")): --- > elif platform.startswith(("linux", "linux2")): 112c111 < if self.platform.endswith(("linux", "linux2")) or self.platform.startswith(("freebsd")): --- > if self.platform.endswith(("linux", "linux2")): 121d119 < # self.platform_name = "linux64" 180,181c178 < fname = self.unzip_package(self.fetch_package()) < subprocess.run(["chmod", "+x", fname]) --- > self.unzip_package(self.fetch_package())
dprocess.py が chromedriver プロセスを立ち上げている.ここの中で,start_detacher() という関数が使われているようで,multiprocessing.Process の前の行に,以下を追加.
os.environ['LD_LIBRARY_PATH'] = "/compat/ubuntu/usr/lib64"
Chrome() 呼出しの際に,API Document を見ると env を渡せると書いてあったが,うまく動作させられなかった.今後の TODO.
headless への対応
undetected_chromedriver の場合,headless での動作がうまくいかない気がする.調べ切れていないが,headless で動作させると,bot detection に引っかかる可能性が高くなるっぽい.
安定動作の為には,Xvfb を利用して,Window を立ち上げてしまう方が無難.
undetected_driver.Chrome() の使い方の注意
スクリプトを書いている中で,エラーが頻発した際などへの対応として,Chrome の再起動を行なう場合がある.
その時,Chrome に渡すオプションを扱う為の Opiotns() というクラスがあるが,これを再利用してはいけない模様.init.py の中に,以下のような記載があった.
try: if hasattr(options, "_session") and options._session is not None: # prevent reuse of options, # as it just appends arguments, not replace them # you'll get conflicts starting chrome raise RuntimeError("you cannot reuse the ChromeOptions object")
firefox? chrome? [2022-08-17]
- 2022 年 8 月時点では、ports において Firefox 向けの webdriver の更新が止まっているので、動作しない。
- Chrome は、chromium のソースコード内に webdriver が存在しているので、バージョンミスマッチを気にせず利用可能.
firefox のバグ? [2022-08-17] [2014年頃]
- 一定以上の高さ(20000pxとか)のあるページをキャプチャしようとすると,save_screenshot が JS 部分で落ちる.
- 実装的には,firefox の js エンジンで,ページを canvas に書き出し,PNG として保存している.
- ./firefox/extension/webdriver.xpi の中にある driver_component.js.
- 多分,firefox のメモリ管理の問題で,一定以上の大きさの画像を扱えないと思われる.
- workaround としては,以下のように javascript を実行して window の高さを事前に取得し,一定以上なら,js 経由ではなく import コマンド経由で取得する.
window_height = @browser.driver.execute_script("return document.body.clientHeight;") if (window_height < 15000) # within 15000px, capture whole page. otherwise, capture on\
ly the displayed window
@browser.screenshot.save("file.png") else # hoge end
- [2022-08-17] 2022 年時点の chrome では,高さを 2 万 px などにしても問題はなかった.マシンは 2014 年時点と同じマシンなので,スペックの問題ではないと思われる.
patch [2014年頃]
- 起動のタイミングの関係で、ネットワークコネクションが立ち上がるまで sleep を入れる必要があるみたい。(かな?)