ブラウザ自動化をやる人は、いずれ必ずある落とし穴にはまる。自動化ブラウザを真人間のように偽装しようとあらゆる手を尽くし、サイトに一目で見破られ、さらに一層偽装を足し、また見破られる。私自身がこのことを理解できたのは、「検出する側は結局何を調べているのか」を考え抜いたところからだった。
まず、直感に反する結論から言う。CreepJS のような検出は、そもそも「あなたはボットか」を直接判定しているわけではない。やっているのは別のことだ。ブラウザが露出している数十項目のシグナルを集め、それらを互いに突き合わせて、どこが合わないかを見る。一箇所合わなければ、「嘘」をひとつ記録する。嘘が多いほど、スコアは低くなる。内部ではこれを lies と呼んでいる。
視点を変えると、多くのことが腑に落ちる。

本物のブラウザはどんな姿か
私は chrome-use を、この Mac でいま開いている Chrome に接続させ、よく調べられるシグナルをいくつか読んだ。
{
"webdriver": false,
"gpu": "ANGLE (Apple, ANGLE Metal Renderer: Apple M3, Unspecified Version)",
"plugins": 5,
"languages": "en,en-US,ja",
"timeZone": "Asia/Tokyo"
}
一見すると平凡だが、どの項目も検出側が注視している点だ。
navigator.webdriver は false。W3C の WebDriver 仕様では、ブラウザが自動化制御されているとき、この getter は true を返すと定められている。Selenium、Puppeteer、Playwright は既定で true だ。ここでは false になっている。なぜなら、実際にフレームワークが駆動しているわけではなく、私本人が使っているからだ。
GPU の Apple M3 という文字列は、WebGL の WEBGL_debug_renderer_info 拡張から取得した本物のGPU名だ。ヘッドレス Chrome には本物の GPU がないため、この項目はたいてい SwiftShader や llvmpipe、つまりソフトウェアレンダリングになる。ソフトウェアレンダリング自体が違反というわけではない。だが「普通の Mac だ」と名乗るブラウザと並べると、矛盾になる。
plugins が 5 個、時区が Asia/Tokyo、言語リストに ja がある。こうした細かなシグナルは、単独で見ても意味はない。価値があるのは、それらが**整合するかどうか**だ。
検出の決め手は相互照合
自動化ブラウザが偽装するとき、たいてい変えるのは表層だ。UA を変えて Mac だと嘘をつき、navigator.webdriver を変え、スクリプトを注入して headless の痕跡を覆い隠す。だが UA は変えられても、それに連動する大量のものまでは変えられない。
UA は macOS と言っているのに、WebGL は llvmpipe、つまり Linux のソフトウェアレンダリングでよく見る値を返す。合わない。時区は東京だと主張しているのに、出口 IP はフランクフルトにある。合わない。画面解像度を手動で変えても、devicePixelRatio と Canvas が実際にレンダリングしたサイズがまた合わない。CreepJS はこれらのシグナルを二つずつ検証し、矛盾ごとに嘘をひとつ記録する。
だから偽装の本当の難しさは、「ある一項目を隠す」ことではまったくない。その一項目を隠したあと、残り数十項目とも自己整合していなければならないことだ。ひとつ変えると、一連の漏れが出る。これは偽装が生まれつき抱える不利だ。
もっとも隠しにくい痕跡は、JS 層にはない
ここまでのものは、まだ JS から読めるシグナルであり、理論上は hook で潰せる。しかし、もっと下の層にある痕跡もある。
Chrome DevTools Protocol でブラウザを駆動するとき、いったん Runtime.enable を送ると、多くの CDP ライブラリは既定でこれを送るが、Chrome は実行コンテキストや console 呼び出しをプロトコル経由で外へ報告し始める。rebrowser の検出が利用しているのは、まさにこの点だ。getter つきのオブジェクトを作り、console.debug する。Runtime.enable が有効になっているかぎり、CDP はそのオブジェクトをシリアライズして外へ渡さなければならない。シリアライズはその getter を発火させる。getter が反応した瞬間、ページはその場で「Runtime.enable を開いたデバッガが自分にぶら下がっている」と知る。
この痕跡がつらいのは、それが JS 内のどこかの属性ではないことだ。hook では消せない。あなたが選んだ駆動方式そのものが残すものだからだ。rebrowser のパネルには runtimeEnableLeak という項目が専用にあり、隠密を謳う多くの方式がここで転ぶ。
本物のブラウザがなぜ楽なのか
冒頭の項目に戻って見る。本物の Chrome の webdriver はもともと false で、GPU はもともと Apple M3 で、時区、言語、Canvas はもともと同じ実機から出ていて、どこも自己整合している。数えるべき嘘がない。chrome-use はブラウザ拡張と native messaging でそれに接続し、--remote-debugging-port を開かず、中継パスも既定では Runtime.enable を開かない。プロトコル層のあの痕跡すらない。
これは「偽装がうまい」のではない。そもそも偽装すべきものがないのだ。
さらに見落とされがちな層がある。パッチを当てる行為自体も痕跡を残す。navigator.webdriver を hook するそのコードは、属性を上書きするときに property descriptor を変え、toString の戻り値を変える。検出側は逆に「この属性は誰かに手を加えられていないか」を調べればよい。補修すればするほど、手を加えられたと指摘できる場所が増える。本物のブラウザにはこの負担がない。パッチを当てていないからだ。
自分で比較してみる
私の言うことを信じる必要はない。ふたつのブラウザでそれぞれ一度走らせるのがいちばん直接的だ。
- CreepJS: trust score と、列挙された lies が具体的にどれかを見る。
- rebrowser-bot-detector: 重点は
runtimeEnableLeakとnavigatorWebdriver。
普通のヘッドレス自動化で一度走らせ、さらにあなたが毎日使っているブラウザでも一度走らせる。両者の lies リストを並べて見れば、どこが違うのか、私が説明するまでもない。「本当に人間でなければ通らない」作業、ログイン状態やリスク管理に敏感な操作をやるなら、丸めるべき嘘のないブラウザを使うほうが、パッチを当て続けて無理に支える偽ブラウザを使うより、省ける手間は少しどころではない。
関連ドキュメント(英語):chrome-use · Stealth & Anti-detection、インストール。

WeChat Pay
Alipay
コメント
コメントは即時公開されますが、ポリシー違反時は非表示になる場合があります。