# アンチスクレイピング検出が数えているのは、あなたが何回嘘をついたか：本物のブラウザが生まれつき破綻しない理由

ブラウザ{自動化|じどうか}をやる{人|ひと}は、いずれ{必|かなら}ずある{落|お}とし{穴|あな}にはまる。{自動化|じどうか}ブラウザを{真人間|まにんげん}のように{偽装|ぎそう}しようとあらゆる{手|て}を{尽|つ}くし、サイトに{一目|ひとめ}で{見破|みやぶ}られ、さらに{一層|いっそう}{偽装|ぎそう}を{足|た}し、また{見破|みやぶ}られる。私{自身|じしん}がこのことを{理解|りかい}できたのは、「{検出|けんしゅつ}する{側|がわ}は{結局|けっきょく}{何|なに}を{調|しら}べているのか」を{考|かんが}え{抜|ぬ}いたところからだった。

まず、{直感|ちょっかん}に{反|はん}する{結論|けつろん}から{言|い}う。CreepJS のような{検出|けんしゅつ}は、そもそも「あなたはボットか」を{直接|ちょくせつ}{判定|はんてい}しているわけではない。やっているのは{別|べつ}のことだ。ブラウザが{露出|ろしゅつ}している{数十項目|すうじゅっこうもく}のシグナルを{集|あつ}め、それらを{互|たが}いに{突|つ}き{合|あ}わせて、どこが{合|あ}わないかを{見|み}る。{一箇所|いっかしょ}{合|あ}わなければ、「{嘘|うそ}」をひとつ{記録|きろく}する。{嘘|うそ}が{多|おお}いほど、スコアは{低|ひく}くなる。{内部|ないぶ}ではこれを lies と{呼|よ}んでいる。

{視点|してん}を{変|か}えると、{多|おお}くのことが{腑|ふ}に{落|お}ちる。

![左:偽ブラウザ/自動化が CreepJS の検出で webdriver=true、headless、フィンガープリント不一致、Runtime.enable という四つの赤旗を出して失敗;右:本物の Chrome は四項目すべて緑で通過](https://img.leeguoo.com/media/f8b08367-08c2-45c4-bf08-8cb52da8abce/creepjs-hero.webp)

## {本物|ほんもの}のブラウザはどんな{姿|すがた}か

私は chrome-use を、この Mac でいま{開|ひら}いている Chrome に{接続|せつぞく}させ、よく{調|しら}べられるシグナルをいくつか{読|よ}んだ。

```json
{
  "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 の検出](https://bot-detector.rebrowser.net/)が{利用|りよう}しているのは、まさにこの{点|てん}だ。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](https://abrahamjuliot.github.io/creepjs/): trust score と、{列挙|れっきょ}された lies が{具体的|ぐたいてき}にどれかを{見|み}る。
- [rebrowser-bot-detector](https://bot-detector.rebrowser.net/): {重点|じゅうてん}は `runtimeEnableLeak` と `navigatorWebdriver`。

{普通|ふつう}のヘッドレス{自動化|じどうか}で{一度|いちど}{走|はし}らせ、さらにあなたが{毎日|まいにち}{使|つか}っているブラウザでも{一度|いちど}{走|はし}らせる。{両者|りょうしゃ}の lies リストを{並|なら}べて{見|み}れば、どこが{違|ちが}うのか、私が{説明|せつめい}するまでもない。「{本当|ほんとう}に{人間|にんげん}でなければ{通|とお}らない」{作業|さぎょう}、ログイン{状態|じょうたい}やリスク{管理|かんり}に{敏感|びんかん}な{操作|そうさ}をやるなら、{丸|まる}めるべき{嘘|うそ}のないブラウザを{使|つか}うほうが、パッチを{当|あ}て{続|つづ}けて{無理|むり}に{支|ささ}える{偽|にせ}ブラウザを{使|つか}うより、{省|はぶ}ける{手間|てま}は{少|すこ}しどころではない。

関連ドキュメント（英語）：[chrome-use · Stealth & Anti-detection](https://chrome-use.leeguoo.com/en/stealth.html)、[インストール](https://chrome-use.leeguoo.com/en/install.html)。
