CrawlerProcess结合APScheduler调度Scrapy爬虫遇报错求助
解决方案:基于CrawlerProcess+APScheduler的Scrapy定时爬虫管理器
核心问题分析
你遇到的两个错误是连锁的逻辑冲突:
ValueError: signal only works in main thread:APScheduler默认在非主线程执行任务,而Scrapy默认的信号处理器仅允许在主线程安装。ReactorNotRestartable:Twisted反应器是全局单例,无法重复启动,每次APScheduler任务调用CrawlerProcess.start()都会触发重复启动的冲突。
要优先使用CrawlerProcess,最直接的解决方式是将每个爬虫任务隔离到独立子进程中运行,让每个子进程拥有独立的Python解释器和Twisted反应器,从根源避免冲突。
实现代码
1. 爬虫管理器代码(crawl_manager.py)
import os import sys import multiprocessing from scrapy.crawler import CrawlerProcess from scrapy.utils.project import get_project_settings from apscheduler.schedulers.blocking import BlockingScheduler # 将项目根目录加入Python路径,确保能加载爬虫和配置 sys.path.append(os.path.dirname(os.path.abspath(__file__))) # 显式指定Scrapy配置模块(根据你的项目结构调整) os.environ['SCRAPY_SETTINGS_MODULE'] = 'scrapy.settings' def run_spider(): """在子进程中运行单个爬虫任务""" settings = get_project_settings() # 创建CrawlerProcess并禁用信号处理器(子进程无需主进程的信号监听) process = CrawlerProcess(settings, install_signal_handlers=False) process.crawl('myspider') # 替换为你的爬虫名称 process.start() # 阻塞直到爬虫完成 def scheduled_crawl_task(): """APScheduler调度的任务:启动子进程运行爬虫""" crawl_process = multiprocessing.Process(target=run_spider) crawl_process.start() crawl_process.join() if __name__ == '__main__': # 初始化阻塞式调度器 scheduler = BlockingScheduler(timezone='Asia/Shanghai') # 添加定时任务(示例:每30秒运行一次) scheduler.add_job(scheduled_crawl_task, 'interval', seconds=30) print("爬虫管理器启动,等待定时任务执行...") scheduler.start()
2. 统计数据写入SQLite(爬虫代码修改)
在你的myspider.py中添加爬虫关闭信号监听,自动将统计数据写入SQLite:
import scrapy import sqlite3 from scrapy import signals class MySpider(scrapy.Spider): name = 'myspider' # 你的爬虫起始URL、解析逻辑等... @classmethod def from_crawler(cls, crawler, *args, **kwargs): spider = super().from_crawler(crawler, *args, **kwargs) # 监听爬虫关闭信号 crawler.signals.connect(spider.on_spider_closed, signal=signals.spider_closed) return spider def on_spider_closed(self, spider): """爬虫结束时写入统计数据到SQLite""" stats = self.crawler.stats.get_stats() # 连接SQLite数据库(不存在则自动创建) conn = sqlite3.connect('crawl_stats.db') cursor = conn.cursor() # 创建统计数据表(首次运行自动创建) cursor.execute(''' CREATE TABLE IF NOT EXISTS crawl_stats ( id INTEGER PRIMARY KEY AUTOINCREMENT, spider_name TEXT NOT NULL, start_time TEXT NOT NULL, end_time TEXT NOT NULL, item_scraped_count INTEGER DEFAULT 0, response_received_count INTEGER DEFAULT 0, finish_reason TEXT ) ''') # 插入本次爬取的统计数据 cursor.execute(''' INSERT INTO crawl_stats (spider_name, start_time, end_time, item_scraped_count, response_received_count, finish_reason) VALUES (?, ?, ?, ?, ?, ?) ''', ( spider.name, stats['start_time'].isoformat(), stats['finish_time'].isoformat(), stats.get('item_scraped_count', 0), stats.get('response_received_count', 0), stats.get('finish_reason', 'unknown') )) conn.commit() conn.close() self.logger.info(f"统计数据已写入SQLite数据库:{stats}")
关键注意事项
- 子进程隔离:每个定时任务启动独立子进程运行爬虫,确保每个
CrawlerProcess都有专属的Twisted反应器,避免重启错误。 - 配置路径:确保
SCRAPY_SETTINGS_MODULE指向正确的项目配置文件,否则get_project_settings()无法加载Scrapy配置。 - 信号处理:
install_signal_handlers=False避免子进程与主进程的信号监听冲突,子进程的信号处理由自身管理。
内容的提问来源于stack exchange,提问作者DrSocket
相关产品推荐
相关产品推荐

