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

