无需 KYC —— 无需身份证、自拍、文件证明。支持加密货币支付:BTC, USDT, USDC, ETH, SOL, LTC
语言: 简体中文
开始使用

文档 · 并发 · 吞吐量 · 礼貌

通过住宅代理实现并发和吞吐量

网关按 GB 计费,而不是按线程计费,因此您的实际限制是每个目标网站。根据所需吞吐量调整并发量,按每个主机设定上限,并通过响应情况决定何时减速。

最后更新: · 3 分钟阅读

快速回答

我可以通过住宅代理运行多少并发请求?

根据每个目标网站的承受能力:CrawlRelay 按住宅代理流量 GB 计费(50 GB 起US$0.50/GB),而非按线程或连接计费。使用 Little 定律计算并发请求数,飞行中的请求 = 每秒页面数 × 每页面秒数,然后为每个主机设定上限,初始值为2到4。遇到429或503响应时降低并发。支持加密货币支付,无需 KYC。

并发一览

计费方式按流量 GB 计费;线程和连接不计费
规模规则飞行中的请求 = 吞吐量 × 延迟(Little 定律)
每主机起始值2–4 并行请求;小型网站每请求加延迟
减速信号429、503响应、响应时间上升、挑战页面
带宽计算每秒页面数 × 每页面字节数 × 3,600 = 每小时字节数
住宅代理中位响应时间代理网络 0.93 秒,真实目标 3.21 秒(Proxyway 2026)

如何根据所需吞吐量确定并发规模?

使用 Little 定律:飞行中请求数等于目标速率乘以每个请求所需时间。Proxyway 2026 年代理市场研究显示,住宅代理访问真实目标的中位请求时间为 3.21 秒,因此规划时以 2 到 4 秒计算真实网站,而非仅考虑网络的亚秒级响应。

目标每请求时间飞行中请求数
每秒 5 页2 秒10
每秒 20 页2 秒40
每秒 20 页4 秒80
每秒 100 页3 秒300

然后将请求分布于不同主机。分布到 200 个站点上的 40 个飞行请求是温和的;40 个请求集中在一个小站点则是其未授权的负载测试。

该吞吐量使用多少带宽?

将每秒页面数乘以每页面字节数。以每秒 20 个 HTML 页面,每页 50 KB 计算,爬取速度为每秒 1 MB 即每小时 3.6 GB,约每日 86 GB。若每渲染页为 2 MB,则同速率带宽为每秒 40 MB。

用结果乘以计划中每 GB 价格,US$0.50/GB 起始 50 GB 为 CrawlRelay,即可算出每小时费用。带宽计算器指南 提供按页面类型的典型页面大小。

每主机限额应从多少开始?

目标每主机并行请求数请求间延迟备注
小型网站(博客、本地商店)12–5 秒若存在则遵守 Crawl-delay 中的 robots.txt
中等网站2–40.5–2 秒监控响应时间;当响应时间翻倍时降低请求速度
拥有 CDN 的大型平台4–16自动节流或自适应仅在 429 和 5xx 率保持平稳时增加速度
公共 API文档中说明的限制如文档所述使用服务商提供的 API 密钥和限制

如何自动调整并发数?

使用加法增加和乘法减少,这也是 TCP 处理拥塞的规则:在一系列正常响应后加一个槽位,遇到 429 或 503 时减半限制。以下限速器为每个主机维护一个限制,对该主机的每个请求暂停 Retry-After 延迟,重试一次,并且绝不超过您设置的上限。网络错误作为返回值出现,所以一次超时不会中断整个批次。

Python asyncio:配合 httpx 的自适应限制
import asyncio
import random
from urllib.parse import urlsplit

import httpx

PROXY = "http://USERNAME-country-us:[email protected]:7777"


