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

Scrapy批量处理2k个URL遇ReactorNotRestartable错误求解决方案

解决Scrapy中ReactorNotRestartable错误,批量处理URL的方案

问题背景

数据库存储约2000个待爬取URL,原代码计划每次处理10个,但仅第一次循环正常执行,第二次触发twisted.internet.error.ReactorNotRestartable错误。

原代码:

from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings

URLs = crawler_table.find(crawl_timestamp=None)
settings = get_project_settings()
for i in range(len(URLs) // 10):
    process = CrawlerProcess(settings)

    limit = 10
    kount = 0

    for crawl in crawler_table.find(crawl_timestamp=None):
        if kount < limit:
            kount += 1
            process.crawl(
                MySpider,
                start_urls=[crawl['crawl_url']]
           )
    process = CrawlerProcess(settings)
    process.start()

错误日志:

File "C:\Program Files\Python310\lib\site-packages\scrapy\crawler.py", line 327, in start
    reactor.run(installSignalHandlers=False)  # blocking call
File "C:\Program Files\Python310\lib\site-packages\twisted\internet\base.py", line 1314, in run
    self.startRunning(installSignalHandlers=installSignalHandlers)
File "C:\Program Files\Python310\lib\site-packages\twisted\internet\base.py", line 1296, in startRunning
    ReactorBase.startRunning(cast(ReactorBase, self))
File "C:\Program Files\Python310\lib\site-packages\twisted\internet\base.py", line 840, in startRunning
    raise error.ReactorNotRestartable()
twisted.internet.error.ReactorNotRestartable

错误原因

Scrapy基于Twisted框架,其核心的反应器(reactor)是不可重启的——一旦调用process.start()启动并停止反应器后,同一个进程内无法再次启动它。原代码循环中反复创建CrawlerProcess并调用start(),第二次循环时反应器已被耗尽,因此触发错误。

解决方案

方案1:一次性调度所有批次(推荐)

无需循环创建CrawlerProcess,一次性将所有URL分批,把每批任务调度到同一个进程中,只启动一次反应器即可完成所有任务。

修改后代码:

from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings

# 提取所有待处理URL
unprocessed_urls = [item['crawl_url'] for item in crawler_table.find(crawl_timestamp=None)]
batch_size = 10
settings = get_project_settings()
process = CrawlerProcess(settings)

# 分批创建爬虫任务
for i in range(0, len(unprocessed_urls), batch_size):
    batch_urls = unprocessed_urls[i:i+batch_size]
    process.crawl(MySpider, start_urls=batch_urls)

# 启动一次反应器,处理所有任务
process.start()

方案2:子进程独立处理每批任务

如果必须分批启动独立进程,可使用subprocess模块为每批URL启动单独的爬虫进程,每个进程拥有独立的反应器,避免冲突。

主调度代码:

import subprocess
import sys

unprocessed_urls = [item['crawl_url'] for item in crawler_table.find(crawl_timestamp=None)]
batch_size = 10

for i in range(0, len(unprocessed_urls), batch_size):
    batch_urls = unprocessed_urls[i:i+batch_size]
    # 将批次URL作为参数传递给独立爬虫脚本
    subprocess.run([
        sys.executable, 'spider_batch.py',
        '--batch-urls', ','.join(batch_urls)
    ])

对应的独立爬虫脚本spider_batch.py:

import sys
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings

class MySpider(scrapy.Spider):
    name = 'my_spider'
    def __init__(self, start_urls=None, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if start_urls:
            self.start_urls = start_urls.split(',')

if __name__ == '__main__':
    settings = get_project_settings()
    process = CrawlerProcess(settings)
    # 解析传入的批次URL
    batch_urls = sys.argv[sys.argv.index('--batch-urls') + 1]
    process.crawl(MySpider, start_urls=batch_urls)
    process.start()

方案3:使用CrawlerRunner手动控制反应器

CrawlerRunner不会自动启动反应器,可手动控制任务调度和反应器生命周期,适合需要在爬虫完成后执行后续操作的场景。

示例代码:

from scrapy.crawler import CrawlerRunner
from scrapy.utils.project import get_project_settings
from twisted.internet import reactor

unprocessed_urls = [item['crawl_url'] for item in crawler_table.find(crawl_timestamp=None)]
batch_size = 10
settings = get_project_settings()
runner = CrawlerRunner(settings)

# 定义单批次爬取函数
def crawl_batch(batch_urls):
    return runner.crawl(MySpider, start_urls=batch_urls)

# 依次调度所有批次任务
deferred = crawl_batch(unprocessed_urls[0:batch_size])
for i in range(batch_size, len(unprocessed_urls), batch_size):
    batch_urls = unprocessed_urls[i:i+batch_size]
    deferred.addCallback(lambda _: crawl_batch(batch_urls))

# 所有任务完成后停止反应器
deferred.addCallback(lambda _: reactor.stop())

# 启动反应器
reactor.run()

关键注意事项

  • 无论采用哪种方案,必须在URL处理完成后更新crawl_timestamp字段,否则后续批次会重复爬取已处理的URL。
  • 方案1是最高效的实现方式,无需创建多进程,所有任务在同一个反应器中调度执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 15:55:01