如果您的 Python 脚本收到 403 错误、验证码或 IP 禁止——这意味着目标网站已经注意到您。将代理连接到 requests 库可以解决这个问题:您更改 IP 地址,绕过地理限制,并在多个地址之间分配负载。在本指南中——从基本连接到高级轮换,提供真实的代码示例。
在 Python 脚本中使用代理的原因
大多数网站和 API 会跟踪进入请求的 IP 地址。如果一个地址每分钟发送 100 个以上的请求——它将被阻止。这是 Wildberries、Ozon、Avito、Google、Instagram 和其他数百个平台使用的标准防止机器人保护措施。代理允许通过具有不同 IP 地址的中间服务器发送请求,使您对保护系统不可见。
下面是 Python 中代理的主要任务:
- 解析市场——从 Wildberries、Ozon、Yandex.Market 收集价格而不被 IP 阻止
- 竞争对手监控——每 5-15 分钟定期向竞争对手网站发送请求
- 处理有限制的 API——在多个 IP 之间分配请求,以避免超过速率限制
- 地理定位测试——检查网站在不同国家和地区的外观
- 表单和注册自动化——从不同 IP 创建帐户或填写表单
- SEO 监控——从俄罗斯和其他国家的不同地区获取排名
没有代理,即使是写得很好的解析器也会在几个小时的工作后遇到阻止。通过正确设置的 IP 轮换,同样的脚本可以不间断地工作数周。
在 requests 中的基本代理设置
requests 库本身支持代理——无需额外的包。代理通过请求参数中的字典 proxies 传递。
最简单的示例——单个请求的 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,而不是您自己的——一切设置正确。
HTTP、HTTPS 和 SOCKS5 代理:区别和代码示例
代理有不同类型,每种类型适合不同的任务。在 Python requests 的上下文中,了解三种主要协议之间的区别非常重要:
| 类型 | URL 中的协议 | 速度 | 支持 UDP | 最佳场景 |
|---|---|---|---|---|
| HTTP | http:// |
高 | 否 | 解析 HTTP 网站 |
| HTTPS | https:// |
高 | 否 | 解析受保护的网站 |
| SOCKS5 | socks5:// |
中 | 是 | 完全匿名,支持任何协议 |
在 Python 中使用 SOCKS5 需要安装额外的包:
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 进行解析
一个代理——仍然是一个 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}
# 解析 10 个页面并轮换 IP
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,通过单个端点,您无需手动管理地址列表。
通过会话使用代理:持久连接和 Cookie
当需要在一个会话中进行多个请求(例如,登录然后进行请求)时,请使用对象 requests.Session()。它在请求之间保存 Cookie、头和代理设置——无需在每次调用中单独传递代理。
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:使用 Cookie 和代理进行请求
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 和自动化
让我们看看开发人员最常遇到的三个实际场景。
场景 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 的文章 ID 获取商品价格。"""
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)}
# 解析多个文章 ID 并添加延迟
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 限制来自同一 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、Beeline、Megafon)的 IP 地址,这些地址极少进入封禁列表。
使用代理前的检查清单
- ✅ 通过
httpbin.org/ip检查 IP——您的真实地址是否可见? - ✅ 检查速度——响应时间不应超过 2-3 秒
- ✅ 通过
blocklist.de或ipqualityscore.com确保代理不在封禁列表中 - ✅ 通过
ipinfo.io检查地理位置——是否与预期区域一致? - ✅ 在目标网站上进行一次请求测试,然后再启动完整脚本
- ✅ 确保 HTTPS 流量也通过代理(字典中两个键都存在)
结论
在 Python requests 中设置代理并不复杂,但需要关注细节。值得记住的主要原则是:在代理字典中始终指定两个键(http 和 https),在多步骤场景中使用 Session,务必处理错误和超时,并将凭据存储在环境变量中,而不是代码中。
对于每天数千个请求的工业解析,手动代理列表是不够的——需要轮换。如果您正在解析像 Wildberries 或 Ozon 这样的受保护市场,或者与社交网络交互,或进行地理定位测试,建议尝试 住宅代理——它们提供高水平的信任,支持通过单个端点自动轮换 IP,这大大简化了您的脚本代码。