第13页 | 精通Scrapy网络爬虫 | 阅读 ‧ 电子书库

同步阅读进度,多语言翻译,过滤屏幕蓝光,评论分享,更多完整功能,更好读书体验,试试 阅读 ‧ 电子书库

第7章
使用Exporter导出数据

通过之前章节的学习,大家掌握了Scrapy中爬取数据、封装数据、处理数据的相关技术,本章我们来学习如何将爬取到的数据以某种数据格式保存到文件中,即导出数据。

在Scrapy中,负责导出数据的组件被称为Exporter(导出器),Scrapy内部实现了多个Exporter,每个Exporter实现一种数据格式的导出,支持的数据格式如下(括号中为相应的Exporter):

(1)JSON (JsonItemExporter)

(2)JSON lines (JsonLinesItemExporter)

(3)CSV (CsvItemExporter)

(4)XML (XmlItemExporter)

(5)Pickle (PickleItemExporter)

(6)Marshal (MarshalItemExporter)

其中,前4种是极为常用的文本数据格式,而后两种是Python特有的。在大多数情况下,使用Scrapy内部提供的Exporter就足够了,需要以其他数据格式(上述6种以外)导出数据时,可以自行实现Exporter。

7.1 指定如何导出数据

在导出数据时,需向Scrapy爬虫提供以下信息:

● 导出文件路径。

● 导出数据格式(即选用哪个Exporter)。

可以通过以下两种方式指定爬虫如何导出数据:

(1)通过命令行参数指定。

(2)通过配置文件指定。

7.1.1 命令行参数

在运行scrapy crawl命令时,可以分别使用-o和-t参数指定导出文件路径以及导出数据格式。

在第1章example项目中,我们使用以下命令运行爬虫:

$ scrapy crawl books -o books.csv ... $ head -10 books.csv # 查看文件开头的10 行 name,price A Light in the Attic,£51.77 Tipping the Velvet,£53.74 Soumission,£50.10 Sharp Objects,£47.82 Sapiens: A Brief History of Humankind,£54.23 The Requiem Red,£22.65 The Dirty Little Secrets of Getting Your Dream Job,£33.34 "The Coming Woman: A Novel Based on the Life of the Infamous Feminist, Victoria Woodhull",£17.93 The Boys in the Boat: Nine Americans and Their Epic Quest for Gold at the 1936 Berlin Olympics,£22.60

其中,-o books.csv指定了导出文件的路径,在这里虽然没有使用-t参数指定导出数据格式,但Scrapy爬虫通过文件后缀名推断出我们想以csv作为导出数据格式。同样的道理,如果将参数改为-o books.json,Scrapy爬虫就会以json作为导出数据格式。

需要明确地指定导出数据格式时,使用-t参数,例如:

$ scrapy crawl books -t csv -o books1.data ... $ scrapy crawl books -t json -o books2.data ... $ scrapy crawl books -t xml -o books3.data ...

运行以上命令后,Scrapy爬虫会以-t参数中的数据格式字符串(如csv、json、xml)为键,在配置字典FEED_EXPORTERS中搜索Exporter,FEED_EXPORTERS的内容由以下两个字典的内容合并而成:

● 默认配置文件中的FEED_EXPORTERS_BASE。

● 用户配置文件中的FEED_EXPORTERS。

前者包含内部支持的导出数据格式,后者包含用户自定义的导出数据格式。以下是Scrapy源码中定义的FEED_EXPORTERS_BASE,它位于scrapy.settings.default_settings模块:

FEED_EXPORTERS_BASE = { 'json': 'scrapy.exporters.JsonItemExporter', 'jsonlines': 'scrapy.exporters.JsonLinesItemExporter', 'jl': 'scrapy.exporters.JsonLinesItemExporter', 'csv': 'scrapy.exporters.CsvItemExporter', 'xml': 'scrapy.exporters.XmlItemExporter', 'marshal': 'scrapy.exporters.MarshalItemExporter', 'pickle': 'scrapy.exporters.PickleItemExporter', }

如果用户添加了新的导出数据格式(即实现了新的Exporter),可在配置文件settings.py中定义FEED_EXPORTERS,例如:

FEED_EXPORTERS = {'excel': 'my_project.my_exporters.ExcelItemExporter'}

另外,指定导出文件路径时,还可以使用%(name)s和%(time)s两个特殊变量:

● %(name)s:会被替换为Spider的名字。

● %(time)s:会被替换为文件创建时间。

请看一个例子,假设一个项目中有爬取书籍信息、游戏信息、新闻信息的3个Spider,分别名为'books'、'games'、'news'。对于任意Spider的任意一次爬取,都可以使用'export_data/%(name)s/%(time)s.csv'作为导出路径,Scrapy爬虫会依据Spider的名字和爬取的时间点创建导出文件:

使用命令行参数指定如何导出数据很方便,但命令行参数只能指定导出文件路径以及导出数据格式,并且每次都在命令行里输入很长的参数让人很烦躁,使用配置文件可以弥补这些不足。

7.1.2 配置文件

接下来,我们学习在配置文件中指定如何导出数据。下面依次说明几个常用选项:

● FEED_URI

导出文件路径。

FEED_URI = 'export_data/%(name)s.data'

● FEED_FORMAT

导出数据格式。

FEED_FORMAT = 'csv'

● FEED_EXPORT_ENCODING

导出文件编码(默认情况下json文件使用数字编码,其他使用utf-8编码)。

FEED_EXPORT_ENCODING = 'gbk'

● FEED_EXPORT_FIELDS

导出数据包含的字段(默认情况下导出所有字段),并指定次序。

