关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

采集业务加机器为什么反而封得更快:出网 IP、并发节奏、连接复用和退避,这笔账该从哪一头开始算

发布时间:2026-10-08

一批机器、一堆 IP,采集速度上不去,于是又加了两台;结果第二天封得比昨天更快。技术负责人把监控翻了一遍,CPU 连一半都没到,内存还剩一大截,出网带宽连三分之一都没跑满,可待抓队列就是不往下掉。

第三天的情况更糟。新加的两台机器上线之后,被目标站点拒绝的请求占比从原来的一成左右涨到了三成往上,重试队列堆得比待抓队列还长,而整个任务的完成量不升反降。到了这一步,很多人会再往下加一圈配置,理由是“既然被拒得多,那就多开几个并发把它捞回来”。

这个动作会把事情彻底推向反面。因为在这类业务里,真正被消耗的东西从来不是算力,而是出口身份在对方那里的信誉额度。你在同一批 IP 上把请求密度抬高一倍,耗尽额度的速度也就跟着翻一倍,收割你的那一天自然来得更早。

说白了,这不是“机器够不够”的账,是“顺序对不对”的账。采集类业务的瓶颈顺序和大多数 web 业务是反的:先到顶的是每个出口 IP 在单位时间里能被接受多少次,而不是这堆机器能算多少。顺序搞反了,加机器就是在一张已经快刷爆的卡上继续刷。

加了两台机器,第二天封得更快:顺序错了

把这个场景拆开看,加机器这个动作实际上改变了三个变量,而其中两个是负的。

头一个变量是请求总量。机器翻倍,单位时间里能发起的请求数上限确实变大了,这是唯一一个正收益。第二个变量是这些请求的密度分布——如果新机器没有配套的新出口 IP,多出来的请求依然从原来那几个 IP 出去,于是每个 IP 在每一秒里的请求数跟着机器的数量一起涨。第三个变量是失败请求的绝对数量:被拒绝的请求同样要占用连接、占用解析、占用重试队列,这部分开销在总量里越占越大。

三个变量合起来,结果就是这样一句绕口令式的现实:你加的是产能,消耗的是信誉。产能每提升一点,单位时间内的流量消耗就多一分,而对方给你的额度是按 IP 算的,不会因为你有六台机器而不是四台就多给一分。

打个更直白的比方。高速口的收费站开了三个窗口,每个窗口每分钟能放行的车是有限的。你在后面又修了两条更宽的路,让更多车更快地涌到同一个口子上,通过量不会按比例变大,反而会让口子更早开始限流。想真正提升通过能力,要开的是窗口,不是车。

所以正确的顺序是反过来的:先把每个出口 IP 的请求节奏压到对方能接受的范围内,再拿这个节奏去倒推需要多少个 IP,最后才轮到“这些 IP 该怎么分摊到机器上、每台机器需要多少连接能力”。机器规格是这条链上最后一环,而最后一环也常常被误认为是头一环。

合规这条线先划清楚:哪些数据能采,哪些不能

在讲任何节奏和参数之前,这条线必须先划清楚,因为它不是“写完了自查一遍”的收尾工作,而是决定你这个项目该不该立项的前置条件。本文讨论的采集场景,仅限于面向公开、且目标站点的服务条款允许自动化访问的数据,例如自有站点的巡检与健康检查、公开目录页面的字段监测、与自己业务相关的公开信息整理。除此之外的情况,都不在本文的建议范围内。

以下几条边界请当作硬约束来读:

其一,遵守 robots 协议。抓取之前先取目标站点的 robots.txt 并严格遵循其中的允许与禁止规则,同时留意页面里的 robots 元标记。Disallow指向的路径不碰;如果对方在协议里给出了抓取延时的指示,应当把它当作对方明确表达的接受频率来对待。这一层的规则由 RFC 9309 所定义,属于互联网通行的基础约定。

其二,遵守目标站点的服务条款与使用协议。很多站点在自己的条款里写明了是否允许自动化访问、允许到什么程度、是否要求事先书面许可。条款写明禁止的,不要做;条款要求申请的,拿到书面授权再做。需要注意的是,robots 协议允许不等于服务条款允许,这两份文件要分别看,取更严格的那一份执行。

其三,不对目标站点造成可用性影响。这是技术设计的硬限制而非口号:请求密度要控制在对方明显能够承受的范围,避开对方的业务高峰时段,遇到对方出现超时或服务端错误时应当主动降速而不是加大重试。如果你的采集让对方的响应时间明显变长、让正常用户的访问受到影响,这件事的性质就变了。

其四,不得规避或破坏对方设置的技术保护措施。验证码、登录墙、签名参数、接口鉴权、设备指纹校验等,都是对方明确设置的访问控制手段。遇到这些关卡,正确的解读是“对方不希望被自动化访问”,正确的动作是停下来、走正规渠道申请授权或改用对方提供的正式接口。本文不会也不提供任何形式的规避方法。

其五,不采集非公开或需授权的数据。登录之后才可见的内容、付费墙之后的内容、站点后台接口、未在文档里公开的 API、需要特定身份才返回的数据——这些一律不在公开数据的范围内。是否需要授权、需要何种授权的判断标准很简单:一个不带任何身份的普通访客能不能在正常浏览中看到它,看到了又能不能拿去重发布。

