ブラウザ操作中の通信の内容をローカルファイルに自動で保存する

Pocket

本記事は JavaScript Advent Calendar 2023 の4日目の記事だ。

3日目は @feo52 氏の 最初のレンダリング直前に起動するpagerevealイベント #JavaScript だった。
Window インターフェース に最初のレンダリング直前に呼ばれる pagereveal イベントを追加する提案の実装を Chrome で試してみる内容だ。これは確かに便利そうなイベントに感じる。
令和の IE こと Safari の存在のせいでなかなか Web 標準に組み込まれる敷居が高いものの、是非ワーキンググループでの議論が進んでいただきたいモノだ。
ところで、こういうのって CSS のワーキンググループでドラフトに載ったりするのね。詳しい流れはよく知らんけども。


いにしえの自分の回答がわかりにくい

さて、ブラウザが裏で呼んでいる API の内容をファイルに書き出したかったのだけれども、ファイルサイズが大きいからか開発者ツール経由ではうまく保存できなかった。
そこで、ブラウザ操作中の通信の内容をローカルファイルに自動で保存する方法がなにか無いかな~と探していたら、偶然にも以前に自分自身がスタック・オーバーフローで回答した puppeteer を使った方法についての内容がヒットしてしまった。

すっかり忘れていたのだが、今見返すとちょっとこのままだと使いづらそうなので、もうちょい使いやすくまとめてみる。

基本的には以前に書いた以下の記事と同じ考え方で、 Chrome DevTools ProtocolFetch Domain を直叩きして、特定の通信の内容を横取りしてローカルに保存している。

実際のコード

前提として、 Chrome がインストールされている必要がある。

任意のディレクトリに上記 GitHub Gist の中身を展開して、 npm install で依存パッケージを展開してから node intercept-requests.js <保存先のディレクトリパス> と実行する。
立ち上がったブラウザのナビゲーションバーに表示したい URL を入れてブラウジングすると、読み込んだリソースが片っ端からローカルに保存されていく。

パフォーマンスのために、スクリプトの第2引数 (urlPattern) 経由で Fetch.RequestPattern に渡すのワイルドカードを指定して、 CDP で横取りする通信を絞ったほうが良いだろう。
(横取りした通信は全て base64 にエンコードされてコールバックに渡される為、そこそこオーバーヘッドがデカい為)

ワイルドカードでは細かい制御ができないので、更に第3引数 (matchRegExp) で保存対象とする URL を正規表現で絞り込むこともできる。
(こちらは、 base64 にエンコードされてコールバックに渡されるのは防げないので、保存する I/O 処理を減らすことが目的だ。)

Firefox での CDP 実行

余談だが、本当は Firefox での通信内容をダンプしたかった。

Firefox にも Firefox remote protocol (CDP) が存在している ので、 puppeteer.launch のオプションを変えて呼び出せば Firefox でも同じことが理論上はできそうに見える。

しかし、私が Firefox 119 で試した限りでは、 CDP Stable 1.3Fetch.enable が実装されていないようで、 UnknownMethodError@chrome 実行時エラーとなってしまう。

CDP 1.3 stable RC の各メソッドを雑に呼んでみたところ、 UnknownMethodError@chrome のエラーにならなかったのは以下の 56個ぐらいだった。
これ以外のメソッドは Firefox では実装されていない可能性が高い。

Browser.close
Browser.getVersion
DOM.describeNode
DOM.disable
DOM.enable
DOM.getBoxModel
DOM.resolveNode
Emulation.setDeviceMetricsOverride
Emulation.setTouchEmulationEnabled
Emulation.setUserAgentOverride
Fetch.disable
Input.dispatchKeyEvent
Input.dispatchMouseEvent
IO.close
IO.read
Log.disable
Log.enable
Network.deleteCookies
Network.disable
Network.emulateNetworkConditions
Network.enable
Network.getCookies
Network.setCacheDisabled
Network.setCookie
Network.setCookies
Network.setUserAgentOverride
Page.addScriptToEvaluateOnNewDocument
Page.bringToFront
Page.captureScreenshot
Page.createIsolatedWorld
Page.disable
Page.enable
Page.getFrameTree
Page.getLayoutMetrics
Page.getNavigationHistory
Page.handleJavaScriptDialog
Page.navigate
Page.navigateToHistoryEntry
Page.printToPDF
Page.reload
Performance.disable
Performance.enable
Runtime.callFunctionOn
Runtime.disable
Runtime.enable
Runtime.evaluate
Runtime.getProperties
Runtime.releaseObject
Security.disable
Security.enable
Target.activateTarget
Target.attachToTarget
Target.closeTarget
Target.createTarget
Target.getTargets
Target.setDiscoverTargets

調査方法:

// メソッド名をすべて定義
const cdpDomainMethods = [
  'Browser.addPrivacySandboxEnrollmentOverride',
  // 'Browser.close',
  'Browser.getVersion',
  'Debugger.continueToLocation',
  // ...
  // [中略]
  // ...
  'Target.TargetID',
  'Target.TargetInfo'
];
await page.goto('https://example.com');
const methodResult = [];
for (let method of cdpDomainMethods) { let ret; try { await client.send(method); ret = null; } catch (e) { ret = e.message; } methodResult.push({ method, ret }); }
const groupedByExceptionMessage = methodResult.reduce((group, l) => { let key = l.ret?.replace(/^.*?\n(.*?):[\s\S]*$/, '$1'); group[key] = group[key] ?? []; group[key].push(l.method); return group; }, {});

CDP と WebDriver BiDi について

Chrome DevTools Protocol (CDP) は基本的に Chrome でしか扱えず、上述の通り Firefox では限定的な互換のみサポートしかなかった。

現在、 Web 標準化団体 W3C では WebDriver BiDi と呼ばれる、Web ブラウザの操作の自動化の標準化が進められており、 Puppeteer も対応を開始 している。

近い将来、 実装を進めている主要な各ブラウザ(Safari を除く)では、統一的な方法で操作ができるようになるだろう。

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください