差分

このページの2つのバージョン間の差分を表示します。

この比較画面へのリンク

両方とも前のリビジョン前のリビジョン
次のリビジョン
前のリビジョン
selenium-webdriver [2023/09/07 21:09] skkselenium-webdriver [2025/02/22 16:07] (現在) skk
行 1: 行 1:
-= [[selenium-webdriver]]+= [[selenium-webdriver]] LM: [2025-02-22 16:05:39] 
 +~~NOCACHE~~ 
 ---- ----
  
行 23: 行 25:
 </code> </code>
  
 +  * [2024-06-27] chrome 126 を headless で動かそうとしたが上手く動作せず.もしかしたら,下記 URL のように,FreeBSD だと上手く動かないのかもしれない.X forward/デスクトップ環境以外で,headless のように動かしたいならば,Xvfb の方が安定すると思われる.
 +      * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=278413
  
 ===== - ruby binding 環境での構築  [2014年頃] ===== ===== - ruby binding 環境での構築  [2014年頃] =====
行 37: 行 41:
     * 本ページと似たような説明文章(日本語): http://yakinikunotare.boo.jp/orebase2/ruby/web_browser_control     * 本ページと似たような説明文章(日本語): http://yakinikunotare.boo.jp/orebase2/ruby/web_browser_control
  