其六,涉及个人信息和受著作权保护的内容要另行处理。个人信息的收集需要有合法基础并履行告知义务,很多时候还需要单独取得同意;受著作权保护的文字、图片、音视频,批量复制和再分发是另一件事,引用应当有限度地小幅使用并注明来源。这两类内容的处理规则与“是不是公开可见”并不重合,公开可见不等于可以随意处理。

其七,最小必要与留存期限。只采集业务真正需要的字段,采集之后设定明确的留存周期和访问权限,数据不外流、不转售。跨境情形下还要额外看数据的处理地与跨境传输规则。

其八,可被识别、可被联系、可被叫停。请求里带上真实的 User-Agent 和可联系的邮箱或网址;对方明确要求停止时应当配合停止。这一点看似吃亏,实际上是长期稳定采集最划算的做法——把自己藏起来只能换来短期隐蔽,换来不了长期稳定。

最后说明本文的立场:下文会讲到“目标站点通常依据哪些特征对访问者做频率限制”,这部分写的是类别层面的常识,用来解释为什么会被限流、以及架构上应当如何顺应对方的可接受范围。它不是、也不打算成为任何形式的规避手册。

采集业务的负载画像和网站业务是反的:下行小、连接多、请求密

很多人把采集业务当成“一个访问量特别大的网站服务”来配,这是全部误判的源头。两者的负载画像几乎是反过来的,看五个维度就清楚了。

请求方向。普通站点是被别人访问,连接是进来的;采集业务的连接是主动发起、往外的。这意味着限制你的不是“能同时接纳多少连接”,而是“能同时建立多少条出去的连接”——后者多了 NAT 网关、本地端口池、出口 IP 身份这三层普通站点不太需要考虑的东西。

单次传输量。一次采集请求拿到的响应通常不大,尤其是列表页、详情页这种场景,单个响应体比视频、图片分发场景小一到两个数量级。所以下行带宽在绝大多数采集业务里都不是瓶颈,这也是开头那个案例里带宽连三分之一都没跑满却没速度的原因。

连接密度。总量不大,但条数极多。HTML 页面篇幅有限,要在单位时间里完成一万次请求,你就得建立、维护、销毁上万级别的 TCP 会话。于是“每秒新建连接数”变成了比“带宽占用”更硬的指标,连接建立与关闭侧的开销(握手、挥手、TIME_WAIT 状态的堆积、句柄的占用与释放)全部浮出水面。

时间分布。普通站点的访问在时间上相对连续,白天高、夜里低;采集任务往往是批量触发的,一批任务是“啪”地全部压上去,跑完就归零,下一个周期再来一次。这种突发形态天然更容易撞上限流,因为绝大多数站点的频率控制都不止一道窗口。

失败与重试占比。普通业务里失败请求是少数;采集任务在目标站点众多、网络路径各异的情况下,失败请求常常占到总请求量的相当一部分,而重试又会产生新的请求,形成“请求数 = 原始请求数 × (1 + 重试放大系数)”。这个放大系数如果不控制,你实际打出去的压力会远大于你以为的那个数。

把这五条合起来,采集业务真正需要被规划的资源维度也就清楚了:出口 IP 数量、单位时间请求数、每秒新建连接数、并发连接数、DNS 查询速率、TLS 握手 CPU、文件句柄,最后是解析与存储的写入能力。CPU 和下行带宽排在最末两位。

出口 IP 的身份额度:限制往往落在“每个 IP 单位时间多少次”

理解这一条,就理解了采集业务的全部架构逻辑。

从目标站点那一侧看过来,一个访问者的最小可辨识单位是来源 IP。它无从知道你的背后是一台机器还是六十台机器、是单进程还是多线程、你的 CPU 有多闲,它能看到的就是“这个地址在这段时间内来了多少次、间隔什么样、失败了几次、有没有按规矩做事”。因此它的频率约束天然是挂在 IP 上的,而不是挂在你的机器数上的。

这带来一个非常反直觉的结果:你在机房里加十台机器,如果它们共用同一个出口,那么在对方眼里,你依然是“那个地址”,只是那个地址突然变得异常活跃。你以为你在扩展,对方看到的是一个 IP 的行为突然变得不自然。

常见的按 IP 计量的约束维度大致有这么几类(这里只讲类别,不讲任何具体阈值,具体阈值由对方规则和你自己的试探确定):单位时间内的请求次数(这是最典型的一类,且往往存在多个时间窗口)、单位时间内的新建连接数(即使请求数很低,频繁开关连接同样会触发)、并发连接数(同时保持的会话数量)、错误请求占比(大量非法请求、大量无效路径会被单独看待)、行为形态(间隔是否高度规律、是否完全没有停顿、是否在人类不可能的时间密度内连续出现)。

注意最后一类。很多团队把精力花在“把间隔调得更随机”上,方向其实放错了。抖动的真实作用是把突发摊平、让自己的每一秒更接近自己宣称的平均水平,属于流量整形,目的是不对你自己的采集目标造成冲击;把它当成伪装手段,短期可能好看,长期只会让对方把阈值收紧,最后所有人的成本一起上升。

