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

Python使用ProcessPoolExecutor执行爬虫任务未保存全部文件是什么原因?

问题核心原因分析

你代码里的疏漏主要集中在多进程资源共享逻辑、变量作用域、异常捕获三个方面,具体如下:

  • 多进程下全局变量不共享,全局状态完全失效
    你定义的crawled去重列表、全局crawler实例、site、title这些变量都是主进程独立的,子进程启动时只会复制一份初始状态的副本,各子进程之间的修改完全不会同步:
    1. 去重逻辑完全无效,不同子进程可能重复处理同一个url
    2. 全局crawler实例的content、soup属性会被多个进程的爬取任务互相覆盖,你看到的请求成功日志可能根本不是当前url的结果,写文件时很容易出现内容错乱、写入空内容甚至报错终止
    3. 全局title变量不会随url变化,多个url生成的文件名完全一致,后续写入会直接覆盖之前的文件,这是文件丢失的最核心原因
  • 异常捕获不完整,任务失败无感知
    你只捕获了crawler.crawl执行的异常,后续文件名生成、文件写入、crawler.soup转字符串等步骤出现的异常(比如文件名包含非法字符、磁盘权限不足、soup对象为空等)完全没有捕获,子进程任务直接静默失败,你只会看到前面的请求成功日志,不知道后续写入已经崩了
  • 任务提交后未校验执行结果
    你提交任务后没有获取Future对象的执行结果,哪怕任务抛出异常终止,主进程也不会有任何提示,大量失败任务你完全感知不到
修复方案

按照以下步骤修改即可解决文件缺失问题:

  1. 主进程提前完成url去重,不要在子进程做去重逻辑
  2. 移除所有全局变量,把site、对应url的title作为参数传入crawl函数,每次执行crawl时新建独立的crawler实例,避免进程间状态互相干扰
  3. 文件名增加url的唯一标识(比如url的md5值),同时清洗title里的非法文件名字符,确保不会重名覆盖
  4. 完整捕获任务全流程异常,同时获取Future对象的执行结果,及时发现运行错误
修复后参考代码
import hashlib
from concurrent.futures import ProcessPoolExecutor
from datetime import date
from multiprocessing import freeze_support

# 替换为你自己的crawler类导入路径
from your_module import crawler

def crawl(current_url, site, title):
    try:
        # 每次任务新建独立的crawler实例,避免进程间状态冲突
        current_crawler = crawler()
        current_crawler.crawl(current_url, use_session=False, timeout=20)
        print(f'Fetched {current_url}: {len(current_crawler.content)} bytes')

        today = date.today()
        # 文件名加url的md5前缀确保唯一,同时清洗title里的非法文件名字符
        url_md5 = hashlib.md5(current_url.encode('utf-8')).hexdigest()[:8]
        safe_title = "".join([c for c in title if c not in r'\/:*?"<>|'])
        filename = f'{site} - {today.strftime("%Y-%m-%d")} {safe_title}_{url_md5}.html'
        
        with open(filename, 'w', encoding='utf-8') as html_out:
            html_out.write(f'{today.strftime("%Y-%m-%d")} @ {current_url}\n')
            html_out.write(str(current_crawler.soup))
        return True, current_url
    except Exception as e:
        print(f'处理{current_url}失败: {str(e)}')
        return False, current_url

if __name__ == '__main__':
    freeze_support()
    # 替换为你自己的url列表,每个元素为(url, 站点名, 页面对应标题)的元组
    list_of_urls = [
        ("https://example.com/1", "example站点", "页面1标题"),
        ("https://example.com/2", "example站点", "页面2标题"),
    ]
    # 主进程提前完成url去重
    seen_urls = set()
    unique_tasks = []
    for url, site, title in list_of_urls:
        if url not in seen_urls:
            seen_urls.add(url)
            unique_tasks.append((url, site, title))

    with ProcessPoolExecutor(max_workers=4) as worker:
        # 提交所有任务并保存future对象
        futures = [worker.submit(crawl, url, site, title) for url, site, title in unique_tasks]
        # 等待所有任务执行完成,统计结果
        success_count = 0
        for future in futures:
            success, url = future.result()
            if success:
                success_count += 1
        print(f"全部任务执行完成,成功{success_count}个,失败{len(futures)-success_count}个")

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 17:27:03