プロキシがブラウザでは動作するのにコードでは動作しない理由:完全な問題分析
典型的な状況:ブラウザでプロキシを設定し、サイトを開くとすべて正常に動作する。しかし、同じプロキシを使ってスクリプトを実行すると接続エラーやタイムアウト、あるいはブロックが発生する。なぜこのようなことが起こるのか、そしてどう修正すればよいのかを解説します。
ブラウザからのリクエストとコードからのリクエストの違い
プロキシ経由でブラウザでサイトを開く場合、単なるHTTPリクエスト以上の多くの処理が行われています。ブラウザは自動的に以下を実行します。
- 完全なヘッダーセット(User-Agent、Accept、Accept-Language、Accept-Encodingなど)を送信する
- 適切な暗号スイートでTLSハンドシェイクを実行する
- リダイレクトとCookieを処理する
- JavaScriptを実行し、依存リソースをロードする
- DNS応答と証明書をキャッシュする
コードからの最小限のリクエストは、サーバーから見ると人間ではなくロボットのように見えます。プロキシ自体が正しく機能していても、ターゲットサイトがあなたのスクリプトをブロックしている可能性があります。
プロキシ認証の問題
最も一般的な原因は、ログイン情報(クレデンシャル)の渡し方が不正確であることです。ブラウザは資格情報の入力を促すポップアップを表示しますが、コードではこれを明示的に行う必要があります。
URL形式の誤り
スキーム(プロトコル)の欠落や特殊文字の不適切なエスケープ処理がよくある間違いです。
# 間違い
proxy = "user:pass@proxy.example.com:8080"
# 正しい
proxy = "http://user:pass@proxy.example.com:8080"
# パスワードに特殊文字 (@, :, /) が含まれる場合
from urllib.parse import quote
password = quote("p@ss:word/123", safe="")
proxy = f"http://user:{password}@proxy.example.com:8080"
IPベース認証 vs ログイン/パスワード認証
一部のプロキシプロバイダーはIPアドレスによるホワイトリスト認証を使用しています。あなたのPC上のブラウザが動作するのは、あなたのIPがホワイトリストに登録されているためです。しかし、サーバー上のスクリプトが動作しないのは、サーバーのIPが異なるためです。
プロバイダーの管理画面で、どの認証方法が使用されているか、またどのIPがホワイトリストに追加されているかを確認してください。
HTTP/HTTPS/SOCKSプロトコルの不一致
ブラウザはプロキシのタイプを自動的に判別することが多いですが、コードでは明示的に指定する必要があり、プロトコルの誤りはサイレントな拒否につながります。
| プロキシタイプ | URLスキーム | 特徴 |
|---|---|---|
| HTTPプロキシ | http:// |
CONNECT経由でHTTPおよびHTTPSに対応 |
| HTTPSプロキシ | https:// |
プロキシとの接続が暗号化される |
| SOCKS4 | socks4:// |
認証なし、IPv4のみ |
| SOCKS5 | socks5:// |
認証あり、UDP、IPv6に対応 |
| SOCKS5h | socks5h:// |
プロキシ経由でDNS名前解決を行う |
重要:SOCKS5プロキシを使用しているのに、コードでhttp://を指定すると、ライブラリはSOCKSサーバーに対してHTTPプロトコルで通信しようとし、接続が確立されません。
ヘッダーの欠落とフィンガープリント
プロキシが正しく機能していても、ターゲットサイトが不審なヘッダーのためにリクエストをブロックすることがあります。比較してみましょう。
ブラウザからのリクエスト
GET /api/data HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
デフォルトのrequestsからのリクエスト
GET /api/data HTTP/1.1
Host: example.com
User-Agent: python-requests/2.28.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
違いは明らかです。アンチボット対策を導入しているサイトは、このヘッダーの違いからリクエストがブラウザから来ていないことを即座に判断します。
擬態のための最小限のヘッダーセット
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Cache-Control": "max-age=0"
}
SSL証明書と検証
ブラウザにはルート証明書の組み込みストアがあり、さまざまなSSL構成を処理できます。コードでは、次のような問題が発生する可能性があります。
SSLエラー: CERTIFICATE_VERIFY_FAILED
一部のプロキシは、トラフィックの検査のために独自の証明書を使用します。ブラウザはこの証明書を信頼済みとして持っているかもしれませんが、あなたのスクリプトは持っていない可能性があります。
# デバッグのための暫定的な解決策(本番環境では非推奨!)
import requests
response = requests.get(url, proxies=proxies, verify=False)
# 正しい解決策 — 証明書へのパスを指定する
response = requests.get(url, proxies=proxies, verify="/path/to/proxy-ca.crt")
重要: SSL検証を無効にすること(
verify=False)は、中間者攻撃に対して接続を脆弱にします。安全な環境でのデバッグ目的でのみ使用してください。
TLSフィンガープリント
高度なアンチボットシステムは、接続確立時の暗号の順序とセットであるTLSフィンガープリントを分析します。Pythonのrequestsは標準的なセットを使用しますが、これはブラウザのものとは異なります。
回避策として、カスタムTLSフィンガープリントを持つライブラリを使用します。
# インストール: pip install curl-cffi
from curl_cffi import requests
response = requests.get(
url,
proxies={"https": proxy},
impersonate="chrome120" # Chrome 120のTLSフィンガープリントを模倣
)
DNSリークと名前解決
もう一つの見落とされがちな問題はDNS解決です。HTTPプロキシを使用する場合、DNSクエリがプロキシを経由せず、あなたのマシンから直接送信されることがあります。
これが動作に与える影響
- サイトがプロキシではなく、実際のDNSリゾルバを検出する
- 地理的位置情報が正しく決定されない
- 一部のサイトは、IPとDNSの地域が一致しない場合にブロックする
SOCKS5の解決策
socks5://の代わりにスキームsocks5h://を使用します。末尾の「h」は、DNS名前解決がプロキシ側で実行されることを意味します。
# DNSがローカルで解決される(リークの可能性あり!)
proxy = "socks5://user:pass@proxy.example.com:1080"
# DNSがプロキシ経由で解決される(正しい)
proxy = "socks5h://user:pass@proxy.example.com:1080"
Python、Node.js、cURLの動作例
Python (requests使用)
import requests
from urllib.parse import quote
# プロキシ情報
proxy_host = "proxy.example.com"
proxy_port = "8080"
proxy_user = "username"
proxy_pass = quote("p@ssword!", safe="") # 特殊文字をエスケープ
# プロキシURLを構築
proxy_url = f"http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}"
proxies = {
"http": proxy_url,
"https": proxy_url
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
}
try:
response = requests.get(
"https://httpbin.org/ip",
proxies=proxies,
headers=headers,
timeout=30
)
print(f"Status: {response.status_code}")
print(f"IP: {response.json()}")
except requests.exceptions.ProxyError as e:
print(f"プロキシエラー: {e}")
except requests.exceptions.ConnectTimeout:
print("プロキシへの接続タイムアウト")
Python (aiohttp使用、非同期)
import aiohttp
import asyncio
async def fetch_with_proxy():
proxy_url = "http://user:pass@proxy.example.com:8080"
async with aiohttp.ClientSession() as session:
async with session.get(
"https://httpbin.org/ip",
proxy=proxy_url,
headers={"User-Agent": "Mozilla/5.0..."}
) as response:
return await response.json()
result = asyncio.run(fetch_with_proxy())
print(result)
Node.js (axios使用)
const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');
const proxyUrl = 'http://user:pass@proxy.example.com:8080';
const agent = new HttpsProxyAgent(proxyUrl);
axios.get('https://httpbin.org/ip', {
httpsAgent: agent,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...'
}
})
.then(response => console.log(response.data))
.catch(error => console.error('Error:', error.message));
Node.js (node-fetchとSOCKS)
const fetch = require('node-fetch');
const { SocksProxyAgent } = require('socks-proxy-agent');
const agent = new SocksProxyAgent('socks5://user:pass@proxy.example.com:1080');
fetch('https://httpbin.org/ip', { agent })
.then(res => res.json())
.then(data => console.log(data));
cURL
# HTTPプロキシ
curl -x "http://user:pass@proxy.example.com:8080" \
-H "User-Agent: Mozilla/5.0..." \
https://httpbin.org/ip
# SOCKS5プロキシ(プロキシ経由でDNS解決)
curl --socks5-hostname "proxy.example.com:1080" \
--proxy-user "user:pass" \
https://httpbin.org/ip
# デバッグ — 接続プロセス全体を表示
curl -v -x "http://user:pass@proxy.example.com:8080" \
https://httpbin.org/ip
診断チェックリスト
コードでプロキシが動作しない場合は、以下の順序で確認してください。
- プロキシURLの形式 — スキーム(http://, socks5://)は付いていますか?
- パスワードの特殊文字 — URLエンコーディングされていますか?
- プロキシのタイプ — 指定したプロトコルは実際のプロキシと一致していますか?
- 認証 — IPベースですか、それともログイン/パスワードですか?サーバーのIPはホワイトリストに登録されていますか?
- ヘッダー — User-Agentやその他のブラウザヘッダーを追加しましたか?
- SSL — 証明書エラーは発生していませんか?
- DNS — プロキシ経由で解決するためにsocks5h://を使用していますか?
- タイムアウト — 接続確立のために十分な時間を設定していますか(特にレジデンシャルプロキシの場合)?
結論
ブラウザとコードの差は、ヘッダー、プロトコル、SSL、DNSといった細部にあります。ブラウザはこれらの複雑さを隠蔽しますが、コードでは各側面を明示的に設定する必要があります。まずはURL形式と認証の確認から始め、次にブラウザヘッダーを追加すれば、問題の90%は解決するはずです。
スクレイピングや自動化のタスクで、安定性とブロック率の低さが重要となる場合、レジデンシャルプロキシが適しています。詳細についてはproxycove.comをご覧ください。