回到钱。既然额度挂在 IP 上,那 IP 就是这类业务里最实在的一种资源。加多少个附加 IP、出网带宽按什么口径算,是下单之前就要明确的事,不是买完机器开完机再回头补的选项。

由总吞吐倒推 IP 数量,而不是由机器数量倒推

顺序确定了,下面这笔账该怎么算。

头一件事是把需求翻译成一个明确的量:目标是通过多长时间覆盖多少个地址。比如要覆盖的 URL 总数是 U,希望一遍跑完的时间是 T,那么在理想情况下需要达到的平均处理速率就是 U 除以 T,单位是“每秒完成的请求数”。这个数字是整个设计的锚,后面所有决定都由它推出来。

但这个理想值不能直接拿来用,因为它没有算代价。实际要发起的请求数 = U ÷ 有效完成率。失效的部分来自三方:一是抓取失败需要重试的请求,二是目标站点主动拒绝的请求,三是被去重或过滤掉的请求。这三部分占比多少,取决于你的目标质量和我方的工程质量,只能靠历史积累去估算,并且应当在计算时留出相当的余量。

第二件事是确定单个 IP 能持续承载的速率。这个数字没有通用值,它由两样东西共同决定:目标站点自身的可接受频率,以及你在小规模下逐步试探得到的实际表现。注意“可持续”这三个字很重要——短时间冲上去能扛住不代表能持续一小时。取那个能长期跑而不引发限制响应的速率,而不是取那个还没被拒绝的最高速率。

第三件事才是倒推:所需出口 IP 数量 ≈ 实际总请求速率 ÷ 单个 IP 的可持续速率。算出来的这个数决定了你至少需要多少个出口身份。有了它,再去谈这些 IP 怎么分配到机器上、每台机器负责几个。

这里有一个非常常见的分配错误:把 IP 全部挂在某一台机器上,其它机器通过它转发。这样做的结果是那台机器同时承担了全部的连接数、全部的握手 CPU、全部的 DNS 查询,它的连接数和句柄先到顶;而其它机器闲着。分配 IP 的头一条原则是让流量在机器上均匀展开,而不是为了方便管理集中摆在一处。说白了,这条原则的目的是容量均衡,是为了容量均衡,是为了不让任何一台机器成为不必要的瓶颈。

最后补一刀现实:IP 数量也不是想要多少就有多少的,它涉及资源成本、涉及对方的接受逻辑、也涉及你自己的管理成本。每多一个出口 IP,就多一份要维护的行为准则——那个 IP 上跑的请求同样要守节奏、同样要退避、同样要有合理的失败表现。管理不好的 IP 池,比小而精的 IP 池更危险。

并发节奏怎么定:间隔、抖动和峰值,哪个才是关键

讲到节奏,很多人的脑子里只有一个参数:两次请求之间隔多久。其实决定你有没有事的,是三个参数,而最关键的是第三个。

头一个参数是平均间隔,它决定均值。只要总请求数除以总时间不变,无论你怎么抖,均值都摆在那里。均值决定的是“这件事长期有多大压力”,它是基础,但不是最容易出问题的那一层。

第二个参数是抖动,它决定分布形状。完全没有抖动的固定间隔是最容易识别、也最容易形成周期共振的形态——如果多个任务、多个 IP 都以相同的周期打在同一个目标上,它们的突发可能会在某个瞬间叠加成一次意外的高峰。加入抖动的工程意义是把这种叠加打散,让实际密度更平滑地贴合你的平均值。

第三个参数是峰值,也就是最短的那个间隔、最密的那一小撮突发。这才是真正致命的一项。原因是绝大多数站点的频率控制并不是单一窗口的:它可能同时存在一道很短的秒级窗口、一道分钟级窗口和一道更长的配额窗口。你的分钟均值完全合规,只要在某一秒里潮了那么一下,撞的就是那道短窗口。很多人拿着“我平均下来很慢”去申诉被拒,问题恰恰出在这里。

所以节奏设计的正确姿势是从限制突发开始,而不是从调整均值开始。在工程实现上,这件事应当做成你自己这一侧的一套主动限速:以令牌桶或漏桶这样的方式,给每个目标域名、每个出口 IP 分别设定上限,让请求在进入网络之前先排好队,而不是等到对方返回拒绝再手忙脚乱。

排队这件事本身有代价,这一点要说清楚。排队论里有一条很朴素的规律:当利用率接近上限的时候,排队时长不是线性增长,而是迅速劣化。也就是说你把并发开到贴近自己能力的那一刻,单次请求的平均耗时会被拖长,而耗时一长,为了完成同样的总量你又需要更多并发——这是一个自我强化的循环。留出余量不是浪费,是让延迟回到可控区间唯一的办法。

另外一个常被忽略的点:限速要挂在“目标域名 × 出口 IP”这一个粒度上,而不是挂在全局。全局限了 A 站点会连累 B 站点;只按 IP 不限域名,则同一时刻忽然全部打到同一个域名上。两个维度都要有。

连接复用为什么能同时降低延迟和被限流的概率

连接复用在这一类业务里的收益被严重低估了,因为它同时解决了三个方向上的问题。

头一个是延迟。每一次新建 TCP 连接都要付出握手往返的时间,如果是加密协议还要再叠加握手的额外往返。这些往返叠加起来常常比真正取回数据的时间还长。把连接保持住、让后续请求直接复用已有会话,等于把每次请求里最贵的那一段一次性抹掉。

