如何在Scrapy爬虫爬完所有指定URL后手动停止?解决重启错误
解决Scrapy爬虫爬完URL后出现
ReactorNotRestartable错误的方案 首先,咱们先拆解下你遇到的问题核心:twisted.internet.error.ReactorNotRestartable本质是Twisted的反应器(reactor)是单例实例,一旦停止就无法再次启动。而你的代码里可能存在重复启动reactor的情况,再加上Spider的继承逻辑有小问题,导致爬取流程异常,最终触发了这个错误。
第一步:修正Spider的继承逻辑
你当前的CoreSpider继承了scrapy.Spider,但却使用了rules属性——这是CrawlSpider专属的特性,普通Spider不会自动解析和应用这些规则,这会导致你的爬取逻辑不符合预期,甚至无法正常结束。
修正后的Spider代码:
from scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractor class CoreSpider(CrawlSpider): name = "final" custom_settings = { # 注意这里用布尔值而非字符串,Scrapy会自动解析 'ROBOTSTXT_OBEY': False, 'HTTPCACHE_ENABLED': True, 'LOG_ENABLED': False, 'DOWNLOADER_MIDDLEWARES': { 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 110, 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, 'random_useragent.RandomUserAgentMiddleware': 320 }, } def __init__(self): # 必须调用父类构造函数,否则CrawlSpider的规则逻辑不会生效 super(CoreSpider, self).__init__() self.all_ngrams = get_ngrams() self.search_term = "" self.start_urls = self.read_url() self.rules = ( Rule(LinkExtractor(unique=True), callback='parse', follow=True, process_request='process_request'), ) def read_url(self): # 示例:从文本文件读取起始URL with open("your_urls.txt", "r") as f: return [line.strip() for line in f if line.strip()] def parse(self, response): # 这里写你的页面解析逻辑 pass def process_request(self, request, spider): # 自定义请求处理逻辑 return request
第二步:正确启动爬虫,避免重复启动Reactor
不管用CrawlerProcess还是CrawlerRunner,核心原则是只启动一次reactor,且确保爬虫结束后不会触发重复启动的逻辑。
方案1:使用CrawlerRunner+inlineCallbacks(推荐)
这种方式能更优雅地控制爬虫生命周期,确保reactor只启动一次:
from scrapy.crawler import CrawlerRunner from scrapy.utils.project import get_project_settings from twisted.internet import reactor, defer from your_spider_module import CoreSpider @defer.inlineCallbacks def run_crawl(): runner = CrawlerRunner(get_project_settings()) # 启动爬虫,等待爬取完成 yield runner.crawl(CoreSpider) # 爬虫完成后停止reactor reactor.stop() if __name__ == "__main__": run_crawl() # 启动reactor,直到被停止 reactor.run()
方案2:使用CrawlerProcess
如果偏好这种方式,确保只调用一次process.start(),且脚本不会重复执行启动逻辑:
from scrapy.crawler import CrawlerProcess from scrapy.utils.project import get_project_settings from your_spider_module import CoreSpider if __name__ == "__main__": process = CrawlerProcess(get_project_settings()) process.crawl(CoreSpider) # 启动爬虫,阻塞直到完成 process.start()
第三步:手动触发爬虫停止(按需)
如果需要在爬完所有URL后主动停止爬虫,可以通过监听spider_idle信号,检查请求队列是否为空来触发停止:
from scrapy.signals import spider_idle class CoreSpider(CrawlSpider): # ... 其他代码 ... @classmethod def from_crawler(cls, crawler, *args, **kwargs): spider = super(CoreSpider, cls).from_crawler(crawler, *args, **kwargs) # 绑定爬虫空闲信号的处理函数 crawler.signals.connect(spider.handle_spider_idle, signal=spider_idle) return spider def handle_spider_idle(self, spider): # 检查调度器是否还有待处理的请求 if not self.crawler.engine.slot.scheduler.has_pending_requests(): # 主动关闭爬虫 self.crawler.engine.close_spider(spider, reason="All URLs have been crawled successfully")
关键注意事项
- 不要在同一个脚本中多次调用
reactor.run()或process.start(),这是触发ReactorNotRestartable的最常见原因。 - 确保Spider继承正确的父类:用
rules就必须继承CrawlSpider,否则规则不会生效。 - 避免在模块级代码中直接启动爬虫(比如
process.start()写在模块顶层),否则当模块被重复导入时会重复启动reactor。
内容的提问来源于stack exchange,提问作者Om Prakash
相关产品推荐
相关产品推荐

