Scrapy回调问题:需获取数据库自增ID爬取后续页面
问题:Scrapy实现父子条目关联爬取与数据库存储
我需要完成以下爬取流程:
- 从目标页面爬取包含子页面URL的父条目;
- 将父条目插入MySQL数据库,获取其auto_increment自增ID;
- 通过父条目里的URL爬取子页面;
- 将子页面的条目插入数据库,并用父条目的自增ID作为外键关联。
这是爬虫开发中的常见场景,但尝试用item_scraped信号在父条目处理完成后触发子页面爬取时,发现信号回调无法直接生成Request。有人提到用Spider Middleware,但不确定具体实现,且Middleware本不应该负责发起请求。我的环境是Scrapy 2.6.2 + Python 3.8,简化代码如下:
爬虫代码
import scrapy from scrapy import signals class MySpider(scrapy.Spider): name = 'MySpider' allowed_domains = ['www.somedomain.com'] start_urls = [ 'https://www.somedomain.com/someurl/', ] @classmethod def from_crawler(cls, crawler, *args, **kwargs): spider = cls() # 尝试绑定条目处理完成后的回调 crawler.signals.connect(spider.item_scraped, signals.item_scraped) return spider def parse(self, response): for node in response.xpath(".//tr/td[@class='foo']"): yield { 'a': ..., 'b': ..., 'url': ... } def item_scraped(self, item, response, spider): ### ### 此处无法正常生成Request ### # 尝试传递自增ID并发起子页面请求 yield Request(item['url'], meta={'id':item['id']}, callback=self.parse_page) def parse_page(self, response): for node in response.xpath("//div[@class='something']//a"): yield { 'id': response.meta['id'], 'title': ... }
管道代码
import mysql.connector class WinebotPipeline(object): def __init__(self): self.conf = {'host': ..., 'user': ..., 'password': ..., 'database': ...} self.connection = self.mysql_connect() self.cursor = self.connection.cursor() def mysql_connect(self): return mysql.connector.connect(**self.conf) def process_item(self, item, spider): sql = "insert into `some_table` (`a`, `b`, `url`) values (%s, %s, %s)" self.cursor.execute(sql, list(item.values())) self.connection.commit() # 获取刚插入条目的自增ID item['id'] = self.cursor.lastrowid return item
解决方案
方法一:在管道中直接调度请求
管道是处理完父条目并拿到自增ID的最佳节点,可通过Scrapy的引擎直接调度子页面请求,无需依赖信号。
修改后的管道代码:
import mysql.connector from scrapy.http import Request class WinebotPipeline(object): def __init__(self): self.conf = {'host': ..., 'user': ..., 'password': ..., 'database': ...} self.connection = self.mysql_connect() self.cursor = self.connection.cursor() self.crawler = None # 通过from_crawler获取爬虫引擎上下文 @classmethod def from_crawler(cls, crawler): pipeline = cls() pipeline.crawler = crawler return pipeline def mysql_connect(self): return mysql.connector.connect(**self.conf) def process_item(self, item, spider): sql = "insert into `some_table` (`a`, `b`, `url`) values (%s, %s, %s)" self.cursor.execute(sql, list(item.values())) self.connection.commit() # 获取自增ID item['id'] = self.cursor.lastrowid # 构造子页面请求并通过引擎调度 req = Request( item['url'], meta={'id': item['id']}, callback=spider.parse_page ) self.crawler.engine.crawl(req, spider=spider) return item
修改后的爬虫代码(移除无用的信号绑定):
import scrapy class MySpider(scrapy.Spider): name = 'MySpider' allowed_domains = ['www.somedomain.com'] start_urls = [ 'https://www.somedomain.com/someurl/', ] def parse(self, response): for node in response.xpath(".//tr/td[@class='foo']"): yield { 'a': ..., 'b': ..., 'url': ... } def parse_page(self, response): for node in response.xpath("//div[@class='something']//a"): yield { 'id': response.meta['id'], 'title': ... }
方法二:在信号回调中用Twisted异步调度请求
如果坚持使用item_scraped信号,可借助Twisted的Deferred实现异步请求调度,避免直接生成Request的限制。
修改后的爬虫代码:
import scrapy from scrapy import signals from scrapy.http import Request from twisted.internet.defer import Deferred class MySpider(scrapy.Spider): name = 'MySpider' allowed_domains = ['www.somedomain.com'] start_urls = [ 'https://www.somedomain.com/someurl/', ] @classmethod def from_crawler(cls, crawler, *args, **kwargs): spider = cls() spider.crawler = crawler crawler.signals.connect(spider.item_scraped, signals.item_scraped) return spider def parse(self, response): for node in response.xpath(".//tr/td[@class='foo']"): yield { 'a': ..., 'b': ..., 'url': ... } def item_scraped(self, item, response, spider): # 通过引擎直接调度子页面请求 req = Request( item['url'], meta={'id': item['id']}, callback=self.parse_page ) self.crawler.engine.crawl(req, spider=self) # 返回Deferred确保异步处理完成 return Deferred.succeed(None) def parse_page(self, response): for node in response.xpath("//div[@class='something']//a"): yield { 'id': response.meta['id'], 'title': ... }
管道代码保持不变即可。
关键说明
crawler.engine.crawl()是Scrapy内部调度请求的核心方法,可直接将Request加入爬取队列,规避信号/管道回调无法生成Request的限制;- 管道中通过
from_crawler获取crawler上下文是Scrapy组件的标准写法,用于获取引擎、信号等核心资源; - 两种方案都能保证父条目入库并拿到自增ID后,再发起子页面爬取,确保外键关联的正确性。
内容的提问来源于stack exchange,提问作者Wolf D.
相关产品推荐
相关产品推荐

