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

CrawlerProcess结合APScheduler调度Scrapy爬虫遇报错求助

解决方案:基于CrawlerProcess+APScheduler的Scrapy定时爬虫管理器

核心问题分析

你遇到的两个错误是连锁的逻辑冲突:

  1. ValueError: signal only works in main thread:APScheduler默认在非主线程执行任务,而Scrapy默认的信号处理器仅允许在主线程安装。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 02:20:30