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

multiprocessing.Pool.map批量爬取链接触发MaybeEncodingError报错

问题根因

你遇到的MaybeEncodingError: RecursionError('maximum recursion depth exceeded while pickling an object')以及之前多套爬取方案不稳定,核心是3个问题:

  • Jupyter Notebook环境下,定义在单元格内的函数、引用了全局作用域非基础类型对象(requests.Session、BeautifulSoup实例、大列表等)的函数,在被multiprocessing序列化传输时会递归回溯整个作用域链,超过Python默认递归深度直接报错;嵌套进程池的写法会触发daemon进程无法创建子进程的限制,进一步加重对象引用循环问题。
  • 之前的单线程、asyncio、线程池方案没有做异常隔离、请求重试、限流落盘,单条链接报错就会中断整个任务,请求频率过高触发目标站连接拒绝、TCP连接泄漏后自然跑不到全量完成。
  • 多进程场景下如果把主进程初始化的Session、解析器对象传给子进程,这类带锁、连接池的复杂对象无法被正常序列化,也会触发pickle递归错误。
修复步骤

1. 先解决多进程序列化报错

在Notebook最开头(重启内核后第一个运行的单元格)执行以下代码,强制进程启动模式为spawn,避开fork模式的作用域引用bug:

import multiprocessing
multiprocessing.set_start_method('spawn', force=True)

注意:这段代码必须放在所有爬虫逻辑、进程池初始化代码之前,不能重复执行。

2. 改造爬取Worker为纯函数

Worker函数不要引用任何Notebook全局作用域的可变对象,不要嵌套进程池,每个子进程独立初始化请求会话,返回值只使用字符串、字典、数字这类基础Python类型,绝对不要返回Response、BeautifulSoup这类带复杂引用的实例,参考实现:

import requests
from bs4 import BeautifulSoup
import time
import random

def crawl_single_link(url: str) -> dict:
    # 子进程内部独立初始化Session,禁止从主进程传入Session对象
    session = requests.Session()
    session.headers.update({
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
    })
    max_retry = 3
    for retry_cnt in range(max_retry):
        try:
            # 随机限流,避免请求过密被站点封禁
            time.sleep(random.uniform(0.3, 1.8))
            resp = session.get(url, timeout=15)
            resp.raise_for_status()
            resp.encoding = resp.apparent_encoding
            soup = BeautifulSoup(resp.text, "html.parser")

            # 以下替换为你自己的页面解析逻辑
            meeting_name = soup.select_one("h1.title").get_text(strip=True) if soup.select_one("h1.title") else ""
            address = soup.select_one(".address").get_text(strip=True) if soup.select_one(".address") else ""
            table_data = {}
            for row in soup.select("table.detail tr"):
                cells = row.select("td, th")
                if len(cells) >= 2:
                    table_data[cells[0].get_text(strip=True)] = cells[1].get_text(strip=True)

            session.close()
            return {
                "url": url,
                "meeting_name": meeting_name,
                "address": address,
                "table_data": str(table_data),
                "status": "success"
            }
        except Exception as e:
            if retry_cnt == max_retry - 1:
                session.close()
                return {"url": url, "status": "failed", "err_msg": str(e)}
            time.sleep(2 * (retry_cnt + 1))

如果改完还是报序列化错误,直接把这个Worker函数存到同目录下的crawl_utils.py文件里,在Notebook里用from crawl_utils import crawl_single_link导入,彻底解决顶层作用域序列化问题。

3. 替换进程池调用逻辑,边爬边落盘

不要用Pool.map等待全量任务完成再返回结果,用imap_unordered按爬取完成顺序实时写入CSV,避免中途崩溃丢失几小时的爬取进度,进程数不要开太高,4-8个足够,进程过多反而会触发站点限流:

import csv
from multiprocessing import Pool

if __name__ == "__main__":
    # 替换为你的33488条链接列表
    with open("target_urls.txt", "r", encoding="utf-8") as f:
        url_list = [line.strip() for line in f if line.strip()]
    
    # 边爬边写,实时刷盘
    with Pool(processes=6) as pool, open("meeting_result.csv", "w", encoding="utf-8-sig", newline="") as csvf:
        writer = csv.DictWriter(csvf, fieldnames=["url", "meeting_name", "address", "table_data", "status", "err_msg"])
        writer.writeheader()
        # chunksize设为16-32,减少进程间通信开销
        for res in pool.imap_unordered(crawl_single_link, url_list, chunksize=16):
            writer.writerow(res)
            csvf.flush()
稳定性补充说明
  • 爬取过程中如果出现大量连接失败,把进程数调低到4,把随机延时范围调到1-3秒,避免触发目标站IP封禁。
  • 不需要用嵌套进程池、itertools做复杂的结果包装,越简单的纯函数逻辑越不容易触发序列化错误。
  • 爬取完成后单独筛选CSV里status为failed的链接二次补爬即可,不需要每次都从头跑全量。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 12:03:23