class AdaptiveLimit:
    """One per host: +1 slot after 20 clean responses, halve on 429/503, pause for Retry-After."""

    def __init__(self, start=2, low=1, high=16):
        self.limit, self.low, self.high = start, low, high
        self.in_flight, self.clean = 0, 0
        self.pause_until = 0.0
        self.cond = asyncio.Condition()

    async def __aenter__(self):
        loop = asyncio.get_running_loop()
        while True:
            async with self.cond:
                await self.cond.wait_for(lambda: self.in_flight < self.limit)
                delay = self.pause_until - loop.time()
                if delay <= 0:
                    self.in_flight += 1
                    return self
            await asyncio.sleep(delay)  # the host asked for a pause: wait outside the lock

    async def __aexit__(self, *exc):
        async with self.cond:
            self.in_flight -= 1
            self.cond.notify_all()

    def record(self, status, retry_after=None):
        if status in (429, 503):
            self.limit, self.clean = max(self.low, self.limit // 2), 0
            wait = int(retry_after) if retry_after and retry_after.isdigit() else 5 + random.random()
            now = asyncio.get_running_loop().time()
            self.pause_until = max(self.pause_until, now + wait)
        elif status < 400:
            self.clean += 1
            if self.clean >= 20 and self.limit < self.high:
                self.limit, self.clean = self.limit + 1, 0


gates = {}


def gate_for(url):
    host = urlsplit(url).hostname
    if host not in gates:
        gates[host] = AdaptiveLimit()
    return gates[host]


async def fetch(client, url, attempts=2):
    gate = gate_for(url)
    status = None
    for _ in range(attempts):
        try:
            async with gate:
                r = await client.get(url)
        except httpx.HTTPError as exc:
            return "error: %s" % type(exc).__name__
        status = r.status_code
        gate.record(status, r.headers.get("Retry-After"))
        if status not in (429, 503):
            break  # the retry, if any, waits for the host's pause in __aenter__
    return status


async def main(urls):
    timeout = httpx.Timeout(30.0, connect=10.0)
    async with httpx.AsyncClient(proxy=PROXY, timeout=timeout) as client:
        print(await asyncio.gather(*(fetch(client, u) for u in urls)))
    print({host: gate.limit for host, gate in gates.items()})


asyncio.run(main(["https://example.com/"] * 10))

代理爬取除了目标主机外,什么会导致速度变慢?

  • TLS 握手。 每次请求建立一个新连接,且每次请求都需要一个新 IP,这会增加与目标网站的握手次数。当 IP 多样性不重要时,对同一网站的多页请求保持粘性会话和长连接可以避免多次握手。
  • 慢出口。 住宅代理和 4G 移动代理是真实的家庭和移动网络链接。使用超时设置并在新连接上重试,而不是等待。
  • 范围太窄。 一个小城市或单个 ASN 的出口比整个国家少。仅在数据发生变化的区域精准投放。
  • 页面渲染。 无头浏览器消耗的 CPU、内存和流量远高于 HTTP 客户端。仅渲染需要 JavaScript 的页面。
  • DNS。 通过 socks5h:// 或 HTTP 端口,DNS 解析发生在出口处;无需为目标主机缓存本地解析器。

并发相关问题

CrawlRelay 是否限制线程数或连接数?

计费按流量 GB 计算,不按线程、连接或请求计费,发布的套餐未设线程数限制。实际限制取决于每个目标主机的容忍度和您自身设备性能。如果计划大规模爬取,请先写信给 [email protected],以便我们核查您的目标范围。

为什么我增加线程后爬取速度反而变慢?

通常是因为目标网站开始限速:响应时间延长,出现 429 或 503 返回码,重试增加负载。另一常见原因是自己设备 CPU 或连接资源不足,尤其是使用无头浏览器时。增加线程前请先测量每个主机的延迟和错误率。

每个请求一个连接会比长连接慢吗?

会的,每次请求增加一个 TCP 和 TLS 握手,大约延迟几百毫秒,尤其是通过住宅代理出口时。这是拥有每次请求新 IP 的代价。在 IP 多样性不重要的情况下,对同一网站的多页请求使用粘性会话和长连接更快且节省流量。

在并发爬虫中如何遵守 robots.txt 的 crawl-delay?

每个主机解析 robots.txt 一次,缓存最长 24 小时,符合 RFC 9309 建议。将 Crawl-delay 值转换为单线程请求之间的最小间隔。Python 的 urllib.robotparser 可通过 crawl_delay() 访问该数据。

按流量 GB 计费实现扩展

US$0.50/GB 从 50 GB 到 1 TB,流量永不过期。

从 US$0.50/GB 开始 · 无需 KYC

开始使用