如何基于Scrapy Item Loaders适配JSON格式爬取场景?
问题背景
我参考ScrapeOps的示例爬取新闻网站,示例返回的类型是scrapy.selector.unified.SelectorList。而我的目标数据包含在<script>标签中,通过以下Python代码提取并解析为List类型:
fetch('https://newswebsite.com/news/national') data = re.findall("<script type=.application.ld.json. id=.listing-ld.>{.@graph.:(.+?),.@context.:.http:..schema.org..<.script>", response.body.decode("utf-8"), re.S) # 先转成字符串再解析成JSON jsonData = json.loads(''.join(data))
由于返回的是List类型,我没法直接沿用示例里的item loaders实现。请帮忙解释以下代码中的Python概念(尤其是for循环的作用),并指导我适配自己的使用场景。
一、ItemLoader类代码解析
from itemloaders.processors import TakeFirst, MapCompose from scrapy.loader import ItemLoader class ChocolateProductLoader(ItemLoader): default_output_processor = TakeFirst() price_in = MapCompose(lambda x: x.split("£")[-1]) url_in = MapCompose(lambda x: 'https://www.chocolate.co.uk' + x )
核心概念说明:
- ItemLoader:Scrapy提供的结构化数据处理工具,负责把提取到的数据清洗后填充到Item对象中,实现数据提取与清洗逻辑分离,让代码更易维护。
- TakeFirst():输出处理器,作用是从提取到的列表型数据中取第一个元素(Scrapy的CSS/XPath提取默认返回列表,即使只有单个结果),作为字段的最终值。
- MapCompose:输入处理器,接受一个或多个函数,会将提取到的每条数据依次传入这些函数做清洗。比如
price_in里的lambda函数,把价格字符串按£分割后取最后一部分,去掉英镑符号;url_in里的lambda则是把相对路径拼接成完整URL。 - xxx_in命名规则:
_in后缀表示这是针对Item中xxx字段的输入处理器,数据提取后先经过该处理器清洗,再存入Item。
二、Spider类代码解析
import scrapy from chocolatescraper.itemloaders import ChocolateProductLoader from chocolatescraper.items import ChocolateProduct class ChocolateSpider(scrapy.Spider): name = 'chocolatespider' start_urls = ['https://www.chocolate.co.uk/collections/all'] def parse(self, response): products = response.css('product-item') for product in products: chocolate = ChocolateProductLoader(item=ChocolateProduct(), selector=product) chocolate.add_css('name', "a.product-item-meta__title::text") chocolate.add_css('price', 'span.price', re='<span class="price">\n <span class="visually-hidden">Sale price</span>(.*)</span>') chocolate.add_css('url', 'div.product-item-meta a::attr(href)') yield chocolate.load_item() next_page = response.css('[rel="next"] ::attr(href)').get() if next_page is not None: next_page_url = 'https://www.chocolate.co.uk' + next_page yield response.follow(next_page_url, callback=self.parse)
核心逻辑说明:
products = response.css('product-item'):用CSS选择器提取页面中所有商品对应的Selector节点,返回SelectorList(类列表对象)。- for循环的作用:遍历SelectorList中的每个
product(单条商品对应的节点),实现逐条处理:- 实例化
ChocolateProductLoader,传入空的ChocolateProduct对象和当前商品的Selector,确保后续的add_css只从当前商品节点内提取数据,避免交叉污染。 - 调用
add_css方法,指定要填充的Item字段、CSS选择器,还可通过re参数进一步过滤内容(比如价格字段的正则匹配)。 - 调用
load_item()将处理好的数据填充到Item对象中,用yield输出(Scrapy会自动收集这些Item)。
- 实例化
- 翻页逻辑:提取下一页链接,若存在则继续调用
parse方法爬取后续页面。
三、适配List类型数据的实现方案
你的jsonData是结构化字典组成的List,只需遍历该List,针对每条新闻数据使用ItemLoader即可,无需依赖Selector:
步骤1:定义NewsItem
# items.py import scrapy class NewsItem(scrapy.Item): title = scrapy.Field() url = scrapy.Field() publish_time = scrapy.Field() # 根据需求添加其他字段
步骤2:自定义NewsItemLoader(可选)
如果需要清洗数据(比如处理链接、格式化时间),可自定义Loader:
# itemloaders.py from itemloaders.processors import TakeFirst, MapCompose from scrapy.loader import ItemLoader class NewsItemLoader(ItemLoader): default_output_processor = TakeFirst() # 清洗URL:若为相对路径则拼接域名 url_in = MapCompose(lambda x: 'https://newswebsite.com' + x if not x.startswith('http') else x) # 格式化时间:只保留日期部分 publish_time_in = MapCompose(lambda x: x.split('T')[0])
步骤3:在parse方法中遍历jsonData
import scrapy import re import json from yourproject.itemloaders import NewsItemLoader from yourproject.items import NewsItem class NewsSpider(scrapy.Spider): name = 'newsspider' start_urls = ['https://newswebsite.com/news/national'] def parse(self, response): # 提取script标签内的结构化数据 data = re.findall(r'<script type="application/ld\+json" id="listing-ld">{"@graph":(.+?),"@context":"http://schema.org"}</script>', response.body.decode("utf-8"), re.S) if data: jsonData = json.loads(''.join(data)) # 遍历每条新闻数据 for news_item in jsonData: loader = NewsItemLoader(item=NewsItem()) # 直接从字典取数据,用add_value方法传入Loader loader.add_value('title', news_item.get('headline')) loader.add_value('url', news_item.get('url')) loader.add_value('publish_time', news_item.get('datePublished')) # 其他字段同理 yield loader.load_item() # 翻页逻辑(根据网站实际规则调整) next_page = response.css('a.next-page::attr(href)').get() if next_page: yield response.follow(next_page, callback=self.parse)
关键说明
- 因为数据是结构化字典,无需使用
add_css/add_xpath,直接用add_value传入字典中的对应值即可。 - 遍历
jsonData的逻辑和示例中遍历SelectorList的逻辑一致:每条数据对应一个Item实例,经过Loader处理后输出。 - ItemLoader的处理器(MapCompose、TakeFirst)依然生效,
add_value传入的数据会自动经过输入、输出处理器完成清洗。
内容的提问来源于stack exchange,提问作者jovicks
相关产品推荐
相关产品推荐