FEED_EXPORT_FIELDS = ['name', 'author', 'price']

● FEED_EXPORTERS

用户自定义Exporter字典,添加新的导出数据格式时使用。

FEED_EXPORTERS = {'excel': 'my_project.my_exporters.ExcelItemExporter'}
7.2 添加导出数据格式

在某些需求下,我们想要添加新的导出数据格式,此时需要实现新的Exporter类。下面先参考Scrapy内部的Exporter类是如何实现的,然后自行实现一个Exporter。

7.2.1 源码参考

Scrapy内部的Exporter类在scrapy.exporters模块中实现,以下是其中的代码片段:

class BaseItemExporter(object): def __init__(self, **kwargs): self._configure(kwargs) def _configure(self, options, dont_fail=False): self.encoding = options.pop('encoding', None) self.fields_to_export = options.pop('fields_to_export', None) self.export_empty_fields = options.pop('export_empty_fields', False) if not dont_fail and options: raise TypeError("Unexpected options: %s" % ', '.join(options.keys())) def export_item(self, item): raise NotImplementedError def serialize_field(self, field, name, value): serializer = field.get('serializer', lambda x: x) return serializer(value) def start_exporting(self): pass def finish_exporting(self): pass def _get_serialized_fields(self, item, default_value=None, include_empty=None): """Return the fields to export as an iterable of tuples (name, serialized_value) """ if include_empty is None: include_empty = self.export_empty_fields if self.fields_to_export is None: if include_empty and not isinstance(item, dict): field_iter = six.iterkeys(item.fields) else: field_iter = six.iterkeys(item) else: if include_empty: field_iter = self.fields_to_export else: field_iter = (x for x in self.fields_to_export if x in item) 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 # json class JsonItemExporter(BaseItemExporter): def __init__(self, file, **kwargs): self._configure(kwargs, dont_fail=True) self.file = file kwargs.setdefault('ensure_ascii', not self.encoding) self.encoder = ScrapyJSONEncoder(**kwargs) self.first_item = True def start_exporting(self): self.file.write(b"[\n") def finish_exporting(self): self.file.write(b"\n]") def export_item(self, item): if self.first_item: self.first_item = False else: self.file.write(b',\n') itemdict = dict(self._get_serialized_fields(item)) data = self.encoder.encode(itemdict) self.file.write(to_bytes(data, self.encoding)) # json lines class JsonLinesItemExporter(BaseItemExporter): ... # xml class XmlItemExporter(BaseItemExporter): ... # csv class CsvItemExporter(BaseItemExporter): ... ...

其中的每一个Exporter都是BaseItemExporter的一个子类,BaseItemExporter定义了一些抽象接口待子类实现:

● export_item(self, item)

负责导出爬取到的每一项数据,参数item为一项爬取到的数据,每个子类必须实现该方法。

● start_exporting(self)

在导出开始时被调用,可在该方法中执行某些初始化工作。

● finish_exporting(self)

在导出完成时被调用,可在该方法中执行某些清理工作。

以JsonItemExporter为例,其实现非常简单:

● 为了使最终导出结果是一个json中的列表,在start_exporting和finish_exporting方法中分别向文件写入b"[\n, b"\n]"。

● 在export_item方法中,调用self.encoder.encode方法将一项数据转换成json串(具体细节不再赘述),然后写入文件。

7.2.2 实现Exporter

接下来,我们参照JsonItemExporter的源码,在第1章example项目中实现一个能将数据以Excel格式导出的Exporter。

在项目中创建一个my_exporters.py(与settings.py同级目录),在其中实现ExcelItemExporter,代码如下:

from scrapy.exporters import BaseItemExporter import xlwt class ExcelItemExporter(BaseItemExporter): def __init__(self, file, **kwargs): self._configure(kwargs) self.file = file self.wbook = xlwt.Workbook() self.wsheet = self.wbook.add_sheet('scrapy') self.row = 0 def finish_exporting(self): self.wbook.save(self.file) def export_item(self, item): fields = self._get_serialized_fields(item) for col, v in enumerate(x for _, x in fields): self.wsheet.write(self.row, col, v) self.row += 1

解释上述代码如下:

● 这里使用第三方库xlwt将数据写入Excel文件。

● 在构造器方法中创建Workbook对象和Worksheet对象,并初始化用来记录写入行坐标的self.row。

● 在export_item方法中调用基类的_get_serialized_fields方法,获得item所有字段的迭代器,然后调用self.wsheet.write方法将各字段写入Excel表格。

● finish_exporting方法在所有数据都被写入Excel表格后被调用,在该方法中调用self.wbook.save方法将Excel表格写入Excel文件。

完成ExcelItemExporter后,在配置文件settings.py中添加如下代码:

FEED_EXPORTERS = {'excel': 'example.my_exporters.ExcelItemExporter'}

现在,可以使用ExcelItemExporter导出数据了,以-t excel为参数重新运行爬虫:

$ scrapy crawl books -t excel -o books.xls

图7-1所示为爬取完成后在Excel文件中观察到的结果。

图7-1

如上所示,我们成功地使用ExcelItemExporter将爬取到的数据存入了Excel文件中。

7.3 本章小结

本章学习了在Scrapy中如何使用Exporter将爬取到的数据导出到文件,首先介绍使用命令行参数以及配置文件指定如何导出数据的方法,然后参考Scrapy内部Exporter的源码实现了一个能将数据导出到Excel文件的Exporter。

请支持我们,让我们可以支付服务器费用。
使用微信支付打赏


上一页 · 目录下一页


下载 · 书页 · 阅读 ‧ 电子书库