-===== tips  ===== +===== tips  ===== 
-==== selenium (bot) 検知への対応について ==== +==== - 画像保存の方法 ==== 
 + 
 +[2025-01-13] 
 + 
 +selenium を使って,Web 上の画像を保存する方法は,大きく分けて, 
 + 
 +  - screenshot をとる 
 +  - 名前をつけて保存 
 +  - 画像の URL を取得し,request.get() する 
 + 
 +の3つな気がする.1 番目は表示さえされてれば確実に画像が保存できるのだが,画像が縮小されていると画質が落ちてしまうし,取得した画像を jpeg や webp に変換していると結構な CPU リソースを食ってしまう. 
 + 
 +2 番目の名前をつけて保存は,もし画像の URL とスクレイピング先のホストが同じ場合,キャッシュから保存されるので,1度の URL アクセスで画像が保存できるのでもっとも効率的になるが,違う場合,クロスサイト対策(?)の関係で,もう一度 URL アクセスが発生してしまう. 
 + 
 +そのため,2 番目と 3 番目はアクセスという面で考えると同じになってしまう. 
 + 
 +状況に応じて,適した方法を選ぶ必要がある. 
 + 
 +==== - 名前をつけて保存 ==== 
 + 
 +[2025-01-13] 
 + 
 +ブラウザ上の画像を右クリックし,名前をつけて保存,と押すと,保存ダイアログが出てきて,保存先を選び,画像を保存することになる. 
 +この時出てくる保存ダイアログは,ブラウザの一部ではなく,OS(Windows)の管理下にあるので,selenium からコントロールすることができない. 
 +よって,名前をつけて保存の処理を自動で行ないたい場合は,selenium とは別の枠組で考える必要がある. 
 + 
 +FreeBSD/Linux などの XWindow な環境の場合,xdotool を利用すると,簡単に XWindow 上での操作が行なえるので,selenium と組み合わせると便利かもしれない.他にも pyautogui などがあるようだが,僕の FreeBSD 環境では上手く動かせなかった.[[http://takeno.iee.niit.ac.jp/~shige/FreeBSD/fbsd-omemo.html#o-20220127|参考1]][[https://stackoverflow.com/questions/58432057/how-do-i-click-enter-when-a-save-as-window-is-open-with-selenium|参考2]] 
 + 
 +僕の場合,ウィンドウマネージャを指定せずに,Xvfb に対して seleinum で操作している chromium を表示している状態である. 
 + 
 +次に,selenium において,次のように javascript を利用してブラウザ上での右クリックを行なわせる. 
 + 
 +<code python> 
 +driver.execute_script(f""" 
 +   const downloadImage = document.createElement('a'); 
 +   document.body.appendChild(downloadImage); 
 +   downloadImage.setAttribute('download', 'image'); 
 +   downloadImage.href = '{img_src}'; 
 +   downloadImage.click(); 
 +"""
 +</code> 
 + 
 +実行すると,Xvfb 上では名前をつけて保存ダイアログにフォーカスがあたった状態で出てくる. 
 +あとは,保存先ディレクトリを指定し,保存ボタンを押す.xdotool ならば, 
 + 
 +<code bash> 
 +% xdotool key Home 
 +% xdotool type /home/skk/Downloads/ 
 +% xdotool key Return  
 +</code> 
 + 
 +となる.もしコマンドライン上で実際に動かす場合は,DISPLAY 環境変数も指定しなければならない. 
 +これらのコマンドを python 上でコマンド呼出せばよい.例えば,以下のような形. 
 + 
 +<code python> 
 +xdotool = ["xdotool", "key", "Home"
 +subprocess.run(xdotool) 
 +</code> 
 + 
 +==== - selenium (bot) 検知への対応について ==== 
 特定のサイトでは,selenium でのアクセスを禁止している.UA などのアクセス情報から selenium で動作していることを検知している. 特定のサイトでは,selenium でのアクセスを禁止している.UA などのアクセス情報から selenium で動作していることを検知している.
  
-=== selenium detection の解説サイト ===  +=== selenium detection の解説サイト ===  
- * https://stackoverflow.com/questions/67551031/how-to-pass-desired-capabilities-to-undetected-chromedriver-with-selenium-python +  * https://stackoverflow.com/questions/67551031/how-to-pass-desired-capabilities-to-undetected-chromedriver-with-selenium-python 
- * https://stackoverflow.com/questions/72406597/how-to-avoid-bot-detection-on-websites-using-selenium-python+  * https://stackoverflow.com/questions/72406597/how-to-avoid-bot-detection-on-websites-using-selenium-python 
 +  * [2025-02-22] https://www.zenrows.com/blog/selenium-cloudflare-bypass
  
-=== undetected chromedriver [2023-09-07] ===+=== undetected chromedriver [2023-09-07] ===
 selenium であることの検知は,主に chromedriver (webdriver) と chrome の通信の中に痕跡が残り,アクセス時にその痕跡がサーバ側に通知されてしまうことで発生する模様. selenium であることの検知は,主に chromedriver (webdriver) と chrome の通信の中に痕跡が残り,アクセス時にその痕跡がサーバ側に通知されてしまうことで発生する模様.
  
行 51: 行 115:
  
 undetected-chromedriver は内部的に,以下のような動作になっている. undetected-chromedriver は内部的に,以下のような動作になっている.
- * chrome のバージョンが指定されてなかったら,chrome のバージョンを取得する +  * chrome のバージョンが指定されてなかったら,chrome のバージョンを取得する 
- * そのバージョンと同じ chromedriver をダウンロードしてくる.Windows, Mac, Linux のバイナリに対応している. +  * そのバージョンと同じ chromedriver をダウンロードしてくる.Windows, Mac, Linux のバイナリに対応している. 
- * chromedriver そのものにパッチをあてる. +  * chromedriver そのものにパッチをあてる. 
- * python で webdriver.Chrome() として chromedriver->chrome と呼び出すところを,undetected_chromedriver.Chrome() でラップし,UA 書き換えなどを行なった状態で,chromedriver->chrome と呼び出すようにする+  * python で webdriver.Chrome() として chromedriver->chrome と呼び出すところを,undetected_chromedriver.Chrome() でラップし,UA 書き換えなどを行なった状態で,chromedriver->chrome と呼び出すようにする
  
-=== UA について ===+FreeBSD で動作させる為には,[[https://www.sakaki.works/doku/doku.php?id=selenium-webdriver#undetected-chromedriver_%E3%81%AE%E5%8B%95%E4%BD%9C%E8%A8%AD%E5%AE%9A_2023-09-07|下記]]参照. 
 + 
 +=== - seleniumbase [2025-02-22] === 
 +undetected_chromedriver は,2025/02 現在,あまりメンテされておらず,Cloudflare などの anti-bot システムで検知されがちなツールになってきている模様. 
 + 
 +ZenRow というクラウドサービスを使っても anti-bot の回避はできるようだが月額がまーまー高い.広告記事だと思うけど,この ZenRow の特集記事が良くできているので参考に読むと良い. 
 + 
 +今は,SeleniumBase というツールまたは,nodriver というツールが最近ではアクティブな模様.nodriver は undetected_chromedriver の作者が作っている後継だけど,selenium の書きにくいところと決別したいらしく,結構独自の書き方にしないといけなくて,既存のコードがある場合には導入しにくい.SeleniumBase は undetected_chromedriver を fork して独自進化させてるっぽいので,undetected_chromedriver からの乗り換えにはとても便利. 
 + 
 +また,multiprocessing 環境への対応を頑張った形跡が見られるのも嬉しい.undetected_chromedriver は,複数のプロセスを動かそうとすると,Text Busy と言われることがちょいちょいあった.これは,ChromeDriver を利用するたびに chromedriver をダウンロードしてきてパッチを当ててたので,複数プロセスで動かそうとすると,時々競合のような状態になっていたと想像している. 
 +SeleniumBase は,初回起動時に uc_driver という,パッチを当てまくった chromedriver を作成して,以降は必要がなければずっとそれを使い続けるので,複数のプロセスから ChromeDriver(=uc_driver) を利用しても問題が起きない. 
 + 
 +さらに,undetected_chromedriver を FreeBSD で無理矢理動かしていた時は,zombie プロセスが大量に作成されてしまったので,定期的にプログラムを再起動してゾンビを殺していたが,終了処理などがきれいになっているのか,SeleniumBase だと zombie が発生しなかった. 
 + 
 +ということで,今から利用する場合は,SeleniumBase の方が全然良い.(nodriver ももしかしたら良いのかもしれないけど,試してはいない) 
 + 
 +=== - UA について ===
   * headless モードで動作させると,UA に headless であることが記載されてしまう.   * headless モードで動作させると,UA に headless であることが記載されてしまう.
   * 不都合が生じる場合は,chrome の起動オプションで UA の変更ができるので,headless ではない記載をすると良い.UA のリストはググルといっぱい出てくる.   * 不都合が生じる場合は,chrome の起動オプションで UA の変更ができるので,headless ではない記載をすると良い.UA のリストはググルといっぱい出てくる.
行 63: 行 143:
  
  
-==== /tmp に作成される cache の場所を変更したい.[2022-08-17] [2014年頃]  ====+==== /tmp に作成される cache の場所を変更したい.[2022-08-17] [2014年頃]  ====
  
-=== chrome 一般 [2022-08-17] === +=== chrome 一般 [2022-08-17] === 
   * chrome が screenshot をとる際,デフォルトでは /tmp を利用する.   * chrome が screenshot をとる際,デフォルトでは /tmp を利用する.
   * 環境変数 TMPDIR を指定すれば screenshot の一時保存先の変更が可能.   * 環境変数 TMPDIR を指定すれば screenshot の一時保存先の変更が可能.
行 75: 行 155:
   * [2022-08-17] headless 環境では,--disable-gpu をつけることで,動作が安定した.もしかしたら,--no-sandbox,--disable-dev-shm-usage も効いているかもしれないが,詳細は実験していない.   * [2022-08-17] headless 環境では,--disable-gpu をつけることで,動作が安定した.もしかしたら,--no-sandbox,--disable-dev-shm-usage も効いているかもしれないが,詳細は実験していない.
  
-=== ruby の場合 [2014 年頃] === +=== ruby の場合 [2014 年頃] === 
   * webdriver が利用する cache が Dir.mktmpdir で作成されるため,/tmp に必ず作成される.大量のページをスクレイプする場合には,/tmp を使いきってしまう可能性がある.   * 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 を以下のように書き換えて,とりあえずしのぐ.   * /usr/local/lib/ruby/gems/1.8/gems/selenium-webdriver-2.27.2/lib/selenium/webdriver/firefox/profile.rb の Dir.mktmpdir を以下のように書き換えて,とりあえずしのぐ.
行 88: 行 168:
  
  
-====== freebsd における tips  ====== +====== freebsd における tips  ====== 
-===== undetected-chromedriver の動作設定 [2023-09-07] =====+===== - SeleniumBase の動作設定 [2025-02-22] ===== 
 +SeleniumBase もソースコードないでは Linux への分岐しか対応してない.ただ,undetected_chromedriver は Linux バイナリをダウンロードしてきていたので,Linux Emulation しなければならなかったが,SeleniumBase は chromedriver に対してなんらかの方法でパッチを当てているので,FreeBSD でインストールできる Chromium に附属している chromedriver をベースにして動作する.つまり,linux emulation しなくても大丈夫. 
 + 
 +==== - python ライブラリの準備 ==== 
 +<code bash> 
 +# pip install seleniumbase  
 +</code> 
 + 
 +==== - python ライブラリの FreeBSD 対応 ==== 
 +/usr/local/lib/python3.11/site-packages/seleniumbase/undetected  以下に,下記のファイルが存在している. 
 + 
 +<code> 
 +__init__.py     cdp_driver      dprocess.py     patcher.py      webelement.py 
 +__pycache__     cdp.py          options.py      reactor.py 
 +</code> 
 + 
 +patecher.py で以下. 
 + 
 +<code python> 
 +15c16 
 +< IS_POSIX = sys.platform.startswith(("darwin", "cygwin", "linux")) 
 +--- 
 +> IS_POSIX = sys.platform.startswith(("darwin", "cygwin", "linux", "freebsd")) 
 +30c31 
 +<     if sys_plat.endswith("linux"): 
 +--- 
 +>     if sys_plat.endswith("linux") or sys_plat.endswith("freebsd"): 
 +</code> 
 + 
 +/usr/local/lib/python3.11/site-packages/seleniumbase/fixtures/shared_utils.py というファイルでも OS 分岐を行なっていたので,ここでも Linux と同じ動作にしてしまう. 
 + 
 +<code python> 
 +47 def is_linux(): 
 +48     return "linux" in sys.platform or "freebsd" in sys.platform 
 +</code>  
 + 
 +これで少なくとも,僕の環境では SeleniumBase が FreeBSD で動作している. 
 + 
 +===== - undetected-chromedriver の動作設定 [2023-09-07] =====
 undetected-chromedriver がダウンロードできるバイナリは上述の通りで,FreeBSD で動作させる為には,以下のどちらかの対応かと考えた. undetected-chromedriver がダウンロードできるバイナリは上述の通りで,FreeBSD で動作させる為には,以下のどちらかの対応かと考えた.
  
行 97: 行 215:
 [[https://www.slideshare.net/hirofumitouhei/selenium-webdriverfreebsd-102google-chrome|こちら]]を見ると,chromedriver のみ linux emulation で動かし,chrome を FreeBSD バイナリというのは成功している模様だったので,1 を試したところ成功したので,FreeBSD バイナリへのパッチあては試していない. [[https://www.slideshare.net/hirofumitouhei/selenium-webdriverfreebsd-102google-chrome|こちら]]を見ると,chromedriver のみ linux emulation で動かし,chrome を FreeBSD バイナリというのは成功している模様だったので,1 を試したところ成功したので,FreeBSD バイナリへのパッチあては試していない.
  
-==== linux emulation 設定 ====+==== linux emulation 設定 ====
   *  [[https://www.sakaki.works/doku/doku.php?id=freebsd#linux_emulation_2023-09-07|FreeBSD 側の設定を参照]]   *  [[https://www.sakaki.works/doku/doku.php?id=freebsd#linux_emulation_2023-09-07|FreeBSD 側の設定を参照]]
  
-==== python ライブラリの準備 ====  +==== python ライブラリの準備 ====  
-<code>+<code bash>
 # pip install undetected-chromedriver  # pip install undetected-chromedriver 
 </code> </code>
行 107: 行 225:
 [2023-09-07] 時点での最新版は 3.5.3. [2023-09-07] 時点での最新版は 3.5.3.
  
-==== python ライブラリの FreeBSD 対応 ====+==== python ライブラリの FreeBSD 対応 ====
 (もっときれいなやりかたをした方が良いと思う) (もっときれいなやりかたをした方が良いと思う)
  
行 151: 行 269:
  
  
-==== headless への対応 ====+==== headless への対応 ====
 undetected_chromedriver の場合,headless での動作がうまくいかない気がする.調べ切れていないが,headless で動作させると,bot detection に引っかかる可能性が高くなるっぽい. undetected_chromedriver の場合,headless での動作がうまくいかない気がする.調べ切れていないが,headless で動作させると,bot detection に引っかかる可能性が高くなるっぽい.
  
 安定動作の為には,Xvfb を利用して,Window を立ち上げてしまう方が無難. 安定動作の為には,Xvfb を利用して,Window を立ち上げてしまう方が無難.
  
-==== undetected_driver.Chrome() の使い方の注意 ====+==== undetected_driver.Chrome() の使い方の注意 ====
 スクリプトを書いている中で,エラーが頻発した際などへの対応として,Chrome の再起動を行なう場合がある. スクリプトを書いている中で,エラーが頻発した際などへの対応として,Chrome の再起動を行なう場合がある.
  
-その時,Chrome に渡すオプションを扱う為の Opiotns() というクラスがあるが,これを再利用してはいけない模様.__init__.py の中に,以下のような記載があった.+その時,Chrome に渡すオプションを扱う為の Opiotns() というクラスがあるが,これを再利用してはいけない模様.%%__init__.py%% の中に,以下のような記載があった.
  
 <code python> <code python>
行 171: 行 289:
  
  
-===== firefox? chrome? [2022-08-17] =====+===== firefox? chrome? [2022-08-17] =====
   * 2022 年 8 月時点では、ports において Firefox 向けの webdriver の更新が止まっているので、動作しない。   * 2022 年 8 月時点では、ports において Firefox 向けの webdriver の更新が止まっているので、動作しない。
   * Chrome は、chromium のソースコード内に webdriver が存在しているので、バージョンミスマッチを気にせず利用可能.   * Chrome は、chromium のソースコード内に webdriver が存在しているので、バージョンミスマッチを気にせず利用可能.
  
-===== firefox のバグ?  [2022-08-17] [2014年頃] =====+===== firefox のバグ?  [2022-08-17] [2014年頃] =====
   * 一定以上の高さ(20000pxとか)のあるページをキャプチャしようとすると,save_screenshot が JS 部分で落ちる.   * 一定以上の高さ(20000pxとか)のあるページをキャプチャしようとすると,save_screenshot が JS 部分で落ちる.
   * 実装的には,firefox の js エンジンで,ページを canvas に書き出し,PNG として保存している.   * 実装的には,firefox の js エンジンで,ページを canvas に書き出し,PNG として保存している.
行 183: 行 301:
 <code> <code>
 window_height = @browser.driver.execute_script("return document.body.clientHeight;") window_height = @browser.driver.execute_script("return document.body.clientHeight;")
-if (window_height < 15000) # within 15000px, capture whole page. otherwise, capture on\ +if (window_height < 15000) # within 15000px, capture whole page. otherwise, capture only the displayed window
-</code> +
-ly the displayed window +
-<code>+
  @browser.screenshot.save("file.png")  @browser.screenshot.save("file.png")
 else  else 
行 194: 行 309:
  
   * [2022-08-17] 2022 年時点の chrome では,高さを 2 万 px などにしても問題はなかった.マシンは 2014 年時点と同じマシンなので,スペックの問題ではないと思われる.   * [2022-08-17] 2022 年時点の chrome では,高さを 2 万 px などにしても問題はなかった.マシンは 2014 年時点と同じマシンなので,スペックの問題ではないと思われる.
 +  * [2023-09-07] window.scrollTo() などをうまく利用して,数万 px の高さの Window は作らない方が無難と思われる.どこにバグが潜んでいるか怪しい.
  
-===== patch [2014年頃] =====+===== patch [2014年頃] =====
   * 起動のタイミングの関係で、ネットワークコネクションが立ち上がるまで sleep を入れる必要があるみたい。(かな?)   * 起動のタイミングの関係で、ネットワークコネクションが立ち上がるまで sleep を入れる必要があるみたい。(かな?)
   * https://github.com/rdeguzman/selenium-webdriver/blob/master/lib/selenium/webdriver/common/socket_poller.rb   * https://github.com/rdeguzman/selenium-webdriver/blob/master/lib/selenium/webdriver/common/socket_poller.rb
 +
 +----
 +このページへのアクセス
 +今日: {{counter|today}} / 昨日: {{counter|yesterday}}
 +総計: {{counter|total}}
  
selenium-webdriver.1694088596.txt.gz · 最終更新: 2023/09/07 21:09 by skk
文書の先頭へ
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0