概要
個人的な情報収集ツール作成時に、とあるサイトで表題のエラーに遭遇したため対処法をメモ。
動作環境等
- Windows 11
- Chrome 136.0.7103.114
- python3.12.1
- selenium 4.25.0
解決法
- リモートデバッグオプションを付けてChromeを起動する
- 対象サイトに「手動で」アクセスする
- サイトの読み込み完了後、webdriverインスタンスを生成する
- あとはwebdriverから操作
発端
とあるサイト(以降サイトAと記述)からスクレイピングするためwebdriverを用いてアクセスしたところ、ERR_HTTP2_PROTOCOL_ERRORに遭遇した。
当該エラーの理由や解決法を調べながら以下のことを試したが、どれも解決に繋がらなかった。
- ブラウザキャッシュ、Cookie、ストレージの削除
- Chrome拡張機能の無効化
- disable-http2オプションの付与
- シークレットモードでのアクセス
- DNSキャッシュの削除
Chromeの再インストールも解決法として挙げられていたが、さすがに面倒だったため行っていない。
また、エラーの発生と解決法の試行は全て「webdriverで操作しているChrome」の話であり、手動でのアクセスでは問題なく表示できていたため、IPアドレス等でアクセスブロックされている訳ではないことだけは判明していた。(そもそもアクセスブロックで表題のエラーが発生するのかは不明。400代のエラーが返されそうな気がするが…)
解決までの道のり
1. まずは手動操作から
手動でのアクセスは問題なかったことから、まずは手動でのChrome起動をベースとし、徐々に自動化のためのオプション付与や実装を行っていった。
その過程で、remote-debugging-portとincognito(シークレットモード)オプションの付与だけではエラーが発生しないことが判明したため、ひとまず上記2点のオプションを付与してChromeを起動するbatファイルを作成した。
start "" "path\to\chrome.exe" %"%対象サイトのURL%"% --user-data-dir="path\to\dir" --profile-directory="profile-name" --incognito --remote-debugging-port=9220 --remote-allow-origins=*
シークレットモードでの実行は要件に含んでいなかったが、キャッシュの削除等諸々の手間を考慮し、「可能であれば」くらいの気持ちでincognitoオプションを付与していた。
2. webdriverからChromeを起動
前項2点のオプション付与は原因ではないと仮定し、次にwebdriverから起動したChromeでサイトAへのアクセスを試みた。
from selenium.webdriver import Chrome, ChromeOptions
import subprocess
import time
def main():
proc = subprocess.run(
["chrome_remote.bat", "対象サイトのURL"],
shell=True
)
time.sleep(1)
options = ChromeOptions()
options.add_experimental_option('debuggerAddress', '127.0.0.1:9220')
drv = Chrome(options=options)
return
if __name__ == "__main__":
main()
この時点で表題のエラーが発生したためwebdriverに何らかの原因があると考えたが、原因については全く見当つかずであった。
上記コードでは省略しているが、マウスとキー入力の自動化も行っていたため、操作の間隔等をランダマイズしたり、アクセスする間隔をあけたり等、思いつく対策を一通り試していた。
それでも依然として解決には至らず、半ば諦めかけていた。
3. 手動アクセスとwebdriverの組み合わせ
ただのスクレイピングで丸二日ほど消費してしまい、我ながら情けなくなったので一旦状況を整理した。
- 手動でのアクセスは問題ない
- webdriverで操作しているChromeではエラーが発生する
- オプションの付与は原因ではない
ここでやっと一つのアイデアを思いつき、早速実装を行った。
そのアイデアというのが、「サイトAへのアクセスまでは手動で行い、ページの読み込みが完了した時点でwebdriverを使えばいけるんじゃね?」というものである。
4. ようやく解決
上記のアイデアを試行するため以下の手順を踏まえて実装したところ、無事にサイトAのスクレイピングに成功した。
from selenium.webdriver import Chrome, ChromeOptions
import subprocess
import time
def main():
# 1.remote-debugging-portを付与してChromeを起動する
proc = subprocess.run(
["chrome_remote.bat", "対象サイトのURL"],
shell=True
)
time.sleep(1)
# 2.起動したChromeを操作しておき、対象サイトの読み込み完了まで待つ
input()
# 3.ページの読み込み完了後にwebdriverインスタンスを生成する
options = ChromeOptions()
options.add_experimental_option('debuggerAddress', '127.0.0.1:9220')
drv = Chrome(options=options)
return
if __name__ == "__main__":
main()
1. リモートデバッグオプションを付けてChromeを起動する
batでも手動でも構わないが、とにかく「webdriver以外」の手段でChromeを起動する。
この時、後にwebdriverで操作するため「remote-debugging-port」オプションを付与しておく。
2. 対象サイトにアクセスする
こちらも手動、自動は問わず対象サイトにアクセスする。
ただし、この時点ではまだwebdriverは使わないこと。
3. ページの読み込み完了後、webdriverインスタンスを生成する
サイトの読み込みが完了した後で、「debuggerAddress」を指定したオプションを引数としてwebdriverインスタンスを生成する。
4. あとはwebdriverから操作
ページの読み込みさえ完了すればwebdriverでもエラーが発生しないため、あとは自由にスクレイピングが可能となる。
原因は不明?
当初の目標は達成できたが、結局エラーの原因は分からないままである。
HTTP2プロトコルについての知識もあやふやであり、webdriverと手動実行したChromeの差異も不明である。
稚拙な内容となってしまったが、私と同じように悩まされた方の助けになれば嬉しい。