Webスクレイピングにおける403 Forbidden: 実際に機能する修正ステップ

403は権限の問題ではなく、検出の問題です。ブロックされたスクリプトと200の間には4つのステップがあり、多くの人は最初のステップで止まります。

Webスクレイピングでの403 forbiddenエラーは、ステータスコードが示す意味とはほとんど関係がありません。HTTP 403は、サーバーがリクエストを理解し、それを承認しないことを定義していますが、スクレイパーに当たると、それは権限やログインの欠如とはほとんど関係がありません。それは、サイトがあなたのリクエストを見て、それが機械から送られたと判断し、ドアを閉めたことを意味します。変更すべきは資格情報ではなく、トラフィックの形状です。以下は、最も安価なステップから始まる修正ステップで、どのステップに引っかかっているかを特定するチェックです。

403 vs 401 vs 429: それぞれが何を伝えているか

コードを書く前に正しい診断を行いましょう。401 Unauthorizedは資格情報を求めます—それを提供することで解決します。403 Forbiddenは資格情報に関係なく拒否するため、ボット検出がトリガーされた場合、ログインしても何も変わりません。429 Too Many Requestsはボリュームに関するもので、ウィンドウがリセットされるとクリアされます。403はアイデンティティに関するもので、リクエストの見た目を変えるまで持続します。スクレイパーが403ではなく429を取得している場合、解決策はペース配分であり、偽装ではありません—それについては429 too many requestsの修正で取り上げています。

コードを変更する前に60秒で診断する

3つのコマンドでほぼすべてを把握できます。素のリクエストを実行し、ブラウザのUser-Agentを入れ替えて再実行し、レスポンスボディを読む—ブロックの理由は通常そこに書かれています。

# 1. Bare request: what does the target give a naked client?
curl -sS -o /dev/null -w '%{http_code}\n' https://target.example/page

# 2. Same request, browser User-Agent only
curl -sS -o /dev/null -w '%{http_code}\n' \
  -A 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' \
  https://target.example/page

# 3. Read the body and the response headers - the reason is in there
curl -sS -D - https://target.example/page | head -c 600

次のように解釈します。ステップ2が200を返す場合、問題はすべてヘッダーにあり、ステップ1で終了です。ボディにCloudflare、Ray ID、またはError 1020が記載されている場合、WAFルールの背後にいます—Cloudflare error 1020を参照してください。シンプルなApacheまたはnginxのForbiddenページは通常、mod_securityのようなサーバーモジュールを意味し、これは現代のボット管理が存在するずっと前から既知のボットUser-Agentをブロックしています。同じマシンのブラウザがページをロードするのにクライアントができない場合、curl 403 but the browser worksを参照してください。

Webスクレイピングのための403 forbidden修正ステップ: ヘッダー、TLSフィンガープリント、IPタイプ、レンダリングの4つのエスカレーティングステップ
1つのステップを登り、再テストし、200を取得したらすぐに停止します。レンダリングは最もコストがかかり、最も修正が少ないです。

ステップ1: ヘッダーで自己紹介をやめる

Pythonのurllibpython-urllib/3.3.0のように自己紹介します;requestspython-requests/2.xを送信します。これらの文字列は告白であり、Pythonスクレイピングにおける403に関する公式Stack Overflowスレッドで最も投票された回答は単に: ブラウザのUser-Agentを送信することです。それは今でも多くのサイトで機能します。しかし、その回答が書かれた時から2つのことが変わりました。まず、素のMozilla/5.0が今やそれ自体がフラグです—同じスレッドのコメント者は、2トークンのUAを送信する実際のブラウザはないため、それを直接ブロックするサイトを報告しています。次に、現代のサーバーはヘッダーセット全体を比較し、1つのフィールドではありません。

一貫したセットを送信します: 現在のブラウザUA、対応するAcceptチェーン、言語、およびChromiumがすべてのナビゲーションに追加するSec-Fetch-*メタデータヘッダー。ヘッダーの順序も厳しいターゲットでは重要です—順序付けされたマッピングを使用し、ブラウザが使用する順序でそれらを配置します。