第二个是减少新建连接的数量。前面说过,目标站点侧往往会单独统计新建连接速率。用一百次新连接取一百个页面,和用一条连接取一百个页面,在对方眼里是两种完全不同的行为,尽管数据量一模一样。省下来的不只是时间,还有你在这个维度上的“曝光量”。

第三个是本机侧的开销。每条新连接都要占用句柄、要经历建立与关闭的状态迁移、关闭之后还要滞留一段时间维持在 TIME_WAIT 状态才能彻底释放那条四元组资源。高并发短连接跑一段时间之后,主机身上堆积的是大量的等待回收状态而不是有效连接,这会直接影响后续连接能不能建立起来。

再往下走一层,新一代的多路复用协议让“一条连接同时承载多个请求”这件事成为可能,它与连接复用是同一个思路的延伸:用更少的连接搬运同样的请求量。支持的话应当用起来,不支持的时候至少做到HTTP 层的 keep-alive。

连接池怎么配大小,是个平衡问题。池子太小,请求要在池外排队,排队时长会被计入你的平均响应时长,看起来像是对方变慢了;池子太大,你在对方那里维持的并发连接数就高,本机占用的句柄和内核缓冲区同样上涨。合理的做法是让它跟“这一个目标域名的实际并发需求”对齐,并且给空闲连接设定一个合理的存活时长——既不至于频繁重建,也不至于长期空占。

最后一句提醒:复用的前提是那条连接还健康。对方主动关闭、中间链路超时、会话凭证过期,都会让复用失败。所以复用必须配套连接健康检查与失败后的重建逻辑,否则复用会变成“复用了一条死连接”,表现为请求莫名其妙地卡住。

DNS 这一环:解析频率、缓存位置和解析失败的代价

在采集业务里,DNS 是被低估得最厉害的一环,因为它平时看不见。

看不见的原因很简单:普通业务一天就那么几个域名,解析一次能用很久;采集业务面对的域名数量是前者的几百上千倍,而且新域名源源不断地进来。于是解析从一个可以忽略的边缘环节,变成了主链路上的常驻环节。

头一个要考虑的是解析频率。如果每个请求发起之前都要解析一次域名,那么你的实际请求量会在递归解析器那里再翻一倍,而且每次解析都要付出一次网络往返——这份延迟会被计入你自己的平均响应时长,看上去像是对方慢。正确的做法是缓存:以域名为单位缓存解析结果,并且在缓存有效期之内复用。有效期本身要看对方设置的记录存活时间,过长会错过地址变更,过短又会重新制造大量查询,这里需要一个上下限,而不是完全照抄。

第二个要考虑的是缓存放在哪一层。放在应用层、每台机器各缓存各的,好处是路径短、无额外依赖,代价是缓存不共享、每台机器都要各自去查询一次;放在本地解析缓存服务里统一处理,好处是跨进程共享、命中率高,代价是多了一层要单独监控的组件。规模小的时候前者够用,规模大了后者省事。另外要特别处理失败结果的缓存——解析失败的域名如果不做短期负缓存,重试逻辑会把它变成一轮新的查询风暴。

第三个是解析失败的代价,这一项最容易被算漏。解析失败意味着这次请求根本没走到目标就失败了,于是进入重试;如果重试逻辑里包含着“立刻重试、不限次数”,那么一次解析抖动会立刻被放大成一串无效重试。解析失败应当被单独计数、单独设限,而不是混进普通请求失败里一起重试。

第四个是预解析。对于已知的大批量待抓域名,可以在真正发起请求之前,用独立的轻量任务提前把它们解析掉并填充缓存,把解析的延迟从关键路径上挪走。这件事做得好的话,采集主流程里几乎感知不到 DNS 的存在。

最后提醒一句跨出口的情形:如果多个出口 IP 分属不同的网络路径,那么解析所在的路径与实际请求发出的路径是否一致,是需要确认的,否则会出现“解析到一个地址、实际从另一个出口去连”的错配,表现为间歇性的连接超时,排查起来相当费劲。

TLS 握手的开销在采集场景里被放大了多少倍

加密连接在今天已经是默认项,采集也不例外。而这个“默认是好事”的东西,在采集这种短请求、高密度的场景里,会被放大得特别厉害。

放大的机制不难理解,它是一个纯粹的比值关系:一条连接的总成本里,握手部分占比 = 握手成本 ÷ 这条连接总共承载的请求数。普通用户在一次会话里连续打开几十个页面,握手被摊薄了;采集任务如果“一次请求一条连接”,握手就无法摊薄,每一次请求都要完整地付一遍。

这一笔账具体包含几项。时间上的代价:完整握手要在 TCP 的往返之外再叠加若干次往返,往返延迟越高、跨地域距离越远,这部分占比越大。算力上的代价:握手过程中涉及的公钥运算,是整条连接里最吃 CPU 的一段,机器本身孱弱时这一段会非常明显。对对方的代价:密集的握手会让目标站点一侧付出同样的公钥运算开销,所以它常常也是一个被单独考量的维度。失败面上的代价:握手多了一个环节就多一批失败原因,证书链校验、协议版本协商、套件协商、服务端主动拒绝,都会在这里冒出来。

