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

Python多进程/列表推导式网页爬取代码速度过慢如何优化

问题原因说明

你之前尝试的4种方案没有提速效果,核心原因是对任务类型和工具的适用场景判断错误:

  • 普通for循环和列表推导式本质都是单线程同步执行,额外的函数调用还会带来开销,速度没有提升属于正常现象
  • 你使用的multiprocessing.dummy.ThreadPool是线程池,Python GIL锁会导致CPU密集型任务无法通过多线程并行,DOM解析属于纯CPU计算场景,线程池不仅无法提速,线程切换还会增加额外开销
  • asyncio方案存在两个问题:一是你写的异步函数内没有任何可等待的IO操作,全是同步CPU计算逻辑,即使不报错也不会有提速效果;二是RuntimeError报错是因为你在已经运行事件循环的环境(如Jupyter、IPython控制台)中调用了asyncio.run(),但这个问题和性能无关,不需要花时间解决。
可行提速方案(按效果优先级排序)

1. 最低成本优化:替换解析器+减少重复查询开销

这一步改完通常就能把3分钟的处理时间压缩到10秒以内,不需要改整体逻辑:

  • 首先把BS4的默认解析器html.parser换成lxml,解析速度直接提升3-5倍,先安装依赖:pip install lxml,初始化soup时指定解析器:soup = BeautifulSoup(html_content, 'lxml')
  • 你当前代码在循环中反复调用select_one,每次调用都会执行CSS选择器匹配逻辑,开销极大,可以改为直接遍历tr节点的子元素,避免重复查询。优化后的代码示例:
ROWS = []
# 提前一次性取出所有目标tr,避免循环内重复查询DOM
target_trs = soup.select('tr[id^=mix]')
for tr in target_trs:
    dt = ""
    H_team = ""
    A_team = ""
    # 直接遍历tr的后代节点,按class匹配目标内容,不需要反复跑CSS选择器
    for node in tr.descendants:
        if node.name != "td":
            continue
        node_cls = node.get("class", [])
        if "h" in node_cls:
            dt = node.text
        elif "Home" in node_cls:
            H_team = node.text.strip()
        elif "Away" in node_cls:
            A_team = node.text.strip()
    ROWS.append([dt, H_team, A_team])

2. 多进程并行优化(适合超大数据量场景)

CPU密集型任务要绕过GIL实现并行,必须使用真正的多进程,注意不能直接把BS4元素对象传给子进程,跨进程序列化对象的开销会抵消并行收益,正确做法是把tr节点转成原始HTML字符串再分块传给子进程处理:

import os
from multiprocessing import Pool
from bs4 import BeautifulSoup

def parse_tr_chunk(tr_str_list):
    chunk_res = []
    for tr_str in tr_str_list:
        tr = BeautifulSoup(tr_str, "lxml").tr
        dt = tr.select_one('.h').text
        H_team = tr.select_one('td.Home').text.strip()
        A_team = tr.select_one('td.Away').text.strip()
        chunk_res.append([dt, H_team, A_team])
    return chunk_res

if __name__ == "__main__":
    # 主进程先读入HTML生成soup
    with open("target.html", "r", encoding="utf-8") as f:
        soup = BeautifulSoup(f.read(), "lxml")
    # 把所有tr转成原始字符串,不要传BS4对象
    all_tr_str = [str(tr) for tr in soup.select('tr[id^=mix]')]
    # 按CPU核心数拆分任务块
    core_num = os.cpu_count()
    chunk_size = len(all_tr_str) // core_num
    tr_chunks = [all_tr_str[i:i+chunk_size] for i in range(0, len(all_tr_str), chunk_size)]
    # 多进程处理
    with Pool(core_num) as pool:
        part_results = pool.map(parse_tr_chunk, tr_chunks)
    # 合并所有结果
    ROWS = []
    for part in part_results:
        ROWS.extend(part)

3. 极致速度优化:替换解析库

如果追求毫秒级解析速度,可以直接放弃BeautifulSoup,使用lxml的原生XPath接口解析,速度是BS4+lxml的5-10倍,3000行tr的解析基本可以在100毫秒内完成:

from lxml import etree

with open("target.html", "r", encoding="utf-8") as f:
    tree = etree.HTML(f.read())

ROWS = []
# 用XPath一次性匹配所有目标tr
for tr in tree.xpath('//tr[starts-with(@id, "mix")]'):
    dt = tr.xpath('.//*[contains(@class, "h")]/text()')[0]
    H_team = tr.xpath('./td[contains(@class, "Home")]/text()')[0].strip()
    A_team = tr.xpath('./td[contains(@class, "Away")]/text()')[0].strip()
    ROWS.append([dt, H_team, A_team])

注意:异步IO只适合网络请求、文件读写这类IO密集型场景,本地DOM解析是纯CPU计算,用异步IO没有任何提速效果,不需要在这个场景下尝试异步方案。

内容的提问来源于stack exchange,提问作者Khaled Koubaa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 23:57:19