实操教程2026年9月29日阅读约 8 分钟

每个代理开多少线程?测出安全的代理并发数

每个代理开多少线程才安全?从低并发开始,错误率和响应时间都平稳时再翻倍,最后按 IP 限定并发。附已测试的 asyncio 与 httpx 代码。

每个代理开多少线程,取决于目标网站,而不取决于代理本身。先让每个 IP 同时跑两到四个请求;只要错误率接近零、响应时间保持平稳,就把并发数翻倍;两者中任何一个开始变化,就停在那一级。实际运行时,用那一级的一半左右。

这个答案要配上测试才有用,所以本文给你一个:一段简短的 Python 脚本,对你自己的目标网站逐级提高代理并发数,报告在哪一级开始撑不住。文章还会讲轮换的住宅 IP 和静态 IP 分别怎么影响这笔账,以及一个会悄悄让轮换失效的连接池细节。代码里的 HOST、PORT、USERNAME 和 PASSWORD,换成控制台里服务页面上的值。

每个代理开多少线程,为什么没有统一答案

有四样东西会限制你的并发,最低的那个说了算:

  • 目标网站对单个 IP 的限制。 大多数网站按地址计数。一个 IP 同时挂着十个请求,比十个请求分散在十个 IP 上显眼得多。
  • 目标网站的整体承受力。 反爬规则、一台小服务器、一个很慢的搜索页,都可能不管你用多少 IP 都顶回来。
  • 你自己的机器。 连接池、打开文件数上限、你的上行带宽,往往比代理先用完。代理超时怎么排查讲了从你这边看是什么样子。
  • 服务商的套餐。 有些服务商按套餐限制线程数或端口数。在我们这里,常见问题中“有端口费、线程费或月费吗?”的回答是:不收端口费,不收线程费,也没有月度门槛。

真正决定这个数字的是前两项,而只有目标网站能告诉你它们是多少。所以答案只能靠测量。

并发、速率和线程是三个不同的数

**并发**是同一时刻正在进行的请求数。速率是每秒发出多少个请求。两者由每个请求花的时间联系在一起:

速率 ≈ 并发数 ÷ 每个请求的秒数

同时挂着四个请求、每个半秒,大约就是每秒八个请求。如果网站慢到每页两秒,同样四个并发就降到每秒两个。你的代码设定的是并发,而网站通常限制的是速率;网站在压力下往往先变慢,然后才开始拒绝。所以下面的逐级测试会盯着响应时间。

线程是获得并发的一种方式。八个线程的线程池,就是同时八个请求:

from concurrent.futures import ThreadPoolExecutor

import requests

PROXY = "http://USERNAME:PASSWORD@HOST:PORT"
PROXIES = {"http": PROXY, "https": PROXY}
THREADS = 4


def status(url):
    try:
        return requests.get(url, proxies=PROXIES, timeout=(5, 30)).status_code
    except requests.RequestException as error:
        return type(error).__name__


urls = [f"https://TARGET_SITE/page/{n}" for n in range(1, 41)]
with ThreadPoolExecutor(max_workers=THREADS) as pool:
    for url, result in zip(urls, pool.map(status, urls)):
        print(result, url)

用 asyncio 的话,信号量(Semaphore)不用线程也能做同样的事,本文后面都用这种写法。

轮换住宅 IP 和静态 IP 的区别

住宅代理,Randomize IP 模式。 每个新连接都从不同的家庭地址出去,你的并发分散在许多出口上,没有哪个 IP 承担很多。按 IP 的限制基本不再是问题;限制变成网站的整体承受力和你按 GB 计费的流量。出口都是普通家庭网络,有快有慢,响应时间的波动会比静态 IP 大。

静态 ISP 或数据中心 IP。 同一个 IP 发出的每个请求都算在它头上。总并发由你租的这些 IP 分摊,所以真正该问的是“每个 IP 多少个”,总数再乘以 IP 数量。下单时可以选国家、地区和城市,结果也更容易前后对比。两者怎么选,见 ISP 还是数据中心代理?

再说一句实话,我们的诚信页面上写着:我们的住宅 IP 池比大厂小。在防守严密的目标上跑很高并发时,这个差距是真实存在的。为一个任务下单之前,先测一测。