应对的办法其实集中在两件事上。头一件是让每条连接多承载一些请求,也就是前面讲的连接复用;第二件是让重复建立的连接尽可能走会话复用,让第二次、第三次握手不必再付完整的公钥运算。这两件事叠在一起,能把这部分的开销压下去一大截。

这里有一条绝对不能碰的底线:证书校验必须保持开启。哪怕是在自己眼皮底下的内网环境,为了省事而去关闭校验,等于把这一整套加密的意义全部抹掉,会引入远比性能严重得多的问题。性能可以这样提速,安全性不能这样让路。

还有一个细节:证书校验是有时效与吊销状态考量的,过度频繁地去查询这些状态,同样会给这条链路增加额外的请求与延迟。如果采用了这类机制,缓存策略要一并设计。

重试与退避:不做退避的重试,等于在对方最虚弱的时候又推一把

这一段是全篇最容易被跳过、也最贵的一段。

为什么这么说?因为失败的分布不是均匀的。对方开始拒绝你,往往是因为它自己也在承压——响应变慢、出现大量服务端错误、连接被重置,这些都是典型的承压表现。而恰恰在这个时候,一个“失败就立刻重试、重试三次”的逻辑会让它承受的压力再涨一截。这就像是在别人最虚弱的时候又推了一把,是把自己推向雪崩的那一记力量。

所以退避不是锦上添花的优雅,而是防止自伤的必要动作。一套说得过去的重试策略需要包含这几项:退避时间是随着失败次数增长的,而不是固定的;退避时间要带随机成分,否则成千上万个失败请求会在同一秒集体苏醒,形成第二轮突发;重试次数要有上限,超过就进入失败队列,而不是无限重试;退避时间要有封顶,否则会出现一次失败导致任务挂起几小时的情况。

还有一样东西必须尊重:对方在响应里给出的建议等待时间。如果对方明确告诉了你多久之后再来,那就照办,不要自作聪明地提前。这是一个沟通渠道,而不是一个可以忽视的提示。

第二个关键点是区分可重试与不可重试。连接超时、连接被重置、服务端错误、明确标记的限流响应,这些值得重试;请求格式错误、鉴权失败、资源不存在这类,重试一万次也是同样的结果,只会白白增加你的错误占比。而错误占比本身,就是对方考量的维度之一。

第三个关键点是很多人忽略的:重试也是请求,也必须走同一套限速。如果重试请求没有走你的令牌桶,那么你的实际发出速率就是“限速值 + 重试量”,限流器形同虚设。正确做法是让重试请求重新进入同一个队列、占用同一份额度。

第四个关键点更重要:一次失败不要立刻换 IP 重试。这个动作看起来聪明,实际危害极大——它会把单点的、局部的问题迅速扩散到你所有的出口身份上。本来只有一个 IP 被对方盯上,几轮换 IP 之后,整个 IP 池在那一个站点上的记录都被弄脏了。失败的时候,正确顺序是先退避、再重试,实在不行再换出口,而不是把换出口当成条件反射。

最后是熔断。当一个目标域名连续失败到一定程度,应当暂停这整个域名,而不是把剩下的 URL 继续往里砸。熔断保护的是你自己的队列和你的 IP 池,也是对方的系统。

失败 IP 的隔离与冷却:轮换不是越快越好

IP 池管理的核心不是“怎么轮换”,而是“什么时候别动”。

先分清楚失败的层级,这一步做错后面全错。单个 IP 在单个目标上失败,是这个 IP 与该目标之间的关系出了问题——可能是这个 IP 被那个站点重点关注了,也可能是那个 IP 所在网段的路径质量不好。所有 IP 在同一个目标上都失败,是这个目标出问题了,可能是对方整体停服,也可能你的请求方式本身有错。多个 IP 在多个目标上同时失败,那是你自己的出口链路或者程序出问题了。

这三类的处置完全不同,但团队里最常见的错误反应是同一个动作:换 IP。如果所有 IP 都失败,换 IP 只是在旋转木马,除了把问题摊开,什么都没解决。正确的动作是先判断层级,是目标站点的问题就整体暂停,是自己程序的问题就先修复。

确定是单个 IP 的问题之后,才轮到隔离与冷却。冷却的意思是:把这个 IP 从这个目标的可选名单里摘出去一段时间,而不是立刻换个 IP 继续。时间长短由对方的表现决定,而不是由你的进度焦虑决定。放回的时候也要慢——先放一小部分流量进去试探,确认表现正常之后再逐步恢复,而不是一把全部切回来。

这里有个必须讲的原则:冷却期的进度损失不能被补回来。很多团队的处理方式是“这个 IP 停了,那就把它的份额分给其它 IP”,结果一个 IP 的问题变成整个池子都不健康。损失要由时间来吸收,不要由压力来平移。你可以延长任务的完成时间、可以缩小单次任务的覆盖范围,唯独不应该把同一个压力换个地方再压一遍。

还有一条值得单独提:健康评分要以“IP × 目标域名”为单位来记,而不是以 IP 为单位。同一个 IP 在甲站点上表现糟糕,在乙站点上可能完全正常。用一个全局分数去评判 IP,会把大量本来可用的身份误杀掉,池子越治理越小。

