スクレイピング

レジデンシャルプロキシで適切なヘッダーとUser-Agentを設定する

クリーンなレジデンシャルIPはドアの前まで連れて行ってくれる。そこを通った後にブラウザらしく見えるかどうかはヘッダーが決め、単一の値よりも一貫性の方が重要になる。

Chris Collins

Chris Collins

2026年8月26日 · 1 分で読める

サポートの会話でよく見られるパターンがある。レジデンシャルプロキシに切り替えてブロック率が改善したものの、一部の頑固なターゲットだけが依然として拒否してくるというものだ。IPはクリーンで、地理情報も正しいのに、リクエストはまだチャレンジされる。ほとんどの場合、答えはヘッダーにある。アドレスはドアまで連れて行ってくれるが、ドアを通り抜けたときにブラウザに見えるかどうかを決めるのはヘッダーだからだ。

ヘッダー問題の根底にある間違いは、これを正しく設定すべき値のリストだと考えることだ。そうではない。互いに整合し、接続に関するその他すべてとも整合していなければならない一連の主張なのであり、部品の噛み合わない状態で組み立てられたリクエストは、何の工夫もないリクエストよりもむしろ疑わしく見える。

個々の値より整合性が勝る

まずここから始めよう。これがこの先すべての前提になるからだ。サーバーはUser-Agentを単独で評価するわけではない。UA文字列、それに伴うクライアントヒント、ヘッダーの集合とその順序、Accept-Languageの値、その下にあるTLSハンドシェイク、そしてリクエスト全体が到達したIP、これらをひとまとめのバンドルとして見ている。本物のブラウザは、同じソフトウェアがすべてを生成しているため、内部的に整合したバンドルを生成する。

スクレイパーは意図せず整合性のないバンドルを生成してしまう。ChromeのUAがPythonのHTTPライブラリのヘッダー集合を伴って届いたり、WindowsのUAがLinuxツールのTLSフィンガープリントの上に載っていたり、米国の出口がAccept-Language: de-DEを送っていたり、ブラウザを主張しているのにブラウザが要求するはずのアセットを一切要求しなかったりする。これらは個別に見れば「ボットだ」とは言わないが、矛盾はそう告げる。そして矛盾は、単一の信号よりもはるかに確実に検出しやすい。これはアンチデテクトブラウザにおけるデバイスとネットワークの対応関係と同じ論理であり、すべての層が同じ物語を語らなければならない。

つまり目標は最も説得力のあるUA文字列を用意することではない。UA、ヘッダー、TLSフィンガープリント、そして出口アドレスのすべてが、同じ、あり得る訪問者を記述しているリクエストを作ることだ。

本物のブラウザが実際に送信しているもの

User-Agentだけを設定しているなら、その時点で既に整合性を欠いている。UAだけを送ってそれ以外を何も送らないブラウザは存在しないからだ。現代のChromeがページに対して送るリクエストは、最低でもおおよそ以下のような集合を伴う。

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br, zstd
sec-ch-ua: "Chromium";v="141", "Not?A_Brand";v="24", "Google Chrome";v="141"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1

ここで単にコピーするのではなく理解しておくべき点が3つある。

クライアントヒントはUAと一致していなければならない。 sec-ch-uaのブランドリスト、sec-ch-ua-platformsec-ch-ua-mobileは、UA文字列が既に主張している内容を構造化して繰り返しているものだ。UAがWindows上のChromeだと言っているのにプラットフォームヒントがmacOSだったり、ブランドリストのバージョンとUAのバージョンが食い違っていたりすれば、隣接する2つのヘッダーで自己矛盾を起こしていることになる。最近のChromeを名乗りながらクライアントヒントを一切送らないことも、本物のChromeは送るのだから、不一致にあたる。

Sec-Fetchヘッダーはコンテキストを記述する。 これらはサーバーに、このリクエストがどのような種類のものかを伝える。トップレベルのナビゲーションなのか、サブリソースの取得なのか、同一オリジンのXHRなのかだ。ページの読み込みはDest: documentMode: navigateで、直接開いた場合はSite: none、内部リンクをたどった場合はsame-originになる。APIへのXHRはDest: emptyMode: corsだ。これらを誤ることは、まさにブラウザでは自動的に行われスクリプトでは忘れやすいがゆえに、正体を明かす手がかりになる。

Accept-Encodingは守るべき主張である。 brzstdを主張するのは、クライアントが実際にそれらを展開できる場合のみにすべきだ。ライブラリの中には、対応できないエンコーディングを主張してしまい、結果としてエラーになったり、ブラウザが交渉するものとは異なるフォールバックになったりするものがある。

Accept-Languageを出口の国に合わせる

これはプロキシ作業に特有のもので、最も一般的な自業自得型の不一致だ。ドイツのレジデンシャルIPを出口として使いながらAccept-Language: en-USを送れば、アメリカ英語向けに設定されたブラウザを使う訪問者がドイツの家庭用回線に座っているという構図を記述してしまう。実際にそうしたことは起こり得るが、信号となるほど珍しく、より実務的には返ってくる内容を変えてしまう可能性がある。多くのサイトはこのヘッダーに基づいてコンテンツを出し分けるため、地理ターゲティングを伴う収集作業が、動いているように見えながら間違った言語を取得してしまうことがある。

言語を出口に結びつけ、理想的には国を選ぶのと同じ場所でそれを行い、二つが決してずれないようにするべきだ。

import requests

MARKETS = {
    "us": "en-US,en;q=0.9",
    "de": "de-DE,de;q=0.9,en;q=0.8",
    "fr": "fr-FR,fr;q=0.9,en;q=0.8",
    "br": "pt-BR,pt;q=0.9,en;q=0.8",
}

UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
      "(KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36")

def fetch(url, country):
    proxy = f"http://customer-USERNAME-country-{country}:PASSWORD@p.shifter.io:443"
    headers = {
        "User-Agent": UA,
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,"
                  "image/avif,image/webp,*/*;q=0.8",
        "Accept-Language": MARKETS[country],      # follows the exit, always
        "Accept-Encoding": "gzip, deflate, br",
        "sec-ch-ua": '"Chromium";v="141", "Not?A_Brand";v="24", '
                     '"Google Chrome";v="141"',
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": '"Windows"',        # agrees with the UA
        "Sec-Fetch-Dest": "document",
        "Sec-Fetch-Mode": "navigate",
        "Sec-Fetch-Site": "none",
        "Upgrade-Insecure-Requests": "1",
    }
    return requests.get(url, headers=headers,
                        proxies={"http": proxy, "https": proxy}, timeout=20)

同じ規律は、ブラウザを操作している場合のタイムゾーンやロケールにも当てはまる。これらは別個に観測可能であり、出口と一致させておくべきだ。これは都市レベルのターゲティングで作業する場合には、さらに厳密さが求められる。

User-Agentをランダムにローテーションしない

これは広く流布しているアドバイスだが、益より害が大きい。リクエストごとにランダムなUAを選ぶと、実際のどの母集団にも存在しないパターンが生まれる。あるIPが1分の間にWindows上のChromeから、MacのSafariへ、LinuxのFirefoxへと変わる、というように。さらに悪いのは、スティッキーセッションを保持して1つのアドレスが複数ステップのフローを処理している場合、フローの途中でUAを変えると、同じ訪問者が検索をクリックした瞬間から結果を見るまでの間にデバイスを乗り換えたように見えてしまうことだ。

整合性のあるモデルは、セッションごとに1つのアイデンティティを持つことだ。あり得るUAを選び、そのセッションの間はそれを維持し、他のすべてをそれと整合させておく。フリート全体で多様性を持たせたいなら、リクエスト間ではなくセッション間で変化させ、実在するすべてのブラウザに均等にではなく、現実的な比率で変化させる。バージョンも最新に保つこと。3年前のブラウザバージョンを主張するUAは、実際のインストールは更新されるものなので、それ自体が異常なのだ。

ヘッダーは一つの層にすぎない

ここでの限界について正直に述べておく価値がある。完璧なヘッダーでも、PythonクライアントをChromeに見せることはできない。その下にある層が依然として異なっているからだ。クライアントが生成するTLSハンドシェイクとHTTP/2の設定はそれ自体のフィンガープリントを形成し、ChromeのUAがPythonだと語るフィンガープリントの上に載っているというのは、まさに冒頭で論じた矛盾そのものだ。この不一致はTLSとHTTP/2フィンガープリンティングで扱われている主題であり、これがヘッダーをいくら工夫しても、防御の厳重な一部のターゲットが単純なHTTPクライアントの手に届かない理由の一つであり、ヘッドレスブラウザに手を伸ばす理由の一つでもある。

ヘッダーの順序も同じ理由で重要だ。ブラウザは安定した順序でヘッダーを送出するが、多くのHTTPライブラリはアルファベット順や挿入順で送出する。これは、すべての値が正しくてもバンドルが出自を裏切ってしまう別の経路だ。クライアントによっては順序を制御できるものがあり、重要な場面では本物のブラウザの順序に合わせる努力に価値がある。これらの信号のより広範なカタログはデータ抽出をブロックしかねないフィンガープリントにあり、よく見られる自業自得のバージョンは検知を引き起こす間違いにある。

簡単なチェックリスト

User-Agentだけでなく、完全なブラウザのヘッダー集合を送ること。クライアントヒントをブランド、バージョン、プラットフォーム、モバイルフラグの点でUAと一致させること。Sec-Fetchヘッダーを実際のリクエストの種類に応じて設定すること。Accept-Languageを出口の国に結びつけ、同じコードパスで保つこと。実際に復号できるエンコーディングのみを主張すること。リクエストごとにローテーションするのではなくセッションごとに1つのアイデンティティを保持し、UAのバージョンを最新に保つこと。そして、TLSフィンガープリントが主張しているブラウザと一致しているかを確認すること。それはヘッダーでは直せない層だからだ。

それでもなおターゲットが拒否してくる場合、問題は別の場所に移っている。ペース、IPのレピュテーション、あるいは行動的な信号であり、これらはより広い範囲としてブロックを避ける厳重に保護されたサイトのスクレイピングで扱われている。

結論

ヘッダーは、プロキシが始めたアイデンティティの後半部分だ。クリーンなレジデンシャルアドレスは接続を目立たなくし、整合性のあるヘッダーバンドルはリクエストを目立たなくする。そして整合性こそがすべてを決める。すべての主張が他のすべての主張と一致していなければならない。クライアントヒントはUAと、言語は出口の国と、フェッチのメタデータはリクエストの種類と、エンコーディングは実際の能力と、そしてその一連すべてが下にあるTLSフィンガープリントと一致する必要がある。セッションごとに1つのアイデンティティを保ち続けることは、どんな巧妙なローテーションよりも優れている。

その接続層を提供するのがレジデンシャルプロキシであり、国と都市レベルのターゲティングを備えた本物の家庭用グレードのアドレスによって、ヘッダーが主張する地理情報が実際に出て行っている地理情報と一致するようになる。GBあたりで課金されるため、取得量を減らすようにリクエストを調整すればコストも下がる。

始める準備はできていますか?

Shifterのレジデンシャルプロキシをお試しください。IP 205M+件、195+カ国、$0.75/GBから。

始める