先把链路画出来:请求到底多走了哪一步
不开代理的时候,你的爬虫和对方站点之间就是一根直线:解析域名、建立连接、协商加密、发请求、收响应,中间没有第三者。开了代理,这根直线就被剪成两段,中间插进一台真实存在的机器——你 → 代理服务器 → 目标站点。目标站点看到的来源地址,是那台代理机的,不是你的。
所以别把"代理层"想成什么虚的东西,它就是一台有带宽上限、有并发上限、可能还挤着几百号人的机器。你每发一个请求,耗时都等于"你到它"加"它到目标"两段之和,再加上它自己做转发的那点开销。这句话记住,后面关于速度和数据的各种怪现象,基本都能自己推出来。
| 对比项 | 直连 | 走代理层 |
|---|---|---|
| 建连次数 | 1 次 TCP + 1 次 TLS | 两段连接,HTTPS 还要多一次隧道握手 |
| 单请求耗时(同一目标) | 约 120~300 ms | 约 180~500 ms |
| 同一出口的压力 | 全压在你一个地址上,很快被限 | 可以摊到很多个出口上 |
| 地区选择 | 只有本机所在的那一个 | 可以按城市挑出口 |
代理层为什么会拖慢速度:四个真实瓶颈
第一,物理距离带来的往返时间。你在广州,目标站点在华东,直连往返一次 30 ms 左右;如果代理节点在更远的机房,往返变成 60~80 ms,看起来只多几十毫秒,但一次请求要来回好几趟,一放大就明显了。原本 180 ms 翻到 400 ms 很常见,50 个并发跑一天,能抓下来的页面数能差出四成。
第二,代理机本身在排队。共享节点上同时有几十上百个任务在跑,你的请求在它那儿排着队呢。这种瓶颈有个很典型的信号:中位数耗时看着还行,P95 却突然飙到两三秒。中位数正常、尾部爆炸,八成不是网络问题,是排队。
第三,建连和认证的固定开销。走 HTTPS 代理要先跟代理建一条隧道,代理还要对每个新连接做一次身份核对(账号密码或者白名单地址),每建一次新连接就多一个来回。解决办法很土但有效:一个 IP 配一个长连接会话,尽量复用,别每个请求都重新建。
第四,超时把连接占死了。有些不稳定的节点,请求发出去七八秒才回一个错误,你的线程就一直挂在那儿等。界面上显示"并发 50",真正在干活的可能只有 12 个。给代理单独设一个短一点的连接超时,比加机器管用得多。
速度之外,代理层还在决定你的数据干不干净
速度慢一点还能忍,数据脏了才叫麻烦。有三个指标,我一般会盯得比耗时更紧。
可用率。跑 1000 个页面,失败 80 个,成功率 92%,看着还行——但那 80 个页面在你库里就是空的,后面要么补抓要么缺一块。可用率掉到 90% 以下,通常意味着节点池里混进了不少已经不好使的地址。
IP 纯净度。这是最容易被忽略的一项。某个出口地址短时间内被反复使用,目标站点会甩给你一个验证页或者一个空列表,而状态码仍然是 200。你的代码照单全收,标题字段变成"请稍后再试",价格字段是空的。等清洗的时候才发现,压根反推不出是哪一步出的问题。IP 纯净度做得好的池子,这类"假成功"会少很多。
地区覆盖。做本地化信息采集的时候,出口在哪个城市直接影响你看到的页面内容——门店列表、区域价格、当地服务范围都可能不一样。要覆盖多个城市,出口就得能按城市挑。
| 现象 | 大概率原因 | 怎么改 |
|---|---|---|
| 中位数正常,P95 暴涨 | 代理节点排队 | 换池子或者降单节点并发 |
| 状态码 200 但内容不对 | 出口 IP 被反复使用,返回验证页 | 缩短单 IP 使用时长,加内容校验 |
| 并发调高反而更慢 | 线程被慢节点拖住 | 连接超时压到 3 秒,失败就换新的 IP |
| 某些城市的数据总是缺 | 出口地区单一 | 按城市维度配置提取 |
动手测一遍:把耗时和失败率量出来
光看感觉没用,写二十行代码就能把问题定位清楚。下面这段做了三件事:每次从接口取一个新 IP、记录单次耗时、统计 P95 和失败率。
import time, statistics, requests
PROXY_API = "你在个人中心复制的提取链接"
TARGET = "你的目标列表页地址"
def fetch(session, url):
t0 = time.perf_counter()
try:
r = session.get(url, timeout=(3, 8)) # 连接3秒,读取8秒
cost = (time.perf_counter() - t0) * 1000
ok = r.status_code == 200 and "验证" not in r.text[:2000]
return cost, ok
except Exception:
return (time.perf_counter() - t0) * 1000, False
costs, fails = [], 0
for i in range(50):
ip = requests.get(PROXY_API, timeout=5).text.strip()
s = requests.Session() # 一个IP一个会话,复用连接
s.proxies = {"http": f"http://{ip}", "https": f"http://{ip}"}
cost, ok = fetch(s, TARGET)
costs.append(cost)
fails += 0 if ok else 1
costs.sort()
print("中位数 %.0f ms" % statistics.median(costs))
print("P95 %.0f ms" % costs[int(len(costs) * 0.95) - 1])
print("失败率 %.1f%%" % (fails / len(costs) * 100))
跑完看三个数就够了。中位数反映日常水平,P95 反映最差那一批,失败率反映池子质量。如果中位数 250 ms、P95 却有 3000 ms,说明瓶颈在节点排队,不在你的代码;如果失败率超过 8%,先把 IP 池换掉再谈优化。顺便说一句,内容校验那一行别省,只判断状态码的脚本,脏数据会一路溜到数据库里。
把代理层配顺的几个实操习惯
第一,现取现用。短效 IP 有有效期,提前取一大把囤着,等你用到后面一半可能已经过期了,白白拉低成功率。第二,失败两次就换,同一目标最多重试两轮,每轮换新的 IP,别在一个死节点上耗。第三,按城市配置提取,做区域数据就别用随机出口,否则同一批页面里混着好几个地区的版本,后期根本对不上。第四,给代理单独分级超时,连接 3 秒、读取 8 秒,超时立刻释放线程。
选服务商的时候,看两个东西最实在。一是出口资源的正规性和覆盖度,比如神龙HTTP 有 3000万+正规授权代理IP、可用率 99.9%、IP 纯度 99.8%,并且能做到 300+ 城市级精准定位——这个对做本地化数据的场景几乎是刚需,出口能按城市挑,页面版本才对得上号。二是能不能把用量看清楚,它的个人中心有可视化用量统计与实时监控,能识别异常请求、看使用趋势,哪段时间失败率抬头一眼就能发现,比翻日志快。接口方面支持 HTTP/HTTPS/SOCKS5,兼容主流爬虫语言,文档和示例代码都有,接进去基本是改两行配置的事,遇到卡点技术团队 7×24 小时在线。
常见问题
Q:加了代理反而变慢,是不是代理不行?
先别急着下结论,跑一遍上面的计时脚本。如果中位数只比直连高几十毫秒,那属于正常开销;如果 P95 是中位数的十倍以上,基本是节点排队或者超时没设好。把连接超时压到 3 秒、一个 IP 配一个长连接会话,通常能救回来一大截。
Q:短效动态 IP 和长效静态 IP 该怎么选?
看你的目标站点对同一地址的容忍度。目标站点对高频访问比较敏感、页面本身不依赖登录状态,用短效动态的,获取新的 IP 的成本低;需要保持会话、要连续翻页的场景,用长效静态或者固定 IP 更省心,不会翻到第三页就被打断。
Q:成功率九成多,还有必要继续优化吗?
要看那不到一成是什么。是超时,可以靠调参解决;是验证页被当成正常页面收进来了,那就是数据质量问题,必须在入库前加内容特征校验。宁可让脚本明确报失败,也别让脏数据悄悄入库,后面清洗的成本比补抓高得多。
Q:怎么在代码里判断拿到的是验证页而不是真数据?
抓两三个稳定特征做判断:页面长度是否明显变短、关键字段的选择器能不能取到值、返回内容里有没有常见的拦截提示词。任意一条不满足就当失败处理,记一条日志、换个地区的 IP 再请求一次,别直接写库。