把上面这条链上的各个环节从头到尾摆一遍,每个环节管什么、超了会怎样、该从哪里动手、动它的代价是什么,如下表:

链路环节 决定什么 超限后的表现 该从哪里调 调它的代价
出口 IP 数量 决定单位时间里能被目标站点接受的请求总量上限,是整个采集能力的天花板 机器上再闲也排不出速度,请求在队列里堆着,整体完成时间拉长 由目标吞吐除以单 IP 可持续速率倒推得出,并均匀分摊到各台机器 需要额外资源与管理成本,每个 IP 都要单独维护行为准则,池子越大治理越难
单 IP 请求节奏 决定对方是否把你当作可接受流量,包括均值、突发形态和多个时间窗口上的分布 出现明确的限流响应、拦截页面或连接被重置,且失败集中在某一批 IP 上 优先削峰而非压均值,在自己这一侧做主动限速而不是等对方拒绝 直接把采集速度降下来,完成周期变长,需要与业务方对齐可接受的时间窗
连接复用与连接池 决定单次请求的平均耗时、每秒新建连接数和本机连接资源的占用水位 响应时长莫名变长、端口资源紧张、握手 CPU 升高、对方侧的新连接指标走高 按目标域名对齐连接池大小,配置合理的空闲存活时长与健康检查 池子过大推高句柄与内存占用、抬高对方侧并发;过小则把延迟转移到排队上
DNS 解析 决定解析引发的额外延迟、查询量放大比例,以及解析失败造成的无效重试 平均响应时长忽高忽低,出现大量“还没发出就失败”的请求,查询侧压力明显 按记录存活时间做缓存并设置上下限,独立统计解析失败并对失败结果做短期负缓存 缓存过久会错过地址变更,要多一层失效兜底;预解析要占用额外的调度资源
重试退避策略 决定失败发生时实际打出去的压力有多大,以及单个 IP 的问题会不会扩散到整个池子 对方承压时压力反而倍增,失败从单个 IP 蔓延到全部出口,任务越跑越慢 区分可重试错误,采用递增且带随机的退避与重试上限,尊重对方给出的等待建议 瞬时失败率看起来会变差,部分数据要推迟到下一轮,任务排期需要留出缓冲

机器这一环:先到顶的通常是连接数与句柄,不是 CPU

到现在才轮到机器,而且这一环也常常被理解错。

先看现象。一台跑采集任务的机器,最典型的画像就是 CPU 常年低位、带宽用不满、内存缓慢上涨、请求耗时越来越长。这不是 CPU 不行,是这个任务压根就不消费 CPU。真正被消耗的是下面这些东西:

并发连接数。每条连接都要占据一个 socket,都要占用相应的文件句柄。连接数堆到一定程度,新连接就再也建不起来了,而这个临界点往往远远早于 CPU 打满的时刻。

每秒新建连接数。短连接模式下,连接的建立与关闭是持续发生的高频动作。每一次都要经历完整的状态迁移,加上 TIME_WAIT 状态会占住那条四元组资源一段时间,堆积起来的等待回收状态会挤压后续连接的可分配空间。这一层的限制是协议层面的,跟机器配了几核没有关系。

文件句柄。连接是文件、打开的重定向或多个下载任务也是文件,句柄上限是进程级和系统级双重设置的。它是一道硬墙,撞上去的表现是“连接失败”而不是“变慢”,所以排查时常常被误认为是对方拒绝。

内存。内存排在 CPU 之前到顶,因为它托住的是上面这几样东西:每条连接都有内核收发缓冲区,连接池越大、缓冲越足,占用越高;待处理队列、解析中间对象、DNS 缓存也在吃内存。说到底,最终限制连接数的那堵墙,通常就是内存。

握手与加解密的 CPU。这一项确实吃 CPU,但它只在频繁新建连接时才显著——一旦把连接复用做好,这部分会迅速降下来。

把这些摆清楚之后,“加机器究竟解决什么”这个问题也就有了答案:横向扩容只有在同时给每个容器或每台机器配上独立或部分独立的出口 IP 时,才算真正扩容。否则新机器只是把同一份额度更快地花掉。CPU 核数、内存大小,决定了单台机器能承载多少并发连接;而总吞吐由出口 IP 数量决定。这两个数要分开算,不能互相替代。

分工上还有一条实用建议:把“发起请求”和“解析存储”拆到不同的机器或不同的进程池里。抓取进程只管按节奏把响应拿回来、按域名拆分丢进队列;解析进程慢慢处理。这两个负载的性格完全不同,缠在一个进程、一台机器上的时候,谁先把水位拉满你根本看不出来。

(顺带说明:本篇讲的是架构侧的容量与节奏设计。主机侧的连接数、句柄、端口占用如何逐项分诊,属于另一类系统诊断话题,本文不展开。)

数据落地:解析和存储是另一条独立的水位线

最后一环经常被当成“顺手的事”,但它具备把前面所有努力一次性废掉的威力。

采集的下游流程大致是:拿到响应 → 解析提取结构化字段 → 去重 → 落库 → 建索引。这条线的资源和前面的抓取线完全不共享:解析吃 CPU,去重吃内存和随机读写,落库吃磁盘写入与事务,索引更新再吃一遍 CPU 和磁盘。它的节奏由自己的承载能力决定,跟对方站点接不接受你没有半点关系。

