Pythonスクリプトが403エラー、CAPTCHA、またはIPによる禁止を受けた場合、ターゲットサイトがあなたに気づいたことを意味します。requestsライブラリにプロキシを接続することでこの問題を解決できます: IPアドレスを変更し、地理的制限を回避し、複数のアドレス間で負荷を分散します。このガイドでは、基本的な接続から高度なローテーションまで、実際のコード例を交えて説明します。
Pythonスクリプトにおけるプロキシの必要性
ほとんどのウェブサイトやAPIは、受信リクエストのIPアドレスを追跡しています。1つのアドレスが1分間に100件以上のリクエストを行うと、そのアドレスはブロックされます。これは、Wildberries、Ozon、Avito、Google、Instagramなど、数百のプラットフォームが使用しているボット対策の標準的な方法です。プロキシを使用することで、異なるIPアドレスを持つ中間サーバーを介してリクエストを送信し、保護システムから見えなくなります。
Pythonでプロキシが必要な主なタスクは以下の通りです:
- マーケットプレイスのスクレイピング — Wildberries、Ozon、Yandex.Marketからの価格収集をIPブロックなしで行う
- 競合の監視 — 競合のウェブサイトへの定期的なリクエストを5〜15分ごとに行う
- 制限のあるAPIとの作業 — 複数のIP間でリクエストを分散し、レート制限を超えないようにする
- ジオロケーションテスト — 異なる国や地域からウェブサイトがどのように見えるかを確認する
- フォームと登録の自動化 — 異なるIPからアカウントを作成したり、フォームを記入する
- SEO監視 — ロシアや他国の異なる地域からのランキングを取得する
プロキシなしでは、どんなに優れたパーサーでも数時間でブロックされてしまいます。適切に設定されたIPローテーションを使用すれば、同じスクリプトが数週間にわたって停止することなく動作します。
requestsにおけるプロキシの基本設定
requestsライブラリは、プロキシをネイティブにサポートしています — 追加のパッケージは必要ありません。プロキシは、リクエストのパラメータにあるproxies辞書を介して渡されます。
最も簡単な例は、1つのリクエストのためのHTTPプロキシです:
import requests
proxies = {
"http": "http://123.45.67.89:8080",
"https": "http://123.45.67.89:8080",
}
response = requests.get("https://httpbin.org/ip", proxies=proxies)
print(response.json())
# {'origin': '123.45.67.89'}
注意: proxies辞書には、両方のキー — httpとhttpsを指定する必要があります。どちらか一方だけを指定すると、もう一方のプロトコルのリクエストはプロキシを介さずに直接行われます。これは初心者によくある間違いで、実際のIPが漏れてしまいます。
プロキシが機能していることを確認するには、httpbin.org/ipサービスを使用してください — これはリクエストが送信されたIPアドレスを返します。レスポンスにプロキシサーバーのIPが表示され、自分のIPが表示されない場合は、すべてが正しく設定されています。
HTTP、HTTPS、SOCKS5プロキシ: 違いとコード例
プロキシにはさまざまなタイプがあり、それぞれのタスクに適しています。Python requestsのコンテキストでは、3つの主要なプロトコルの違いを理解することが重要です:
| タイプ | URLのプロトコル | 速度 | UDPサポート | 最適なシナリオ |
|---|---|---|---|---|
| HTTP | http:// |
高速 | なし | HTTPサイトのスクレイピング |
| HTTPS | https:// |
高速 | なし | 保護されたサイトのスクレイピング |
| SOCKS5 | socks5:// |
中程度 | あり | 完全な匿名性、任意のプロトコル |
SOCKS5をPythonで使用するには、追加のパッケージをインストールする必要があります:
pip install requests[socks] # または別々に: pip install PySocks
インストール後、SOCKS5プロキシの接続は次のようになります:
import requests
# SOCKS5プロキシ
proxies = {
"http": "socks5://123.45.67.89:1080",
"https": "socks5://123.45.67.89:1080",
}
response = requests.get("https://httpbin.org/ip", proxies=proxies)
print(response.json())
SOCKS5は、匿名性が重要なタスクに推奨されるプロトコルです。HTTPプロキシとは異なり、SOCKS5はX-Forwarded-Forヘッダーを追加せず、実際のIPを開示することはありません。
ユーザー名とパスワードによる認証プロキシ
ほとんどの有料プロキシサービスは、ユーザー名とパスワードによる認証を使用しています。これは標準的な手法であり、認証がないとプロキシはリクエストを通過させません。requestsライブラリでは、認証情報はプロキシのURLに直接渡されます。
import requests # フォーマット: プロトコル://ユーザー名:パスワード@ホスト:ポート proxy_url = "http://myuser:[email protected]:8080" proxies = { "http": proxy_url, "https": proxy_url, } response = requests.get("https://httpbin.org/ip", proxies=proxies) print(response.status_code) print(response.json())
パスワードやユーザー名に特殊文字(例: @, #, %)が含まれている場合は、URLエンコードする必要があります。そのためには、urllib.parseモジュールを使用します:
import requests
from urllib.parse import quote
username = "myuser"
password = "p@ss#word!" # 特殊文字
# ユーザー名とパスワードをURLエンコード
encoded_user = quote(username, safe="")
encoded_pass = quote(password, safe="")
proxy_url = f"http://{encoded_user}:{encoded_pass}@123.45.67.89:8080"
proxies = {
"http": proxy_url,
"https": proxy_url,
}
response = requests.get("https://httpbin.org/ip", proxies=proxies)
print(response.json())
💡 セキュリティのヒント
スクリプトのコードに直接ユーザー名とパスワードをハードコーディングしないでください。環境変数や.envファイルを使用し、python-dotenvライブラリを利用してください。これにより、GitHubにコードを公開する際に資格情報が漏れるのを防ぐことができます。
プロキシのローテーション: 自動IP変更
1つのプロキシは依然として1つのIPアドレスであり、ブロックされる可能性があります。真の禁止対策はローテーションです: 各リクエスト(またはNリクエストごと)で新しいIPから送信されます。以下は、ローテーションを実装するためのいくつかのアプローチです。
方法1: リストからのランダム選択
import requests
import random
# プロキシリスト
proxy_list = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
def get_random_proxy():
proxy = random.choice(proxy_list)
return {"http": proxy, "https": proxy}
# IPローテーションで10ページをスクレイピング
urls = [f"https://example.com/page/{i}" for i in range(1, 11)]
for url in urls:
proxies = get_random_proxy()
try:
response = requests.get(url, proxies=proxies, timeout=10)
print(f"URL: {url} | IP: {proxies['http'].split('@')[1]} | ステータス: {response.status_code}")
except requests.RequestException as e:
print(f"エラー: {e}")
方法2: itertoolsを使用した循環ローテーション
import requests
import itertools
proxy_list = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
# プロキシリストの無限ループを作成
proxy_cycle = itertools.cycle(proxy_list)
def get_next_proxy():
proxy = next(proxy_cycle)
return {"http": proxy, "https": proxy}
# 各リクエストは次のプロキシを循環して使用
for i in range(9):
proxies = get_next_proxy()
response = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(f"リクエスト {i+1}: {response.json()['origin']}")
時間あたり数千のリクエストを処理する産業用タスクには、レジデンシャルプロキシを使用することをお勧めします。これにより、組み込みの自動ローテーションが可能になり、プロバイダーが各リクエストごとにIPを変更し、手動でアドレスリストを管理する必要がありません。
セッションを通じたプロキシ: 永続的な接続とクッキー
1つのセッション内で複数のリクエストを行う必要がある場合(たとえば、ログインしてからリクエストを行う場合)は、requests.Session()オブジェクトを使用してください。これにより、リクエスト間でクッキー、ヘッダー、プロキシ設定が保存され、各呼び出しにプロキシを個別に渡す必要がなくなります。
import requests
# プロキシを使用したセッションを作成
session = requests.Session()
session.proxies = {
"http": "http://user:[email protected]:8080",
"https": "http://user:[email protected]:8080",
}
# ブラウザを模倣するためのヘッダーを追加
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept-Language": "ru-RU,ru;q=0.9",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
})
# ステップ1: 認証
login_data = {"username": "myuser", "password": "mypass"}
session.post("https://example.com/login", data=login_data)
# ステップ2: クッキーを使用してプロキシ経由でリクエスト
response = session.get("https://example.com/dashboard")
print(response.status_code)
# ステップ3: セッションを閉じる
session.close()
Sessionを使用すると、パフォーマンスの観点でもより効率的です: TCP接続が再利用され、各リクエストのために新たに開かれることはありません。1000ページ以上のスクレイピングを行う場合、速度の顕著な向上が得られます。
エラー、タイムアウト、そして自動再試行の処理
プロキシサーバーは利用できない場合や、遅延応答、接続エラーを返すことがあります。信頼性の高いスクレイピングスクリプトは、これらの状況を処理し、失敗時に別のプロキシに自動的に切り替える必要があります。
import requests
import random
import time
proxy_list = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
def fetch_with_retry(url, max_retries=3, timeout=10):
"""
エラー時にプロキシを自動的に切り替えてリクエストを行います。
Responseオブジェクトを返すか、試行回数が尽きた場合はNoneを返します。
"""
available_proxies = proxy_list.copy()
random.shuffle(available_proxies)
for attempt, proxy_url in enumerate(available_proxies[:max_retries], 1):
proxies = {"http": proxy_url, "https": proxy_url}
try:
response = requests.get(
url,
proxies=proxies,
timeout=timeout,
headers={"User-Agent": "Mozilla/5.0"}
)
response.raise_for_status() # 4xx/5xxで例外を発生させる
print(f"✓ 試行 {attempt} 成功")
return response
except requests.exceptions.ProxyError:
print(f"✗ 試行 {attempt}: プロキシが利用できません — {proxy_url}")
except requests.exceptions.Timeout:
print(f"✗ 試行 {attempt}: タイムアウト — {proxy_url}")
except requests.exceptions.HTTPError as e:
print(f"✗ 試行 {attempt}: HTTPエラー {e.response.status_code}")
if e.response.status_code == 403:
print(" → ブロックされました、次のプロキシを試します...")
except requests.exceptions.RequestException as e:
print(f"✗ 試行 {attempt}: 一般的なエラー — {e}")
time.sleep(1) # 試行間の待機時間
print(f"✗ {url}のすべての{max_retries}試行が尽きました")
return None
# 使用例
result = fetch_with_retry("https://httpbin.org/ip")
if result:
print(result.json())
raise_for_status()に注意してください — このメソッドは、HTTPステータスが4xxまたは5xxの場合に自動的に例外を発生させます。これがないと、スクリプトは403(ブロック)や429(リクエスト制限超過)のコードを持つレスポンスを成功と見なしてしまいます。
環境変数を通じたプロキシ: データの安全な保存
requestsライブラリは、環境変数HTTP_PROXYとHTTPS_PROXYを自動的に読み取ります。これにより、コード内に資格情報を保存せず、スクリプトを変更することなくプロキシを簡単に切り替えることができます。
ターミナルでの変数設定(Linux/macOS):
export HTTP_PROXY="http://user:[email protected]:8080" export HTTPS_PROXY="http://user:[email protected]:8080" export NO_PROXY="localhost,127.0.0.1"
または、.envファイルを使用し、python-dotenvライブラリを使用します:
# .envファイル(.gitignoreに追加してください!) HTTP_PROXY=http://user:[email protected]:8080 HTTPS_PROXY=http://user:[email protected]:8080
# Pythonスクリプト
from dotenv import load_dotenv
import requests
import os
load_dotenv() # .envから変数を読み込む
# requestsは自動的にHTTP_PROXYとHTTPS_PROXYを使用します
response = requests.get("https://httpbin.org/ip")
print(response.json())
# または環境変数から明示的に取得することもできます:
proxies = {
"http": os.getenv("HTTP_PROXY"),
"https": os.getenv("HTTPS_PROXY"),
}
response = requests.get("https://httpbin.org/ip", proxies=proxies)
print(response.json())
⚠️ 注意: NO_PROXY変数
NO_PROXY変数は、特定のアドレスをプロキシから除外することを可能にします。必ずlocalhostと127.0.0.1を追加して、ローカルリクエストがプロキシを経由しないようにしてください。
実際のシナリオ: マーケットプレイスのスクレイピング、APIとの作業、自動化
開発者が最もよく直面する3つの実践的なシナリオを見てみましょう。
シナリオ1: マーケットプレイスからの価格スクレイピング
WildberriesやOzonの価格を監視する際には、実際のユーザーの行動を模倣することが重要です: 正しいブラウザヘッダーを渡し、リクエスト間に遅延を追加し、IPをローテーションします。このタスクには、データセンタープロキシが適しています — 大量のデータを扱う際に高速かつ安価です。
import requests
import time
import random
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": "application/json, text/plain, */*",
"Accept-Language": "ru-RU,ru;q=0.9",
"Referer": "https://www.wildberries.ru/",
}
PROXIES = [
{"http": "http://user:[email protected]:8080",
"https": "http://user:[email protected]:8080"},
{"http": "http://user:[email protected]:8080",
"https": "http://user:[email protected]:8080"},
]
def get_product_price(article_id: int) -> dict:
"""Wildberriesからアーティクルに基づいて商品価格を取得します。"""
url = f"https://card.wb.ru/cards/v1/detail?appType=1&curr=rub&nm={article_id}"
proxies = random.choice(PROXIES)
try:
resp = requests.get(url, headers=HEADERS, proxies=proxies, timeout=15)
resp.raise_for_status()
data = resp.json()
product = data["data"]["products"][0]
return {
"id": product["id"],
"name": product["name"],
"price": product["salePriceU"] / 100, # 価格はコペイカ単位
}
except (requests.RequestException, KeyError, IndexError) as e:
return {"error": str(e)}
# 遅延を持っていくつかのアーティクルをスクレイピング
articles = [12345678, 87654321, 11223344]
for article in articles:
result = get_product_price(article)
print(result)
time.sleep(random.uniform(1.5, 3.0)) # 1.5〜3秒のランダムな遅延
シナリオ2: プロキシを介したAPIとの作業
一部のAPIは、1つのIPからのリクエスト数を制限しています(レート制限)。複数のプロキシ間でリクエストを分散させることで、この制限を回避できます:
import requests
import itertools
from typing import Optional
class ProxyAPIClient:
"""プロキシを介してAPIと連携するクライアント。"""
def __init__(self, api_key: str, proxy_list: list):
self.api_key = api_key
self.proxy_cycle = itertools.cycle(proxy_list)
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
})
def _get_proxy(self) -> dict:
proxy = next(self.proxy_cycle)
return {"http": proxy, "https": proxy}
def get(self, url: str, **kwargs) -> Optional[dict]:
proxies = self._get_proxy()
try:
resp = self.session.get(url, proxies=proxies, timeout=10, **kwargs)
resp.raise_for_status()
return resp.json()
except requests.RequestException as e:
print(f"APIリクエストが失敗しました: {e}")
return None
# 使用例
proxy_list = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
client = ProxyAPIClient(api_key="your_api_key", proxy_list=proxy_list)
data = client.get("https://api.example.com/products")
シナリオ3: ジオロケーションテスト
マーケティング担当者やSEO専門家は、さまざまな地域からウェブサイトがどのように見えるかを確認することがよくあります。必要なロケーションからのプロキシを使用することで、このプロセスを自動化できます:
import requests
# 異なる地域からのプロキシ
regional_proxies = {
"モスクワ": "http://user:[email protected]:8080",
"サンクトペテルブルク": "http://user:[email protected]:8080",
"ノヴシビルスク": "http://user:[email protected]:8080",
"アメリカ": "http://user:[email protected]:8080",
}
url = "https://example.com/prices"
for region, proxy_url in regional_proxies.items():
proxies = {"http": proxy_url, "https": proxy_url}
try:
resp = requests.get(url, proxies=proxies, timeout=15)
print(f"[{region}] ステータス: {resp.status_code} | "
f"サイズ: {len(resp.content)} バイト")
except requests.RequestException as e:
print(f"[{region}] エラー: {e}")
タスクに適したプロキシの選び方
プロキシのタイプの選択は、プロジェクトの成功に直接影響します。安価なデータセンタープロキシは、あるタスクにはうまく機能する一方で、別のタスクには完全に失敗することがあります。以下は、選択のための実用的なガイドです:
| タスク | プロキシタイプ | 理由 |
|---|---|---|
| マーケットプレイスのスクレイピング(Wildberries、Ozon) | レジデンシャル | 通常のユーザーのように見え、ブロックされにくい |
| オープンデータ、ニュースのスクレイピング | データセンター | 高速で安価、十分な匿名性 |
| Facebook API、Instagramとの作業 | モバイル | ソーシャルメディアはモバイルIPを最も信頼します |
| ジオロケーションテスト | ジオターゲティング付きレジデンシャル | 正確なジオロケーション、必要な地域の実際のIP |
| 高負荷のスクレイピング(10k+リクエスト/時) | データセンター(プール) | 大規模なデータ処理時の速度とコスト |
| 認証とアカウントとの作業 | レジデンシャルまたはモバイル | アンチフロードシステムのトリガーが少ない |
保護されたウェブサイトでの作業時に最大の信頼性とブロックリスクを最小限に抑える必要があるタスクには、開発者はしばしばモバイルプロキシを選択します — これらは実際のモバイルオペレーター(MTS、ビライン、メガフォン)のIPアドレスを使用し、ブロックリストに載ることが非常に少ないです。
プロキシ使用前のチェックリスト
- ✅
httpbin.org/ipでIPを確認 — 実際のアドレスが見えますか? - ✅ 速度を確認 — 応答時間は2〜3秒を超えてはいけません
- ✅
blocklist.deやipqualityscore.comを使用して、プロキシがブロックリストに載っていないことを確認してください - ✅
ipinfo.ioでジオロケーションを確認 — 期待される地域と一致していますか? - ✅ フルスクリプトを実行する前に、ターゲットサイトで1つのリクエストをテストしてください
- ✅ HTTPSトラフィックもプロキシを経由していることを確認してください(辞書内の両方のキー)
結論
Python requestsでのプロキシ設定は難しくありませんが、細部に注意を払う必要があります。覚えておくべき主な原則は、プロキシ辞書には常に両方のキー(httpとhttps)を指定し、マルチステップシナリオにはSessionを使用し、エラーとタイムアウトを必ず処理し、資格情報はコード内ではなく環境変数に保存することです。
1日あたり数千のリクエストを処理する産業用スクレイピングには、手動のプロキシリストは不十分です — ローテーションが必要です。WildberriesやOzonのような保護されたマーケットプレイスをスクレイピングしたり、ソーシャルメディアと連携したり、ジオロケーションをテストしたりする場合は、レジデンシャルプロキシを試すことをお勧めします — これにより、アンチボットシステムからの高い信頼性が得られ、単一のエンドポイントを介して自動IPローテーションをサポートし、スクリプトのコードが大幅に簡素化されます。