Scrapy框架中如何正确拆分爬虫的多套页面解析逻辑
问题根因
你当前的写法无法拿到全量解析结果,核心有三个问题:
- Scrapy调度器默认开启URL去重,你在
parse方法中对同一个详情页URL连续发起2次携带不同回调的请求,第二个请求会直接被过滤丢弃,仅会执行第一个绑定的回调。 BaseParser类存在基础语法错误:parse_price方法直接调用未定义的response变量,类本身未实现xpath方法却在parse_ex中直接调用self.xpath,运行时会直接抛出异常,即便请求正常到达也无法返回正确结果。- 你错误地把代码层面的解析逻辑拆分,等同于HTTP请求链路拆分:把不同字段的提取逻辑绑定到独立的请求回调上,没有做数据聚合,即便两个请求都执行完成,也只会返回两个独立的零散字典,无法得到关联的完整数据。
正确拆分方式
解析逻辑拆分是代码结构层面的拆分,不需要为了不同解析规则重复请求同一个页面,按以下三层调整即可:
1. 修正重复请求逻辑
同一URL仅发起一次请求,在回调中完成所有解析逻辑调用,避免无意义的带宽浪费。如果确实需要对同一URL发起多次请求(比如需要携带不同cookie/请求头),可以在Request中传入dont_filter=True关闭去重,但非必要不这么做。
原来的错误写法:
# 同URL重复发请求,第二个会被去重过滤 yield scrapy.Request(response.urljoin(url), callback=self.parse_requem) yield scrapy.Request(response.urljoin(url), callback=self.parse_obj)
替换为:
# 同URL仅发一次请求,统一进入详情页解析回调 yield scrapy.Request(response.urljoin(url), callback=self.parse_detail)
2. 补全BaseParser基础实现
把解析类的基础依赖绑定到实例上,每个字段的解析逻辑拆分为独立方法,最后通过构造方法按需聚合返回:
from scrapy import Selector class BaseParser: def __init__(self, response): # 把response和选择器绑定到实例,供各个解析方法调用 self.response = response self.selector = Selector(response=response) # 单个字段解析逻辑独立拆分,互不影响,方便后续修改规则 def parse_price(self): # 修正原来直接调用未定义response的问题 price = self.response.body return price def parse_ex(self): # 修正原来直接调用不存在的self.xpath的问题 exists = self.selector.xpath('//text()').extract_first() return exists # 按需返回不同结构的解析结果,不需要额外发请求 def construct_price_item(self): return {"price": self.parse_price()} def construct_ex_item(self): return {"ex": self.parse_ex()} # 如果需要全量字段,直接聚合所有解析结果即可 def construct_full_item(self): item = {} item.update(self.construct_price_item()) item.update(self.construct_ex_item()) return item
如果后续不同站点/不同页面的解析规则有差异,直接继承BaseParser重写对应字段的解析方法即可,不需要修改爬虫主逻辑。
3. 简化爬虫侧回调逻辑
去掉不必要的中间请求跳转,在同一个详情页回调中调用所有需要的解析方法:
def parse(self, response): xpath = '//div[@class="proinfor"]//div[@class="prolist_casinforimg"]/a/@href' urls = response.xpath(xpath).extract() for url in urls: url = url.replace("//", "", 1) yield scrapy.Request( response.urljoin(url), callback=self.parse_detail ) def parse_detail(self, response): parser = BaseParser(response) # 需要什么结构的数据,直接调用对应的构造方法即可 yield parser.construct_price_item() yield parser.construct_ex_item() # 如果你原来的parse_requem是需要跳转到其他接口拿额外数据,用meta传递已解析的字段做聚合即可 def parse_requem(self, response): # 先拿到当前页已经解析的内容 exist_item = BaseParser(response).construct_ex_item() # 发起下一个请求时,把已解析的数据通过meta传递 yield scrapy.Request( url="价格接口地址", callback=self.parse_item, meta={"item": exist_item} ) def parse_item(self, response): # 取出之前传递的已解析数据 item = response.meta["item"] # 补充新解析的价格字段 item["price"] = response.body yield item
注意事项
- 不要为了拆分解析逻辑刻意拆分HTTP请求,只有当数据确实在不同的URL下时,才需要通过多请求+meta传参的方式聚合数据。
- 解析规则的修改尽量收敛在Parser类中,爬虫层只负责请求调度和页面跳转,不要把xpath选择器、字段处理逻辑全写在回调函数里,方便后续维护。
内容的提问来源于stack exchange,提问作者Rodion Ilnitskiy
相关产品推荐
相关产品推荐