危险在于两者的耦合方式。如果下游慢了而上游继续全速抓取,中间堆的是队列。队列堆在内存里就是内存上涨,堆到一定程度触发回收、换页、垃圾回收,整个进程的响应能力直线下降,表现为“抓取变慢了”——而你会误以为是对方限流,于是去调爬取的并发,越调越糟。这是加机器之后第二天更糟的另一个解释,跟 IP 额度无关,但同样致命。

所以这两条线之间必须有背压:下游处理不过来的时候,上游要自己慢下来,而不是把数据堆在中转层里等着溢出。背压不是故障处理,是常态管理机制。

去重也是一个深坑。多个机器同时抓、同时又用一个集中的去重表做判重,那张表会成为全局热点:锁竞争、写放大、连接数打满,全都集中在这一点上。合理的做法是按目标域名或者按哈希对被抓取对象做分片,让每台机器负责互不重叠的区间,同一个对象不会被两台机器同时拿到。这样既省了一次抓取,也去掉了热点。

存储写入还要考虑幂等。既然上游一定会重试,那么同一个对象被写两次是必然会发生的,不是异常。写入接口要能识别重复,否则你的库里会多出一堆内容相同、主键不同的记录,清理起来比当初设计幂等所花的成本高得多。

最后是容量规划的习惯问题。给下游留的余量要大于上游,因为在业务量涨上来的时候,下游的资源(尤其是数据库)比上游难扩展得多。宁可让抓取慢一点、整体周期长一点,也不要让数据堆积。

监控要看什么:成功率、限流响应占比、单 IP 失败率、平均响应时长

采集类业务的监控和普通网站正好相反:均值几乎没有信息量,分布才有。下面这四项是必看的,缺一项你都会在关键时候判断错方向。

成功率要按返回类别拆开看。只统计一个总成功率会被掩盖:正常响应、重定向、客户端错误、服务端错误、明确的限流响应、连接层错误——这六类要分开计。很多团队看到总成功率还行就放心了,没注意到限流响应已经从零悄悄涨到了两位数百分点。

限流响应占比要看趋势而不是绝对值。这一类响应的绝对数量会随任务量自然波动,所以真正有用的是它在同期总请求里的占比曲线,以及它相对于上一周期的变化率。占比开始抬头的那一刻,就是你该降速的时候,而不是等到失败成灾。这个指标也应当按目标域名拆开,因为往往是某一个站点先出问题。

单 IP 失败率要看分布,而不是看整体。这一项最能暴露架构问题:如果整体失败率正常,但失败高度集中在某几个 IP 上,说明要么流量分配不均,要么那几个 IP 已经被对方重点关注。反过来说,如果失败率在所有 IP 上均匀升高,那么问题大概率在程序、在自己的出口链路、或者对方整体状态不好,换 IP 没有意义。同一个失败率数字,两种分布,处置方式完全相反。

平均响应时长要看分位数。平均数会被大量快速成功的请求拉低,而真正先坏的是长尾。把中位数、高位分位数、极端分位数三条线分开画,长尾先抬起来往往是一切恶化的前兆——它意味着对方开始慢了、或者在排严重的队,这时候主动降速的代价远小于事后补救。

在这四项之外,还有几个专项指标值得单独立面板:每秒新建连接数、连接池占用率与等待时长、DNS 解析成功率与耗时、重试请求在总请求里的占比、被熔断暂停的目标域名数量、上游去重前后的条目数对比。后两项分别告诉你“你的吞吐有多少是浪费的”和“有多少是不该发生的”。

最后一句务实的建议:保留一份可回溯的记录。记录谁在什么时候、用了哪个出口、抓了哪个目标的哪些路径、遵守了什么样的频率。这份记录在出现争议的时候是唯一的自证材料,而且它的存在会反向约束团队不去碰边界。合规要能拿出来看,不能只留在口头上。

采集类业务部署前,最容易被忽略的七个问题

速度上不去的时候,应该先加机器还是先加出口 IP?先加 IP,而且在此之前先查节奏。判断标准很直白:如果机器的 CPU 和内存都很闲、带宽没跑满,而队列就是不下去,那瓶颈就不在机器上,加机器只会让同一批 IP 上的请求密度继续变高。正确顺序是:先确认目标站点的可接受频率并把单个 IP 的节奏压到那个范围内,再拿需要处理的总吞吐倒推需要多少个 IP,最后才讨论这些 IP 分摊到几台机器、每台机器要多大规格。把顺序颠倒过来,钱花得越多,被拒得越快。

出口 IP 是不是越多越好?不是。IP 数量应当是由吞吐需求倒推出来的结果,不是往多了要的资源。每多一个 IP 就多一份管理负担:它同样要守节奏、同样要退避、同样要记录行为。管理不善的大 IP 池比小而精的池子危险得多——池子里的 IP 一旦在多个目标上留下坏记录,恢复周期比重新申请长得多。另外一个常被忽略的前提:新增 IP 能不能申请、多少个在合理范围内、出网带宽按什么口径计算,这些都需要下单之前向资源提供方确认。

