ドイツの出口を設定し、アドレスは正しくジオロケーションされるのに、対象はそのセッションを不審なものとして扱ったり、別の場所向けのコンテンツを返してくる。IPは正しかった。それ以外のすべてが、依然として別の国にいる訪問者を示していたのだ。
位置情報は単一の信号ではない。それは複数の信号の集合であり、本物の訪問者はそれらすべてを同じ場所から発する。なぜなら、その人は一つの国にある一つのマシンから来ているからだ。スクレイパーはそれらを異なるソースから組み立てる。国はプロキシのパラメータから、タイムゾーンはサーバーの時計から、ロケールはライブラリのデフォルトから、言語ヘッダーはハードコードされた何かから、といった具合だ。これらが一致しないとき、その矛盾は単一の値以上に検出しやすく、さらに収集するデータそのものを変えてしまうこともある。
一致していなければならない信号
サイトがあなたの位置を知る手がかりは6つあり、ほとんどのスクレイパーはそのうち1つか2つしか制御していないため、明示的に列挙する価値がある。
IPアドレスは主要な信号であり、プロキシが設定するものだ。Accept-Languageは言語の好みを表すHTTPヘッダーで、多くのサイトはこれを直接使ってコンテンツを出し分ける。タイムゾーンはブラウザ上でJavaScriptのdate APIを通じて、より正確にはIntl APIが解決するタイムゾーンを通じて観測可能だ。ロケールはnavigator.languageと、Intl APIが解決する書式規則をカバーし、これらが日付や数値、通貨の表示形式を決定する。通貨と単位は、サイトがクライアント側にそれらを示唆させる場合に関係する。そしてDNS解決は無関係に聞こえるが実はそうではない。クライアントがローカルでホスト名を解決しながら遠隔で出口を出る場合、その解決処理はあなたの実際の場所から行われ、地域的に誤ったエンドポイントを返してしまうことがある。これがDNSリークの問題だ。
単純なHTTPの作業では、最初の2つとDNSしか見えない。そのためヘッダーに関する記事では、Accept-Languageを主要な組み合わせとして扱っている。ブラウザ自動化では6つすべてが観測可能であり、そこで不一致が現れるのが通常だ。
すべてを単一の真実の源から導出する
修正はチェックリストというより構造的なものだ。国がある場所で選ばれ、タイムゾーンが別の場所で選ばれていれば、誰かが新しい市場を追加した瞬間にそれらはずれていく。各市場を、それが含意するすべての信号とともに一度だけ定義し、セッション全体をそのレコードから導出する。
MARKETS = {
"de": {"lang": "de-DE,de;q=0.9,en;q=0.8", "locale": "de-DE",
"tz": "Europe/Berlin", "currency": "EUR"},
"us": {"lang": "en-US,en;q=0.9", "locale": "en-US",
"tz": "America/New_York", "currency": "USD"},
"jp": {"lang": "ja-JP,ja;q=0.9,en;q=0.8", "locale": "ja-JP",
"tz": "Asia/Tokyo", "currency": "JPY"},
"br": {"lang": "pt-BR,pt;q=0.9,en;q=0.8", "locale": "pt-BR",
"tz": "America/Sao_Paulo", "currency": "BRL"},
}
def proxy_for(country, session=None):
user = f"customer-USERNAME-country-{country}"
if session:
user += f"-sid-{session}-ttl-600"
url = f"http://{user}:PASSWORD@p.shifter.io:443"
return {"http": url, "https": url}
これにより、市場は単一の引数となり、国とタイムゾーンが一致しなくなるようなコードパスは存在しなくなる。誰もそれらを別々に設定していないからだ。
ブラウザセッションへの適用
ブラウザ自動化フレームワークはタイムゾーンとロケールをコンテキストのオプションとして公開しており、これは設定すべき正しい場所だ。これらはページ上のどのスクリプトが実行される前にも適用されるため、ページは実マシンの実際の値を観測できない。
# Playwright: プロキシ、タイムゾーン、ロケールはすべて単一の市場レコードから導出される
m = MARKETS[country]
context = browser.new_context(
proxy={"server": "http://p.shifter.io:443",
"username": f"customer-USERNAME-country-{country}-sid-{sid}",
"password": "PASSWORD"},
locale=m["locale"], # navigator.languageとIntlの書式
timezone_id=m["tz"], # Intlが解決するタイムゾーンとDateのオフセット
extra_http_headers={"Accept-Language": m["lang"]},
)
これらを後からプロパティをパッチするのではなくコンテキストレベルで設定することが重要だ。というのも、Intl.DateTimeFormatをパッチしてもそれがDateのオフセットと一致しなければ、それ自体が検出可能な矛盾になり、検出スクリプトはこの2つを日常的に相互チェックしているからだ。
どの程度の精度が必要か
タイムゾーンとロケールは基本的に国単位のものなので、ほとんどの作業では国レベルの粒度で十分だ。より注意が必要なのは3つのケースだ。
複数のタイムゾーンを持つ国では、国のデフォルト値が一部の人口には当てはまらない。米国の出口はいくつかのゾーンのどれでも妥当だ。そのため、都市レベルのターゲティングで作業している場合は、国からではなく都市からタイムゾーンを導出すべきだ。そうでない場合は、ユーザーの最大割合に一致するゾーンを選び、ランダム化せずそれを一貫して使い続けるべきだ。
複数の公用語を持つ国では、意図的な選択が必要だ。スイスやカナダの出口は複数のロケールとして妥当であり、正しい答えは通常、収集しているコンテンツに一致するものであり、それを一定に保つことだ。
地域的な書式の違いはより微妙であり、追いかける価値はほとんどないが、ロケールを提示するのであれば、日付や数値のフォーマットを手作業で組み立てるのではなく、Intl APIにフォーマットを任せるべきだ。それが実際にそのロケールで生成されるものと一致しない可能性があるからだ。
セッションの生涯にわたる一貫性
セッションは一つの物語であり、その物語は途中で変わってはならない。スティッキーセッションが複数ステップのフローに対して一つのアドレスを保持する場合、そのフロー内のすべてのリクエストは同じ言語、タイムゾーン、ロケールを持たなければならない。フローの途中でそのいずれかを変更することは、検索をクリックしてから結果を見るまでの間に国を移動した訪問者を示すことになり、それは静的な不一致よりも強い異常となる。
ここでも、単一の市場レコードから導出することが再び効果を発揮する。セッション識別子とロケールのまとまりが同じ場所から来て、同じ期間だけ持続する。これらを明示的に結びつけ、セッションを解放すればアイデンティティ全体が解放され、新しいセッションは内部的に一貫した新しいものとして始まるようにする。同じ論理が、アンチデテクトブラウザにおいてデバイスフィンガープリントとネットワークアイデンティティを組み合わせる仕組みを支配している。
正しく設定できたかを検証する
何も仮定せず、2つのレベルで確認する。
まず、ブラウザが自分自身について何を報告するかだ。解決されたタイムゾーン、navigator.language、フォーマット済みの日付を読み取るページを実行し、それらが意図した市場と一致することを確認する。これは設定ミスを即座に検出できる。
JSON.stringify({
tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
lang: navigator.language,
langs: navigator.languages,
offset: new Date().getTimezoneOffset(),
})
次に、より重要なのは、対象が実際に何をするかだ。地域に敏感なページを取得し、通貨、言語、地域向けコンテンツが現地の訪問者が見るものと一致しているかを確認する。ジオロケーションデータベースと対象の判断が常に一致するわけではないため、サイト自身の振る舞いこそが本当の判定基準となる。それが位置精度のテストの目的だ。アドレスは正しくジオロケーションされているのにコンテンツが間違っている場合は、まずDNS解決を疑うべきだ。
結論
位置情報は信号の集合であり、サイトはそれらをまとめて読み取る。プロキシに国を設定しながら、タイムゾーン、ロケール、言語をサーバーのデフォルトのままにしておくと、存在し得ない訪問者が生成される。これは検出の信号であると同時に、静かに誤ったデータの原因にもなる。各市場を、それが含意するすべての信号とともに一度だけ定義し、プロキシパラメータとブラウザコンテキストをその一つのレコードから導出することでずれを防ぎ、タイムゾーンとロケールは後からパッチするのではなくコンテキストレベルで設定し、セッションの生涯にわたってそのまとまり全体を一定に保ち、両方のレベルで検証する。つまりブラウザが報告するものと、対象が実際に提供するものだ。そうすれば、あなたのトラフィックが位置について語ることは、あなたが選んだ一つのことだけになる。
その地理情報そのものはレジデンシャルプロキシから来ている。国および都市ターゲティングに対応した本物の家庭グレードのアドレスであり、あなたのセッションが主張する位置は、実際にそこから出口を出ている位置となる。さらにGB単位の価格設定により、多くの市場で同じジョブを実行するのに適している。