写在前面:我是一个用了快六年 HTTP 代理的老玩家,从最早免费代理 IP 挨个试到现在管理着日请求量百万级的代理池,这中间踩过的坑、交过的学费、攒下的经验,足够写一本小册子。这篇文章不讲虚的,从原理到代码、从选型到排障,每一个环节都给你能直接抄走的实战方案。如果你是刚接触代理的新手,这篇能让你少走半年弯路;如果你已经在用代理但总觉得 "差点意思",后面的进阶章节大概率能戳中你的痛点。
很多人用了很久代理,却说不清它到底在网络请求里扮演什么角色。说白了,HTTP 代理就是你和目标网站之间的一个 "中间人"。你不直接访问目标网站,而是把请求发给代理服务器,代理服务器替你去访问,再把结果原样返回给你。
这个过程看起来简单,但里面有两个完全不同的工作模式,新手最容易搞混:
当你访问一个 http:// 开头的网站时,你的浏览器会把完整的 HTTP 请求(包括 URL、请求头、甚至请求体)直接发给代理服务器。代理服务器完全能看懂你在请求什么,它可以:
X‑Forwarded‑For)这也是为什么用 HTTP 代理访问明文 HTTP 网站时,你的流量对代理服务器是裸奔的。
当你访问 https:// 开头的网站时,情况完全不同。因为 HTTPS 的内容是 TLS 加密的,代理服务器无法解密,所以浏览器会用 HTTP 的 CONNECT 方法,让代理服务器建立一条到目标网站 443 端口的纯 TCP 隧道。
整个流程是这样的:
客户端 → 代理服务器:CONNECT example.com:443 HTTP/1.1
代理服务器 → 客户端:HTTP/1.1 200 Connection Established
(此后,客户端和目标网站之间直接进行TLS握手,代理只负责转发加密字节)隧道建立之后,代理服务器就退化成了一个纯粹的 "字节搬运工",它看不到你请求的具体 URL、内容、Cookie—— 只能看到你在和哪个 IP 的哪个端口通信。这就是为什么HTTPS over HTTP 代理是安全的,代理服务商无法窃取你的加密内容(当然,它能看到你连接了哪个域名和 IP)。
用代理时,DNS 解析可能在客户端完成,也可能在代理服务器端完成,这取决于你的配置方式:
http://user:pass@1.2.3.4:8080 这种 IP 形式的代理地址给程序,程序自己解析目标域名,然后把目标 IP 告诉代理。这种方式会暴露你的 DNS 查询给本地 DNS 服务器。socks5h:// 或某些支持远程 DNS 的 HTTP 代理配置,目标域名直接发给代理,由代理服务器去解析。这种方式更隐私,也能绕过本地 DNS 污染。对于爬虫场景,建议尽量让代理端做 DNS 解析,避免本地 DNS 泄漏和被污染。
买代理时你一定会看到 "透明代理"" 匿名代理 ""高匿代理" 这些词,价格差好几倍。很多新手图便宜买了透明代理,结果爬啥啥被封,还以为是自己代码写得烂。
透明代理会在请求头里老老实实告诉目标网站:"我是代理,真实客户端 IP 是 xxx"。典型的请求头长这样:
REMOTE_ADDR: 代理服务器IP
HTTP_VIA: 1.1 proxy‑server
HTTP_X_FORWARDED_FOR: 你的真实IP, 代理IP目标网站一眼就能看到你的真实 IP,也知道你在用代理。这种代理除了能加速缓存,对爬虫和匿名访问毫无意义。很多公司网络里的强制网关就是透明代理,你不需要配置,但所有流量都经过它。
匿名代理会移除 X‑Forwarded‑For,不暴露你的真实 IP,但会保留 Via 头,告诉目标网站 "这个请求来自一个代理"。
REMOTE_ADDR: 代理服务器IP
HTTP_VIA: 1.1 proxy‑server
HTTP_X_FORWARDED_FOR: (不存在)目标网站不知道你是谁,但知道你在用代理。对于有反爬机制的网站来说,"知道你用代理" 就足够把你标记为可疑流量了。
高匿代理会移除所有代理相关的请求头(Via、X‑Forwarded‑For、Forwarded 等),目标网站看到的请求和一个普通用户直接访问没有任何区别:
REMOTE_ADDR: 代理服务器IP
(没有任何代理标识头)做爬虫、数据采集、多账号运营,必须用高匿代理,这是底线。匿名代理在严格的反爬面前和透明代理没区别。
光看商家宣传没用,自己验证最靠谱。访问这些检测网站:
https://httpbin.org/headers —— 看返回的请求头里有没有代理相关字段https://whatismyip.com/ —— 看显示的 IP 是不是代理 IPhttps://browserleaks.com/ —— 更全面的泄漏检测一个简单的 Python 验证脚本:
import requests
# 不带代理直接访问
direct = requests.get("https://httpbin.org/headers", timeout=10)
print("直连IP相关头:", direct.json().get("headers", {}))
# 带代理访问
proxies = {
"http": "http://用户名:密码@代理IP:端口",
"https": "http://用户名:密码@代理IP:端口",
}
proxied = requests.get("https://httpbin.org/headers", proxies=proxies, timeout=10)
print("代理IP相关头:", proxied.json().get("headers", {}))如果代理返回的结果里出现了 X‑Forwarded‑For、Via、Forwarded 这些字段,或者 X‑Real‑Ip 显示的是你的真实 IP,那这个代理就不是高匿的,赶紧换。
这是被问得最多的问题之一。网上很多文章把两者的区别写得很复杂,其实记住一句话就行:你要代理的是网页 / API 流量,选 HTTP 代理;你要代理的不是网页(游戏、SSH、FTP、自定义协议),选 SOCKS5。
表格
维度 | HTTP 代理 | SOCKS5 代理 |
|---|---|---|
工作层级 | 应用层(Layer 7) | 传输层(Layer 4) |
支持协议 | HTTP/HTTPS | TCP/UDP,几乎所有协议 |
能否看懂流量 | 能(HTTP 明文) | 不能,纯转发 |
能否修改请求头 | 能 | 不能 |
匿名度 | 取决于代理类型(透明 / 匿名 / 高匿) | 天然高匿,不添加任何头 |
客户端兼容 | 浏览器、爬虫框架原生支持 | 需要客户端支持 SOCKS5 |
典型场景 | 网页爬取、API 调用、广告验证 | 游戏、SSH 隧道、非 HTTP 协议 |
很多人觉得 SOCKS5 更 "底层"、更 "高级",所以爬虫也用 SOCKS5。这其实是个误区。对于纯网页 / API 采集来说,HTTP 代理有几个明显优势:
SOCKS5 的优势场景是:你需要代理一个不支持 HTTP 代理的软件(比如某些游戏客户端、数据库连接工具、自定义 TCP 协议的程序),或者你需要 UDP 流量代理(比如 DNS 查询、某些 P2P 应用)。
成熟的代理使用方案通常是HTTP 代理为主力,SOCKS5 做补充。日常爬虫、API 调用走 HTTP 代理,遇到特殊软件或协议时切换到 SOCKS5。不少代理服务平台会同时开放 HTTP 与 SOCKS5 接入,同一套 IP 资源可以切换协议使用,按需选用即可。
原理讲完了,上干货。这一章覆盖最常用的五种配置方式,每一段代码都经过验证,可以直接抄。
Chrome 和 Edge 不支持在界面里直接填带账号密码的代理,需要用插件或者系统代理。最方便的方式是用 SwitchyOmega 插件:
Firefox 则原生支持带认证的 HTTP 代理配置,在 设置→网络设置→手动代理配置 里填写即可。
curl 是调试代理最快的工具:
# 基本用法
curl -x http://用户名:密码@代理IP:端口 https://httpbin.org/ip
# 分别指定http与https代理
curl --proxy http://代理IP:端口 http://target.com
curl --proxy https://代理IP:端口 http://target.com
# 详细输出,看连接过程
curl -v -x http://代理IP:端口 https://httpbin.org/ip
# 不走代理
curl --noproxy '*' https://httpbin.org/ip调试代理时永远先用 curl 验证,确认代理本身能通再去写代码,能帮你省下大量排查时间。
import requests
# 方式一:每次请求指定代理
proxies = {
"http": "http://用户名:密码@代理IP:端口",
"https": "http://用户名:密码@代理IP:端口", # 注意:HTTPS也用http://开头
}
resp = requests.get(
"https://httpbin.org/ip",
proxies=proxies,
timeout=(5,10), # 必须设超时,(连接超时,读取超时)
)
print(resp.json())
# 方式二:环境变量全局生效(适合临时调试)
# export HTTP_PROXY=http://代理IP:端口
# export HTTPS_PROXY=http://代理IP:端口
# requests会自动读取环境变量,不需要传proxies参数几个新手必踩的坑:
https 的代理值也是 http:// 开头,不是 https://—— 因为代理连接本身是 HTTP 协议,HTTPS 是通过 CONNECT 隧道承载的。@、:、#)必须做 URL 编码,@ → %40,: → %3A,# → %23。timeout 一定要设,建议分开设置连接超时和读取超时。异步场景下代理配置稍有不同:
import aiohttp
import asyncio
async def fetch_with_proxy(url, proxy):
timeout = aiohttp.ClientTimeout(total=15)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(url, proxy=proxy) as resp:
return await resp.json()
# proxy格式:http://用户名:密码@代理IP:端口
proxy = "http://用户名:密码@代理IP:端口"
result = asyncio.run(fetch_with_proxy("https://httpbin.org/ip", proxy))
print(result)aiohttp 的代理是每个请求单独传,不像 requests 那样有全局 proxies 字典。如果要做并发代理池,需要自己管理代理列表和轮换逻辑。
Scrapy 里配置代理最规范的方式是写一个下载器中间件:
# middlewares.py
import random
from scrapy import signals
class RotatingProxyMiddleware:
def __init__(self, proxies):
self.proxies = proxies
@classmethod
def from_crawler(cls, crawler):
proxies = crawler.settings.getlist("PROXY_LIST")
return cls(proxies)
def process_request(self, request, spider):
if self.proxies:
proxy = random.choice(self.proxies)
request.meta["proxy"] = proxy
spider.logger.debug(f"使用代理: {proxy}")
def process_exception(self, request, exception, spider):
# 代理失败时,移除该代理并重试
failed_proxy = request.meta.get("proxy")
if failed_proxy in self.proxies:
self.proxies.remove(failed_proxy)
spider.logger.warning(f"代理失效已移除: {failed_proxy}, 剩余{len(self.proxies)}个")
# 重新加入调度队列
return request在 settings.py 里启用:
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.RotatingProxyMiddleware": 350,
}
PROXY_LIST = [
"http://user:pass@ip1:port",
"http://user:pass@ip2:port",
# ...
]单个代理 IP 能用,但只要请求量稍微上去,必然面临两个问题:IP 被封、IP 过期。代理池就是解决这两个问题的标准方案。
一个最小可用的代理池包含三个模块:
import random
import time
import requests
from threading import Lock
from dataclasses import dataclass, field
@dataclass
class Proxy:
url: str
success_count: int = 0
fail_count: int = 0
last_check: float = field(default_factory=time.time)
@property
def success_rate(self):
total = self.success_count + self.fail_count
return self.success_count / total if total > 0 else 1.0
class ProxyPool:
def __init__(self, check_url="https://httpbin.org/ip", timeout=5):
self.proxies = []
self.lock = Lock()
self.check_url = check_url
self.timeout = timeout
def add(self, proxy_url):
with self.lock:
self.proxies.append(Proxy(url=proxy_url))
def get(self):
"""按成功率加权随机选取一个代理"""
with self.lock:
if not self.proxies:
return None
# 成功率越高,被选中概率越大
weights = [max(p.success_rate, 0.01) for p in self.proxies]
return random.choices(self.proxies, weights=weights, k=1)[0].url
def report_success(self, proxy_url):
with self.lock:
for p in self.proxies:
if p.url == proxy_url:
p.success_count += 1
break
def report_fail(self, proxy_url):
with self.lock:
for p in self.proxies:
if p.url == proxy_url:
p.fail_count += 1
# 连续失败超过5次且成功率低于30%,移除
if p.fail_count > 5 and p.success_rate < 0.3:
self.proxies.remove(p)
break
def health_check(self):
"""全量健康检查,剔除不可用代理"""
with self.lock:
to_remove = []
for p in self.proxies:
try:
proxies = {"http": p.url, "https": p.url}
requests.get(self.check_url, proxies=proxies, timeout=self.timeout)
p.last_check = time.time()
except Exception:
to_remove.append(p)
for p in to_remove:
self.proxies.remove(p)
print(f"健康检查完成,剩余可用代理: {len(self.proxies)}")
# 使用示例
pool = ProxyPool()
pool.add("http://user:pass@ip1:port")
pool.add("http://user:pass@ip2:port")
for i in range(10):
proxy = pool.get()
if not proxy:
print("代理池已空!")
break
try:
resp = requests.get("https://httpbin.org/ip",
proxies={"http": proxy, "https": proxy},
timeout=(5, 10))
pool.report_success(proxy)
print(f"请求{i}成功,IP: {resp.json()['origin']}")
except Exception as e:
pool.report_fail(proxy)
print(f"请求{i}失败: {e}")这个代理池实现了加权随机选择、成功 / 失败统计、自动剔除低质量 IP、健康检查四个核心功能,小规模爬虫直接能用。
如果你不想自己维护代理池,隧道代理(也叫动态转发代理)** 是更省心的选择。它的原理是:服务商给你一个固定的代理地址,你每次请求都走这个地址,服务商在后端自动帮你轮换出口 IP。
# 隧道代理:地址固定,IP自动轮换
proxies = {
"http": "http://用户名:密码@隧道地址:端口",
"https": "http://用户名:密码@隧道地址:端口",
}
# 每次请求出口IP都可能不同
for i in range(5):
resp = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(f"第{i}次请求IP:", resp.json()["origin"])隧道代理的好处是零维护,缺点是单 IP 的请求间隔和并发数受服务商限制,且 IP 轮换策略你控制不了(有些服务商支持按请求换、按时间换、按会话保持)。对于中小规模爬虫,隧道代理的性价比很高;大规模、精细化控制的场景,还是自建代理池更灵活。
很多新手以为 "用了代理 IP 就不会被封了",这是最大的误解。代理只是解决了 IP 层面的问题,反爬系统看的是一整套指纹。这一章讲几个代理之外必须配合的反爬手段。
requests 默认的 User‑Agent 是 python‑requests/2.x.x,目标网站看到这个直接就知道是爬虫。必须伪装成浏览器:
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,*/*;q=0.8",
"Accept‑Language": "zh‑CN,zh;q=0.9,en;q=0.8",
"Accept‑Encoding": "gzip, deflate, br",
"Connection": "keep‑alive",
"Upgrade‑Insecure‑Requests": "1",
}更进一步,每个代理 IP 搭配一个固定的 User‑Agent,不要同一个 IP 一会儿 Chrome 一会儿 Firefox,这在真实用户行为里是不可能的。
这是进阶内容,但非常重要。现代反爬系统(如 Cloudflare、Akamai)会检测 TLS 握手时的指纹。Python 的 requests 底层用的是 OpenSSL,其 TLS 握手特征和真实浏览器(Chrome 用的是 BoringSSL)有明显差异。即使你把请求头伪装得再好,TLS 指纹一暴露就会被识别。
解决方案:
from curl_cffi import requests
# impersonate参数直接模拟Chrome的TLS指纹
resp = requests.get("https://example.com", impersonate="chrome120", proxies=proxies)建议:小规模用 curl_cffi,大规模复杂场景用 Playwright + 代理。
用了代理池不代表可以无限速请求。目标网站还会看:
import random
import time
def human_like_delay(base=2.0, jitter=1.5):
"""模拟人类操作的随机延迟"""
delay = base + random.uniform(-jitter, jitter)
time.sleep(max(0.5, delay))很多网站的反爬是基于 Cookie 的。如果你的代理 IP 换了但 Cookie 没变,或者 Cookie 换了但 IP 没变,都会触发异常检测。
最佳实践:每个代理 IP 绑定一个独立的 Cookie 会话,IP 和 Cookie 一起轮换。用 requests 的 Session 对象管理:
import requests
class ProxySession:
def __init__(self, proxy):
self.session = requests.Session()
self.session.proxies = {"http": proxy, "https": proxy}
self.session.headers.update({
"User‑Agent": "Mozilla/5.0 ... Chrome/120.0.0.0 Safari/537.36"
})
self.proxy = proxy用代理的过程中,你一定会遇到各种报错。这一章把最常见的错误整理成排查手册,按图索骥就能解决 90% 的问题。
含义:代理服务器要求认证,但你没给认证信息或者给错了。
排查顺序:
@ 必须写成 %40,: 写成 %3A,# 写成 %23。这是最常见的坑。Proxy‑Authorization 头,而不是把账密写在 URL 里。含义:你的客户端连不上代理服务器,或者连上了但代理服务器连不上目标网站。
排查顺序:
curl -v -x http://代理IP:端口 https://httpbin.org/ip,看卡在哪一步。ping 代理IP,看网络通不通。如果 ping 不通,可能是代理服务器宕机或你的网络被限制。https://httpbin.org/ip(通用检测站),如果能通但访问目标网站超时,说明是目标网站封了这个代理 IP,换 IP 即可。含义:代理连上了,目标网站拒绝了请求。
可能原因:
含义:代理服务器本身出问题了,它无法连接到目标网站。
解决:这通常是代理服务商的问题,换一个代理节点或稍后重试。如果频繁出现,说明这个服务商的节点质量不行。
这是新手最崩溃的问题。常见原因:
http 和 https 是两个独立的 key,访问 HTTPS 网站只配了 http 是不会走代理的。HTTP_PROXY / HTTPS_PROXY 环境变量,和你代码里的配置冲突了。localhost、127.0.0.1 甚至某些域名加入了 NO_PROXY,导致这些域名不走代理。验证代理是否生效的黄金命令:
# 先看直连IP
curl https://httpbin.org/ip
# 再看代理IP
curl -x http://代理IP:端口 https://httpbin.org/ip两个结果不一样,说明代理生效了。
技术讲完了,最后说点实在的。代理是一把双刃剑,用不好会惹麻烦。
合理合法的使用场景:
绝对不能碰的红线:
市面上代理服务商鱼龙混杂,选的时候看这几点:
实操建议:不要直接选定某一家,至少选取 2‑3 家平台做对照测试,结合自己业务场景的实际表现再做采购决策。
如果你从头读到这里,应该已经能独立完成代理选型、配置、代理池搭建和基本故障排查了。但 HTTP 代理的水远比这深,有几个话题我故意没有展开,因为每一个都足够单独写一篇长文:
这些话题我会在后续文章里逐一拆解。如果你在实际使用中遇到了文章里没覆盖到的问题,欢迎评论区交流,我会把高频问题补充进下一篇。
代理这条路没有银弹,只有不断踩坑、不断调优。但只要方向对了,每一个坑都不会白踩。咱们下篇见。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。