请求间隔到底设多少合适?这个问题没有通用答案,只有两条确定的规则。头一条:看目标站点自己的表述——协议文件里给出的指示、服务条款里写明的限制,这些是别人明确说出来的接受范围,优先于你自己的想象。第二条:在你自己的小规模范围内逐步试探,从明显保守的值出发、观察限流响应的占比有没有变化、稳定之后再小幅调整,并且始终保留余量。不要在对外发布的大任务上做这种试探。另外记住,间隔对应的均值不是唯一变量,突发形态往往先出问题,所以削峰比拉长平均间隔更优先。

被限流之后要不要立刻换个 IP 重试?不要。正确的顺序是先退避、再考虑重试、最后才考虑换出口。拿到明确的限流响应之后立刻换 IP 重试,会把一个 IP 上的局部问题迅速扩散到整个池子,让所有 IP 在同一个目标上的记录一起变差。而且此刻对方大概率正在承压,你换 IP 只是把压力换个形象再压一遍,性质上等于拒绝接受对方的反馈。如果响应里带了建议等待时间,照办;没有的话,按递增且带随机的方式退避,超出重试上限就丢进失败队列,留到下一轮。

多台机器共享一个出口,和每台机器有独立出口,差别到底在哪里?差别在两处。头一处是额度:共享出口意味着所有机器的请求在对方眼里都来自同一个身份,加机器只是在这个身份上加密度,并不能提升总量上限;独立出口才能让 “机器数量×每台的能力” 真正转化为吞吐。第二处是排错:共享出口一旦出现问题,全部机器的表现同时恶化,很难判断是自己的问题还是对方的问题;独立出口则可以让故障范围清晰可见。需要留意的是,独立出口不等于可以放开跑——每个出口同样要遵守同样的节奏。

采集任务的失败率到多少就要停下来看?不要盯绝对数字,盯三件事。盯变化趋势:同样的 Task,失败率连续几个周期往上走,说明有什么东西在恶化,这时候就该停下手查而不是继续加并发。盯分布:失败集中在个别 IP,那是 IP 层面的问题,隔离与冷却即可;失败在所有 IP 上均匀升高,那是目标站点或自己程序的问题,应当整体暂停。盯失败类型:可重试的错误(超时、服务端错误、明确的限流)通常意味着降速;不可重试的错误(请求格式、鉴权、资源不存在)占比上升,说明是你的程序有问题,这时候跑得越快错的越多。任何一项出现持续恶化,都应当先停下来,而不是先用加大并发去补进度。

这样做有没有法律风险?取决于你采什么、怎么采,本文不构成法律意见。能确定的是几条硬边界:只采集面向公开、且对方服务条款允许自动化访问的数据;遵守 robots 协议;不采集需要登录或授权才能看到的内容;不得规避或破坏对方设置的技术保护措施;不能因为你的采集影响对方的可用性;涉及个人信息的,要有合法基础并履行告知、必要情形下取得同意;受著作权保护的内容不得批量复制或转发。此外还要留意数据出境、数据留存期限、以及不同法域对同类行为的差异认定。业务规模上去之后,把这套规则写成书面的操作规范并让执行的人签字确认,是成本最低的一步。建议在实际运营前咨询专业人士。

本文关于出网链路设计的公开依据在哪里

本文涉及的协议层面的公开依据,主要来自以下公开标准文档:RFC 9309《Robots Exclusion Protocol》,它规定了 robots.txt 的格式与解析约定;RFC 6585及其引入的429 Too Many Requests状态码,这是服务端表达“请求频率过高”的通行方式;RFC 9110《HTTP Semantics》,其中规定了Retry-After响应头、503 等状态码的语义,这是退避逻辑应当尊重的依据;RFC 9113(HTTP/2)关于单条连接上多路复用的约定,这是连接复用降低连接数的规范基础;RFC 8446(TLS 1.3)中关于会话复用的机制,这是降低重复握手开销的依据。需要强调的是,这些标准是通信层的规范,它们不构成对你采集行为的授权,也不替代目标站点的服务条款和适用法律的判断。

本文讨论的采集场景限定于面向公开、且目标站点条款允许自动化访问的数据(自有站点巡检、公开目录监测、公开页面整理等);文中不提供任何形式的反爬对抗、身份证明规避或风控突破手段,涉及目标站点如何识别异常访问的部分仅作类别层面的说明,用于理解频率限制存在的原因。具体的频率阈值、单 IP 可持续速率、机器承载的并发连接数等参数,均应由目标站点规则与使用者自身的小规模试探确定,本文未给出任何推荐数值,也未引用任何个案数据。

在资源层面,一万网络深耕 IDC 19 年(成立于 2007 年),在多 IP 与出网带宽这类资源上可作为选型时的参考对象;附加 IP 数量、出网带宽的计费口径需要在下单之前向官方确认,具体价格需实时询价,不同节点、不同产品线之间的政策可能存在差异,官网未明示的内容请勿自行推定。产品信息以官网 https://www.idc10000.net/ 的实时展示为准。


上一篇:中国香港这一页 01 和 02 硬件字段完全相同,价格却从 1000 变成 2200:差的那 1200 藏在 CPU 型号后面

下一篇:物联网平台别按设备数配机器:连接数、上报频率、规则链放大和留存天数,这四个乘起来才是规格