在不明确定义每个字段的情况下抓取数据

9 投票
4 回答
4227 浏览
提问于 2025-04-16 12:14

我想用Python的Scrapy库抓取一页数据,但不想一个个定义页面上的每个字段。相反,我想根据元素的id来动态生成字段名。

一开始我觉得最好的办法是有一个处理流程,先收集所有数据,然后等收集齐了再输出。

但后来我意识到,我需要把数据传递给这个处理流程,而传递数据时需要用到一个项目(item),可是我不知道这个项目需要哪些字段,没法定义它!

那么,我该如何解决这个问题呢?

4 个回答

3

这个在0.24版本中可以正常使用,并且还支持与项目加载器一起使用:

import scrapy
from collections import defaultdict

class FlexibleItem(scrapy.Item):
    fields = defaultdict(scrapy.Field)

    def __setitem__(self, key, value):
        # all keys are supported
        self._values[key] = value
4

这个解决方案适用于导出工具(scrapy crawl -t json -o output.json):

import scrapy

class FlexibleItem(scrapy.Item):
    def __setitem__(self, key, value):
        if key not in self.fields:
            self.fields[key] = scrapy.Field()
        super(FlexibleItem, self).__setitem__(key, value)

更新:已经更新到最新的Scrapy版本可以使用了

17

更新:

之前的方法在使用项目加载器时不太好用,还让事情变得复杂。这里有个更好的方法来实现灵活的项目:

from scrapy.item import BaseItem
from scrapy.contrib.loader import ItemLoader

class FlexibleItem(dict, BaseItem):
    pass

if __name__ == '__main__':
    item = FlexibleItem()
    loader = ItemLoader(item)

    loader.add_value('foo', 'bar')
    loader.add_value('baz', 123)
    loader.add_value('baz', 'test')
    loader.add_value(None, {'abc': 'xyz', 'foo': 555})

    print loader.load_item()

    if 'meow' not in item:
        print "it's not a cat!"

结果:

{'foo': ['bar', 555], 'baz': [123, 'test'], 'abc': ['xyz']}
it's not a cat!

旧方案:

好吧,我找到了一种解决方案。这有点像“黑科技”,但确实有效。

Scrapy中的一个项目会把字段名存储在一个叫做fields的字典里。当你往一个项目里添加数据时,它会检查这个字段是否存在,如果不存在,就会报错:

def __setitem__(self, key, value):
    if key in self.fields:
        self._values[key] = value
    else:
        raise KeyError("%s does not support field: %s" %\
              (self.__class__.__name__, key))

你可以重写这个__setitem__函数,让它不那么严格:

class FlexItem(Item):
    def __setitem__(self, key, value):
        if key not in self.fields:
            self.fields[key] = Field()

        self._values[key] = value

这样就可以了。

现在,当你往一个项目里添加数据时,如果这个项目没有定义那个字段,它会被添加进去,然后数据就会像往常一样添加。

撰写回答