连接池会让轮换失效

HTTP 客户端会复用连接。对 https 网站来说,复用的连接就是穿过代理的同一条隧道,也就是同一个出口 IP。用 httpx.AsyncClient 同时跑四个请求时,二十个请求大约走四条隧道,也就是四个出口各跑五个请求。用 Python 做代理轮换讲了 requests.Session 的同一个坑。

如果想每个请求都换一个新出口,就在客户端里关掉保持连接:

import httpx

PROXY = "http://USERNAME:PASSWORD@HOST:PORT"
limits = httpx.Limits(max_keepalive_connections=0)
client = httpx.AsyncClient(proxy=PROXY, limits=limits, timeout=httpx.Timeout(30.0, connect=5.0))

我们用一个会记录每条隧道的本地代理核对过:四个并发、二十个请求,默认设置下开了 4 条隧道,关掉保持连接后开了 20 条。代价是每个请求都要重新建立连接和 TLS 握手,会多花时间;如果每个工作协程固定一个出口对目标网站没问题,就保留默认设置。

用逐级加压测出你的数字

脚本在每个并发级别发一批请求,从 2 开始翻倍,每一级测三样东西:每秒页面数、响应时间中位数、失败比例。一出现问题的迹象就停下。

pip install httpx
import asyncio
import statistics
import time
from itertools import cycle, islice

import httpx

PROXY = "http://USERNAME:PASSWORD@HOST:PORT"
SAMPLE_URLS = [
    "https://TARGET_SITE/page/1",
    "https://TARGET_SITE/page/2",
    "https://TARGET_SITE/page/3",
]
START, CEILING = 2, 32
REQUESTS_PER_SLOT = 10
MAX_ERROR_RATE = 0.02
MAX_SLOWDOWN = 2.0
MIN_GAIN = 1.2
COOL_DOWN = 30


async def timed_get(client, gate, url):
    async with gate:
        start = time.monotonic()
        try:
            response = await client.get(url)
            status = response.status_code
        except httpx.HTTPError as error:
            status = type(error).__name__
        return status, time.monotonic() - start


async def run_level(concurrency):
    urls = list(islice(cycle(SAMPLE_URLS), concurrency * REQUESTS_PER_SLOT))
    gate = asyncio.Semaphore(concurrency)
    timeout = httpx.Timeout(30.0, connect=5.0)
    started = time.monotonic()
    async with httpx.AsyncClient(proxy=PROXY, timeout=timeout) as client:
        results = await asyncio.gather(*(timed_get(client, gate, url) for url in urls))
    elapsed = time.monotonic() - started
    failed = sum(1 for status, _ in results if status != 200)
    median = statistics.median(seconds for _, seconds in results)
    return len(results) / elapsed, median, failed / len(results)


async def ramp():
    safe, baseline, best_rate = None, None, 0.0
    level = START
    while level <= CEILING:
        rate, median, error_rate = await run_level(level)
        baseline = baseline or median
        print(f"{level:>3} at once: {rate:5.1f} req/s, median {median:.2f}s, {error_rate:.0%} failed")
        if error_rate > MAX_ERROR_RATE:
            print("  errors: the site is pushing back")
            break
        if median > baseline * MAX_SLOWDOWN:
            print("  responses slowed down: the site is queuing you")
            break
        if best_rate and rate < best_rate * MIN_GAIN:
            print("  no real gain in throughput: more workers only wait longer")
            break
        safe, best_rate = level, rate
        level *= 2
        await asyncio.sleep(COOL_DOWN)
    print(f"Highest level that held up: {safe}")


asyncio.run(ramp())

在 SAMPLE_URLS 里放三到十个目标网站上有代表性的页面。每一级按每个并发位发十个请求,所以第 8 级发 80 个,一直跑到第 32 级总共大约 600 个。把 CEILING 设成你实际最多会用的并发数;另外记住,在按 GB 计费的线路上,测试流量和其他流量一样计费。

怎么看结果

