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

如何基于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)

核心逻辑说明:

  1. products = response.css('product-item'):用CSS选择器提取页面中所有商品对应的Selector节点,返回SelectorList(类列表对象)。
  2. for循环的作用:遍历SelectorList中的每个product(单条商品对应的节点),实现逐条处理:
    • 实例化ChocolateProductLoader,传入空的ChocolateProduct对象和当前商品的Selector,确保后续的add_css只从当前商品节点内提取数据,避免交叉污染。
    • 调用add_css方法,指定要填充的Item字段、CSS选择器,还可通过re参数进一步过滤内容(比如价格字段的正则匹配)。
    • 调用load_item()将处理好的数据填充到Item对象中,用yield输出(Scrapy会自动收集这些Item)。
  3. 翻页逻辑:提取下一页链接,若存在则继续调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 15:00:11