返回介绍

4.3 Field 元数据

发布于 2024-02-05 21:13:20 字数 2985 浏览 0 评论 0 收藏 0

在第2章中曾讲到,一项数据由Spider提交给Scrapy引擎后,可能会被递送给其他组件(Item Pipeline、Exporter)处理。假设想传递额外信息给处理数据的某个组件(例如,告诉该组件应以怎样的方式处理数据),此时可以使用Field的元数据。请看下面的例子:

class ExampleItem(Item):
 x = Field(a='hello', b=[1, 2, 3])    # x 有两个元数据,a是个字符串,b是个列表
 y = Field(a=lambda x: x ** 2)     # y 有一个元数据,a是个函数

访问一个ExampleItem对象的fields属性,将得到一个包含所有Field对象的字典:

>>> e = ExampleItem(x=100, y=200)
>>> e.fields
{'x': {'a': 'hello', 'b': [1, 2, 3]},
 'y': {'a': <function __main__.ExampleItem.<lambda>>}}
>>> type(e.fields['x'])
scrapy.item.Field
>>> type(e.fields['y'])
scrapy.item.Field

实际上,Field是Python字典的子类,可以通过键获取Field对象中的元数据:

>>> issubclass(Field, dict)
True
>>> field_x = e.fields['x']   # 注意,不要混淆e.fields['x']和e['x']
>>> field_x
{'a': 'hello', 'b': [1, 2, 3]}
>>> field_x['a']
'hello'
>>> field_y = e.fields['y']
>>> field_y
{'a': <function __main__.ExampleItem.<lambda>>}
>>> field_y.get('a', lambda x: x)
<function __main__.ExampleItem.<lambda>>

接下来,看一个应用Field元数据的实际例子。假设我们要把爬取到的书籍信息写入csv文件,那每一项数据最终由Scrapy提供的CsvItemExporter写入文件(数据导出在第7章详细讲解),在爬取过程中提取到的信息并不总是一个字符串,有时可能是一个字符串列表,例如:

>>> book['authors'] = ['李雷', '韩梅梅', '吉姆']

但在写入csv文件时,需要将列表内所有字符串串行化成一个字符串,串行化的方式有很多种,例如:

1. '李雷|韩梅梅|吉姆'      # '|'.join(book['authors'])
2. '李雷;韩梅梅;吉姆'      # ';'.join(book['authors'])
3. "['李雷', '韩梅梅', '吉姆']" # str(book['authors'])

我们可以通过authors字段的元数据告诉CsvItemExporter如何对authors字段串行化:

class BookItem(Item):
 ...
 authors = Field(serializer=lambda x: '|'.join(x))
 ...

其中,元数据的键serializer是CsvItemExporter规定好的,它会用该键获取元数据,即一个串行化函数对象,并使用这个串行化函数将authors字段串行化成一个字符串。以下是Scrapy源码中的相关实现:

# exports.py

class BaseItemExporter(object):
 ...

 def _get_serialized_fields(self, item, default_value=None, include_empty=None):
  ...
  for field_name in field_iter:
    if field_name in item:
      field = {} if isinstance(item, dict) else item.fields[field_name]
      value = self.serialize_field(field, field_name, item[field_name])
    else:
      value = default_value
    yield field_name, value
 ...

class CsvItemExporter(BaseItemExporter):

 ...

 def export_item(self, item):
  ...
  fields = self._get_serialized_fields(item, default_value='',
              include_empty=True)
  values = list(self._build_row(x for _, x in fields))
  self.csv_writer.writerow(values)

 ...

 def serialize_field(self, field, name, value):
  serializer = field.get('serializer', self._join_if_needed)
  return serializer(value)
 ...

解释上述代码如下:

爬取到的每一项数据由export_item方法导出到文件,写入文件之前,先调用_get_serialized_fields方法(在基类中实现)获得数据中每个字段串行化的结果。

在_get_serialized_fields方法中调用serialize_field方法,获取其中一个字段串行化的结果。

在serialize_field方法中获取字段的元数据serializer,得到串行化函数(如果不存在,就使用默认的_join_if_needed函数),最终调用该函数对字段串行化,并将结果返回。

在实际应用中,我们可以仿照上面的例子灵活使用Field元数据。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文