import requests

HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
        "(KHTML, like Gecko) Chrome/131.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-GB,en;q=0.9",
    "Accept-Encoding": "gzip, deflate",   # add 'br' only if brotli is installed
    "Upgrade-Insecure-Requests": "1",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-User": "?1",
    "Connection": "keep-alive",
}

with requests.Session() as s:
    s.headers.update(HEADERS)
    r = s.get("https://target.example/page", timeout=20)
    print(r.status_code, len(r.content))

ほとんどの人が引っかかる一貫性の罠: 米国のChromeデスクトップを主張するUAとAccept-Language: de-DE、ブラジルの出口IPを組み合わせると、まともなシステムが気づく不一致です。ユーザーエージェント、言語、IPの地理を同じストーリーに保ちます。

このステップでのいくつかの403はさらに単純です: Refererの欠如です。リクエストが自分のページから来たように見える場合にのみアセットを提供するサーバーは、直接ヒットに403を返し、参照URLを追加した瞬間に200を返します。それは古典的であり、テストに1つのヘッダーがかかります。

ステップ2: TLSフィンガープリント ヘッダーでは修正できない

完璧なヘッダーがまだ403を返す場合、ブロックはあなたのヘッダーが読み取られる前に発生しました。すべてのHTTPSクライアントは、TLS ClientHelloで暗号スイート、拡張、楕円曲線、ALPNを発表し、その組み合わせはJA3またはJA4フィンガープリントにハッシュされます。Pythonのrequests、Goのnet/http、標準のcurlはそれぞれ特徴的なもので、Chromeと区別するのに問題があるアンチボットベンダーはいません。ヘッダーでChrome 131を主張しながら、OpenSSLのようにハンドシェイクするのは、スクレイパーができる最も大きな矛盾です。

修正は、TLSレイヤーで実際のブラウザを模倣するクライアントです。Pythonでは、それはcurl_cffiであり、ブラウザのClientHellosを再現するパッチを当てたlibcurlへのバインディングです:

# pip install curl_cffi
from curl_cffi import requests as cffi

proxy = "http://USER:PASS@gate.quantumproxies.io:8000"

r = cffi.get(
    "https://target.example/page",
    impersonate="chrome",              # Chrome JA3/JA4 + HTTP/2 settings
    proxies={"http": proxy, "https": proxy},
    timeout=20,
)
print(r.status_code)

Nodeには同じパッチを当てたTLSスタックに基づく同等のものがあります。この単一の変更が保護されたサイトで403を200に変える理由の完全なメカニズムを知りたい場合は、JA3/JA4フィンガープリントがあなたのスクレイパーを暴露する方法を読んでください。

ステップ3: IPがメッセージ

ヘッダーとTLSはクライアントを説明します。IPは誰が尋ねているかを説明し、それは重く評価されます。ホスティングやクラウドの範囲内のアドレスは公開されており、ASNによって簡単にマッピングでき、リクエストの最初のバイトが検査される前に低い信頼スコアを持っています—これがVPS上のスクレイパーがホーム接続で同じコードを実行すると403を受け取る理由です。住宅用アドレスは消費者インターネットプロバイダーに属し、人として扱われます。モバイルキャリアのIPは、各々何千もの実際の加入者を持つCGNATの背後にあります。これが、全体をブロックするのが最も難しい理由です。

したがって、ステップ3は書き換えではなく交換です: 住宅用出口から同じ整形されたリクエストを送信します。QuantumProxiesは、200以上の国に90M以上の住宅用IPを運営し、リクエストごとのローテーションまたはスティッキーセッション、すべてのプランでHTTPおよびSOCKS5、GB単位の請求を行っています—1つの設定行でターゲットが見るASNを変更します。すでに住宅用でブロックされている場合は、無料のIP品質スコアチェッカーでプールの評判を確認してください: 75以上のスコアを持つものは焼かれており、ヘッダーがどれだけ良くても403を集めます。

