You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python3下如何高效发送10万次Web请求并优化URL状态码检测?

优化10万条同域名URL状态码检测的方案

针对你用线程+PyCurl处理10万条同域名URL时前期准备耗时久的问题,我整理了几个针对性的优化方向,结合你的代码给出具体改进方案:

一、核心问题分析

你当前的代码有两个关键瓶颈导致前期/整体效率低下:

  • 无限制创建线程:10万条URL会瞬间创建10万个线程,系统线程调度、资源分配的开销极大,前期线程创建阶段会占用大量时间,甚至导致系统卡顿。
  • 重复创建销毁PyCurl实例:Curl对象的初始化有不小的开销,每请求一次就新建、关闭一次,会累积大量额外耗时。

二、针对性优化方案

1. 用线程池控制并发+复用线程

用concurrent.futures.ThreadPoolExecutor代替手动创建线程,它会提前创建固定数量的线程并复用,避免大量线程创建/销毁的开销,同时能合理控制并发数(避免超出系统承载)。

2. 线程级复用PyCurl对象

通过threading.local()实现线程本地存储,让每个线程复用一个Curl实例,不用每次请求都重新初始化,大幅减少初始化开销。

3. 进阶:PyCurl MultiHandle(单线程异步)

PyCurl的MultiHandle支持单线程处理多个异步请求,比线程池更高效——完全没有线程切换的开销,再结合同域名TCP连接复用,能把请求效率拉到最高。


三、改进后的代码示例

方案1:线程池+复用Curl对象(易上手,兼容原有逻辑)

import pycurl
import certifi
from concurrent.futures import ThreadPoolExecutor
import threading

# 线程本地存储:每个线程复用一个Curl实例
thread_local = threading.local()

def get_curl():
    if not hasattr(thread_local, 'curl'):
        curl = pycurl.Curl()
        curl.setopt(pycurl.CAINFO, certifi.where())
        curl.setopt(pycurl.WRITEFUNCTION, lambda x: None)
        curl.setopt(pycurl.CONNECTTIMEOUT, 5)
        # 开启TCP连接复用,同域名请求直接复用已建立的连接
        curl.setopt(pycurl.TCP_KEEPALIVE, 1)
        curl.setopt(pycurl.TCP_KEEPIDLE, 30)
        curl.setopt(pycurl.TCP_KEEPINTVL, 10)
        thread_local.curl = curl
    return thread_local.curl

def req(url, counter):
    curl = get_curl()
    try:
        curl.setopt(pycurl.URL, url)
        curl.perform()
        print(f"Requests: {counter} | URL: {url} | Status Code: {curl.getinfo(pycurl.HTTP_CODE)}")
    except pycurl.error as e:
        print(f"Requests: {counter} | URL: {url} | Error: {e}")

if __name__ == "__main__":
    # 用生成器逐行读取URL,避免一次性加载10万条到内存
    def load_urls():
        with open("urllist.txt") as f:
            for line in f:
                yield line.strip()

    urls = load_urls()
    # 并发数根据系统配置调整,同域名建议100-200(避免触发目标站限流)
    max_workers = 150
    counter = 0

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = []
        for url in urls:
            counter += 1
            futures.append(executor.submit(req, url, counter))
        # 等待所有请求完成
        for future in futures:
            future.result()

    # 最后清理线程内的Curl实例
    if hasattr(thread_local, 'curl'):
        thread_local.curl.close()

方案2:PyCurl MultiHandle(单线程异步,效率最高)

这个方案完全避免线程开销,适合处理大量同域名请求:

import pycurl
import certifi

def check_urls():
    multi = pycurl.CurlMulti()
    active_handles = []
    max_concurrent = 200  # 同时处理的请求数
    counter = 0

    # 读取URL列表(如果文件极大,建议用生成器逐行处理)
    with open("urllist.txt") as f:
        urls = [line.strip() for line in f.read().splitlines()]
    remaining_urls = urls.copy()

    def add_request(url):
        nonlocal counter
        counter += 1
        curl = pycurl.Curl()
        curl.setopt(pycurl.CAINFO, certifi.where())
        curl.setopt(pycurl.WRITEFUNCTION, lambda x: None)
        curl.setopt(pycurl.CONNECTTIMEOUT, 5)
        curl.setopt(pycurl.URL, url)
        # 开启TCP连接复用
        curl.setopt(pycurl.TCP_KEEPALIVE, 1)
        # 存储额外信息,方便后续打印
        curl.url = url
        curl.counter = counter
        multi.add_handle(curl)
        active_handles.append(curl)

    # 先填充一批初始请求
    for _ in range(min(max_concurrent, len(remaining_urls))):
        add_request(remaining_urls.pop(0))

    # 启动异步处理循环
    while active_handles:
        # 执行请求
        ret, num_handles = multi.perform()
        if ret != pycurl.E_CALL_MULTI_PERFORM:
            break

        # 等待活动连接就绪
        while True:
            ret, num_handles = multi.select(1.0)
            if ret != pycurl.E_CALL_MULTI_PERFORM:
                break

        # 处理完成的请求
        while True:
            queued, ok_list, err_list = multi.info_read()
            # 处理成功的请求
            for curl in ok_list:
                print(f"Requests: {curl.counter} | URL: {curl.url} | Status Code: {curl.getinfo(pycurl.HTTP_CODE)}")
                multi.remove_handle(curl)
                active_handles.remove(curl)
                curl.close()
                # 添加下一个请求(如果还有剩余)
                if remaining_urls:
                    add_request(remaining_urls.pop(0))
            # 处理失败的请求
            for curl, errno, errmsg in err_list:
                print(f"Requests: {curl.counter} | URL: {curl.url} | Error: {errno} - {errmsg}")
                multi.remove_handle(curl)
                active_handles.remove(curl)
                curl.close()
                if remaining_urls:
                    add_request(remaining_urls.pop(0))
            if queued == 0:
                break

if __name__ == "__main__":
    check_urls()

四、额外优化建议

  • TCP连接复用:两个方案都开启了TCP_KEEPALIVE,同域名请求会复用已建立的TCP连接,大幅减少握手时间,这对同域名大量请求的提升非常明显。
  • 并发数控制:不要设置过高的并发数,避免触发目标网站的限流机制,同时也避免本地系统资源耗尽。同域名建议控制在100-200之间。
  • 结果写入文件:如果10万条请求的打印会拖慢速度,可以把结果写入本地文件而不是打印到控制台,进一步提升效率。
  • URL预校验:提前过滤掉格式无效的URL,避免无效请求浪费资源。

内容的提问来源于stack exchange,提问作者神瀬来未

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:04:55