脚本会因为三种原因之一停下:

  • 错误率超过 2%。 429、403、超时和连接错误都算。网站在往回推。
  • 响应时间中位数比第一级翻了一倍。 网站或某个慢出口在让你排队,错误通常紧随其后。
  • 每秒页面数的提升不到 20%。 多出来的并发只是在等。这是最安静的一种:没有任何失败,也没有变快。

下面是对一台本地测试服务器跑的结果,它一次处理六个请求,同时超过十个就拒绝:

  2 at once:   8.0 req/s, median 0.24s, 0% failed
  4 at once:  16.4 req/s, median 0.23s, 0% failed
  8 at once:  25.8 req/s, median 0.28s, 0% failed
 16 at once:  68.5 req/s, median 0.05s, 60% failed
  errors: the site is pushing back
Highest level that held up: 8

注意第 16 级每秒页面数的跳升。拒绝来得很快,所以一个正在失败的级别看起来反而像最快的一级。脚本因此先检查错误率。

从结果到实际设置

  • 按撑住的那一级的一半到四分之三来跑。 测试很短,而你的任务要跑好几个小时,负载持续下去,网站也会收紧。
  • 用静态 IP 时,如果测试只用了其中一个 IP,结果就是每个 IP 的数字,给每个 IP 分配各自的份额。
  • 在并发之上再加速率限制。 并发限制的是同时进行的请求数,页面很快时它管不住速度。429 Too Many Requests 怎么解决里有一个按主机限速的限速器。
  • 情况变了就重新测:换了目标网站、换了线路,或者 429 连着一周在增加。

把任务分摊到多个静态 IP 上

有一组静态 IP 时,给每个 IP 固定数量的工作协程,这样没有哪个 IP 会超过自己的份额。所有工作协程从同一个队列里取下一个网址:

import asyncio
from collections import Counter

import httpx

PROXIES = [
    "http://USERNAME:PASSWORD@HOST_1:PORT_1",
    "http://USERNAME:PASSWORD@HOST_2:PORT_2",
    "http://USERNAME:PASSWORD@HOST_3:PORT_3",
]
PER_IP = 2


async def worker(proxy, queue, results):
    exit_label = proxy.rsplit("@", 1)[1]
    timeout = httpx.Timeout(30.0, connect=5.0)
    async with httpx.AsyncClient(proxy=proxy, timeout=timeout) as client:
        while not queue.empty():
            url = queue.get_nowait()
            try:
                response = await client.get(url)
                results.append((exit_label, url, response.status_code))
            except httpx.HTTPError as error:
                results.append((exit_label, url, type(error).__name__))


async def main(urls):
    queue = asyncio.Queue()
    for url in urls:
        queue.put_nowait(url)
    results = []
    await asyncio.gather(*(
        worker(proxy, queue, results) for proxy in PROXIES for _ in range(PER_IP)
    ))
    return results


urls = [f"https://TARGET_SITE/page/{n}" for n in range(1, 61)]
results = asyncio.run(main(urls))
print(Counter((label, status) for label, _, status in results))

三个 IP、PER_IP = 2,总共同时六个请求,每个地址从不超过两个。我们对一台会统计在途请求数的本地服务器跑过,峰值正好是六。想更快就加 IP;提高 PER_IP 只会让每个地址更显眼。

快速问答

住宅代理每个开多少线程? 不存在按 IP 的数字可找,因为每个连接都换一个新出口。用脚本逐级提高总并发,盯着 429 和变慢的响应。

每个数据中心或 ISP IP 开多少线程? 从每个 IP 两个开始往上测。很多网站接受几个;繁忙或防守严的网站只接受一个。

ProxyPanda 限制线程数吗? 我们的价格里不收端口费,不收线程费,也没有月度门槛。实际的上限在目标网站那边。

线程越多,爬虫越快吗? 只到网站或你的机器成为瓶颈为止。过了那个点,多出来的线程只会增加等待,然后是错误。

先做一次小测试

下更大的单之前,先通过一项服务对你的目标网站跑一遍逐级测试。各条线路的价格见价格页面;如果测出来的数字看着不对劲,带上目标网站和所用线路,发到 Discord 一起看看。

还有后续问题?

去 Discord 问。答案还能帮到下一个读到这个帖子的人。

加入 Discorddiscord.gg/proxypanda
$5 起步去 Discord 问