ステップを飛ばす: Scraper APIで任意のページを取得する

403 forbiddenエラーを引き起こすスクレイパーリクエスト信号を実際のブラウザが送信する信号と比較するチェックリスト
アンチボットシステムは一貫性をテストします。このリストの1つの不一致信号が403の原因となります。

ステップ4: レンダリングとチャレンジ

最後のステップは高価です。いくつかの403はJavaScriptチャレンジの見える半分です: サーバーは小さなスクリプトを送信し、数秒以内に答えを期待し、それを実行できないものをすべて拒否します。ヘッダーセットもIPもそれを修正できません。なぜなら、テストはコードを実行できるかどうかだからです。コストの上昇順に: ステルスパッチセットを備えたヘッドレスブラウザ、管理されたブラウザプール、またはオンデマンドでレンダリングするスクレイピングAPI。レンダリングは、プレーンなHTTPリクエストよりもページごとに何倍もコストがかかるため、本当に必要なURLのみにエスカレートします。

QuantumProxiesのScraper APIは、ステップ2から4を1つのリクエストにまとめます: ブラウザグレードのTLS、住宅用出口、ページが必要な場合のJavaScriptレンダリング、そしてmarkdown、JSONまたは生のHTMLを返します。それが正直な取引です—梯子を維持するのをやめ、代わりに成功したページごとに支払います。

実際に梯子を使う

禁止防止は兄弟の技術です: 200を取得したら、それを維持するのはペース配分、セッション衛生、プールの健康の問題であり、偽装ではありません。

よくある質問

Webスクレイピングで403 forbiddenエラーの原因は何ですか?

ほとんどの場合、検出です。通常のトリガーはデフォルトのライブラリUser-Agent、不完全または矛盾したヘッダーセット、評判の悪いデータセンターIP、あまりにも規則的なリクエストペース配分、または主張するブラウザと一致しないTLSフィンガープリントです。本物の権限エラーも存在しますが、それはブラウザにも403を返します—仮定する前に1つでテストしてください。

Pythonで403 forbiddenエラーをバイパスするにはどうすればよいですか?

梯子を使います。requests.Sessionに完全なブラウザヘッダーセットを追加します。それが失敗した場合は、curl_cffiのようなブラウザTLSを模倣するクライアントに切り替えます。それが失敗した場合は、住宅用プロキシを経由します。ページがJavaScriptチャレンジを送信する場合は、それをレンダリングするか、スクレイピングAPIを使用します。安価なステップが実際にテストされたときにのみエスカレートします。

なぜhttpxはブラウザがしないときに403を返すのですか?

requestsと同じ理由です: httpxは最小限のヘッダーセットとPython TLSフィンガープリントを送信します。DevToolsからブラウザの正確なリクエストをコピーし、それをhttpxで再生すると、通常403は消えます—それがヘッダーの違いを示しています。それが同一のヘッダーで持続する場合、ブロックはTLSまたはIPレイヤーにあります。

Scrapyで403 forbiddenを修正するにはどうすればよいですか?

現実的なDEFAULT_REQUEST_HEADERSUSER_AGENTを設定し、ROBOTSTXT_OBEYが取得を許可されていることを正直に保ち、AUTOTHROTTLE_ENABLEDを有効にし、リクエストを回転プロキシミドルウェアを通じてルーティングします。Scrapyのリトライはデフォルトで403をリトライしません—試行間でIPを回転させる場合にのみRETRY_HTTP_CODESに追加します。

403は情報であり、壁ではありません。それはどの4つの信号があなたを暴露したかを教えてくれ、梯子の各ステップはその下のものよりもコストがかかります。最も安価なものから始め、変更ごとにテストし、ステータスコードが200に変わった瞬間に登るのをやめます。

ステップ3をクリアする住宅用プロキシを取得する