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

Python Scrapy2.5.0运行若干请求后无报错自动停止问题求助

问题根因分析
    1. 全局MongoDB连接非协程安全:Scrapy 2.5.0默认基于Twisted异步框架运行,你直接在顶层初始化的pymongo同步客户端,在并发请求处理时访问MongoDB会出现不可预知的错误,比如find_one()返回None,后续访问link['md5']抛出未捕获的异常,导致当前请求的parse方法中断,无法生成下一个爬取请求。你没看到报错大概率是日志级别设置过高,没有输出异常栈。
    1. 请求生成逻辑链路脆弱:你把新请求的生成完全耦合在单次响应的处理流程中,只要某一次响应处理中断没有yield新请求,后续的爬取链条就会断裂,等调度器中已有的请求全部处理完成后,Scrapy判定爬取结束,自动触发Spider closed (finished)。
    1. 可能存在重复请求过滤:Scrapy默认开启请求去重,如果你生成的请求URL和已爬取的URL重复,会被直接过滤不会加入调度队列,也会导致调度器提前变空。
修复方案

1. 替换MongoDB异步客户端,保证并发访问安全

把同步的pymongo替换为异步驱动motor,适配Scrapy的异步运行环境,避免并发查询出错。首先执行安装:
pip install motor
将MongoDB连接初始化逻辑放到爬虫的__init__方法中,避免使用非线程安全的全局连接。

2. 调整Scrapy基础配置

在项目settings.py中修改以下配置:

  • 调整日志级别到DEBUG,方便排查隐藏的异常:LOG_LEVEL = 'DEBUG'
  • 可根据实际需求调整并发数,避免并发过高导致MongoDB查询出错或被目标站点拦截:CONCURRENT_REQUESTS = 8
  • 如确认不存在重复爬取的需求,可临时关闭默认去重测试运行:DUPEFILTER_CLASS = 'scrapy.dupefilters.BaseDupeFilter'(生产环境建议保留去重规则避免无效请求)

3. 修复后的参考代码

import scrapy
import motor.motor_asyncio

class LibgenDlinkSpider(scrapy.Spider):
    name = "libgen_dlink"
    start_urls = ["https://booksmotion.com/main/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 初始化异步MongoDB客户端
        self.client = motor.motor_asyncio.AsyncIOMotorClient(
            "mongodb+srv://<user>:<Password>@booksmotionscraper.9c8us.mongodb.net/booksmotion?retryWrites=true&w=majority"
        )
        self.db = self.client.libgen.libgen2

    async def parse(self, response):
        # 异步查询MongoDB获取待爬MD5
        link = await self.db.find_one({})
        if not link:
            self.logger.info("所有待爬数据已处理完成,爬虫退出")
            return
        
        # 优先生成下一个请求,避免当前请求处理出错导致爬取断链
        next_url = f"https://booksmotion.com/main/{link['md5']}"
        yield scrapy.Request(next_url, callback=self.parse)
        
        # 处理当前响应解析逻辑
        try:
            dlink = response.css('#download > ul > li:nth-child(2) > a').attrib['href']
        except KeyError:
            dlink = 0
        
        yield {
            'md5': response.url.rsplit('/', 1)[-1],
            'dlink': dlink
        }

        # 数据处理完成后再删除数据库记录
        await self.db.delete_one({'_id': link['_id']})

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 18:48:00