从一个凌晨两点的报错说起
朋友做电商比价,脚本跑得好好的,前一百多页顺风顺水,到第一百二十页突然开始返回 403。他把日志甩给我,我翻了两分钟就发现规律:前一百页平均每秒两次请求,全部来自同一个出口地址,而且请求间隔整齐得像用尺子量过。对方服务器不傻,这种"机械感"一眼就能认出来。
后来加上代理,成功率回到 98% 以上。但中间也踩过坑——有的代理加上去反而更慢,有的跑几十个请求就掉线。所以这件事值得拆开讲清楚:代理到底替我们做了什么,对方又是靠什么认出我们,以及一次请求的时间究竟耗在了哪一段。
代理不是"隐身衣",是中间多了一个代收点
把一次请求想成寄快递。直连的时候,你自己打包、自己送到对方仓库,包裹上写着你的地址,对方签收时一眼看到的就是你。用了代理,你先把包裹交给代收点,代收点换成自己的面单再送过去,对方看到的是代收点的地址,回执也是先回到代收点,再转到你手上。
技术上就这么两步:你的程序把请求发给代理服务器,代理服务器用它自己的出口 IP 重新向目标站发起一次请求;目标站返回的内容先到代理,代理再原样回传给你。整个过程你的本机 IP 不会出现在对方的访问日志里,出现在日志里的是代理的出口 IP。
在代码层面,这件事简单到只多一个参数:
import requests
# 代理地址格式:协议://用户名:密码@代理主机:端口
proxy = "http://user:pass@proxy-host:port"
proxies = {"http": proxy, "https": proxy}
url = "https://target-site/list?page=1"
# timeout 拆成 (连接超时, 读取超时),避免一直挂着
resp = requests.get(url, proxies=proxies, timeout=(5, 10))
print(resp.status_code, len(resp.text))
如果你要连续翻一百页,别每页都新建连接,把会话复用起来,握手成本能省掉一大截:
session = requests.Session()
session.proxies.update(proxies)
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...",
"Accept-Language": "zh-CN,zh;q=0.9",
})
for page in range(1, 101):
r = session.get(f"https://target-site/list?page={page}", timeout=(5, 10))
print(page, r.status_code)
请求转发里有两个细节,决定你稳不稳
第一个是转发方式。访问 http 地址时,代理能读懂你的请求内容,这叫普通转发;访问 https 地址时,代理只做加密字节的搬运工,连你请求了什么都看不见,这叫隧道模式。所以你会发现,同一个代理,跑 http 目标站和 https 目标站表现可能不一样——隧道模式下代理没法帮你改请求头,该你自己带好的东西一个都不能少。
第二个是 IP 怎么换。这是新手最容易忽略的地方。代理不是"加上了就万事大吉",你得想清楚:这一批请求是让同一个出口 IP 一直跑完,还是每隔几十个请求就换一个?前者叫会话保持,适合需要登录态、需要连续翻页的场景;后者叫按次轮换,适合量大、彼此独立的列表页抓取。用错了,要么被限流,要么数据对不上号。
还有一点:出口 IP 的地理位置会体现在对方看到的访问来源里。做本地生活、区域价格这类研究时,300+城市级精准定位就很有用,你可以让请求从目标城市附近的节点出去,拿到的页面内容跟当地用户看到的更接近。神龙HTTP 的短效动态 IP 就是按这个思路设计的,按时长或按次切换,接口里直接指定城市,不用自己维护一大堆积压的 IP。
对方到底靠什么判断"你不对劲"
很多人以为只要换了 IP 就安全了,其实来源识别是一套组合拳,IP 只是其中一拳。
看 IP 本身。同一个 C 段里短时间内冒出几百个请求、或者这个 IP 段历史上被标记过,都会被提前关照。所以选代理时别只看数量,IP 纯度更关键——一堆被反复用烂的地址,接进来只会更快被拦。
看请求头。UA 写着 iPhone,Accept-Language 却是空的,Cookie 一点没有,还带着明显的脚本库特征,这组合在一起就很假。把常用的几个头补齐,别嫌麻烦,成本极低。
看访问节奏。人类翻页是快慢不一的,脚本翻页如果每一跳都是 0.500 秒,太整齐了。加一点随机抖动,把并发压到对方能接受的量级,比疯狂换 IP 有效得多。
看链路特征。比如 TLS 握手的参数组合、请求的先后顺序。这类特征跟 IP 无关,换 IP 也救不了,只能靠更接近真实浏览器的请求方式去覆盖。
一次请求的耗时,究竟花在哪几段
很多人说"加了代理就慢",但慢多少、慢在哪,往往说不清楚。把一次请求拆开,实际是四段:
第一段是域名解析,把域名换成 IP 地址,通常 10 到 50 毫秒,有缓存时几乎为零。第二段是握手,TCP 握手一个来回,https 再叠加 TLS 握手一到两个来回,这段是固定成本,距离越远越贵。第三段是代理转运,你的数据先到代理节点,再由节点发出去,这一跳通常只多几十毫秒,前提是节点离你近、负载不高。第四段是等目标站处理并回传,这段波动最大,从几十毫秒到几百毫秒都正常。
| 耗时环节 | 直连参考值 | 经代理参考值 | 说明 |
|---|---|---|---|
| DNS 解析 | 10–50 ms | 0–50 ms | 多数代理侧已有解析缓存 |
| TCP 握手 | 20–80 ms | 30–90 ms | 省外/跨运营商节点更明显 |
| TLS 握手 | 40–120 ms | 40–150 ms | 会话复用后接近零 |
| 代理转运 | — | 5–40 ms | 多出来的一跳,节点质量决定高低 |
| 等待响应 | 80–500 ms | 80–500 ms | 跟目标站处理速度绑定,最不可控 |
看这张表就明白了:代理真正给你增加的,主要是"转运那一跳"和可能的握手成本,加起来通常不到一百毫秒。如果你的代理跑起来慢到一秒以上,问题基本不在原理,而在节点——要么距离太远,要么这个节点上挤了太多人。选低延迟高并发提取的线路,比盲目加大并发数量管用。
一套能直接抄的参数组合
把上面这些揉成可执行的设置,大概是这个思路:
连接超时 5 秒,读取超时 10 秒,别设成默认的无限等待,一个卡死的连接会拖垮整批任务。并发从 5 到 10 起步,先看成功率再往上加,不要一上来就拉满。会话保持类任务用同一个出口跑完一轮,列表页这类独立请求按 20 到 50 次换一次出口。请求间隔加 0.1 到 0.8 秒的随机抖动。
还有一件容易漏的事:出问题时要能看见。很多人接了代理就撒手不管,成功率先掉到 60% 了才发现。个人中心的可视化用量统计和实时监控这类功能,作用就是让你早点看出异常——某个城市节点突然全红、某个时段失败率飙升,一眼就能定位,不用去翻几万行日志。神龙HTTP 支持 HTTP/HTTPS/SOCKS5 三种协议,API 接入方式对主流爬虫语言都友好,文档里给了现成的示例代码,改两行就能跑通,省下的时间够你调好几轮参数了。
最后提一句选型:短效动态 IP 适合量大、彼此独立的公开数据采集;需要长期稳定登录态的任务,用长效静态 IP 或者固定 IP 代理更合适。别拿动态的去做需要保持会话的活,那属于用错工具。
常见问题
Q:已经用了代理,为什么还是被拦?
先分清是哪种拦法。如果是换 IP 后立刻恢复,说明是频率或 IP 维度的问题,加抖动、降并发、轮换出口就行。如果是换了 IP 依旧返回 403,多半是请求头或链路特征露馅了,重点去补 UA、Accept-Language、Cookie,以及看看是不是漏了 Referer。
Q:HTTP 代理和 SOCKS5 该怎么挑?
抓 https 页面并且只需要更换出口的,两者差别不大。如果你的程序不是浏览器类工具、走的不是标准 http 协议,或者需要代理层少做干预,SOCKS5 更省心。拿不准就先按目标站的协议试,跑一轮看成功率再定。
Q:加代理后慢多少算正常?
单个请求多出几十毫秒是正常的,属于转运那一跳的成本。如果慢到几百毫秒甚至超时,先排查节点距离和负载,再检查自己是不是没做会话复用——每页都重新握手,累积起来很吓人。
Q:短效动态和长效静态,到底怎么选?
看你的任务要不要"记住上一次"。需要连续翻页、需要保持登录状态的,选长效静态或固定 IP;纯列表页、详情页这种一次一取互不相干的,选短效动态,切换灵活、成本也更好控制。






