前面,我们学习了Scrapy的数据爬取和数据封装,但有时可能还需要对数据进行处理,例如过滤掉重复数据、验证数据的有效性,以及将数据存入数据库等。Scrapy的Item Pipeline(项目管道)是用于处理数据的组件。本节就来学习使用Item Pipeline实现数据的处理的方法。
当Spider将收集到的数据封装为Item后,将会被传递到Item Pipeline(项目管道)组件中等待进一步处理。Scrapy犹如一个爬虫流水线,Item Pipeline是流水线的最后一道工序,但它是可选的,默认关闭,使用时需要将它激活。如果需要,可以定义多个Item Pipeline组件,数据会依次访问每个组件,执行相应的数据处理功能。
以下为Item Pipeline的典型应用:
·清理数据。
·验证数据的有效性。
·查重并丢弃。
·将数据按照自定义的格式存储到文件中。
·将数据保存到数据库中。
还是以起点中文网小说热销榜项目qidian_hot为例,来看看如何使用Item Pipeline处理数据。如图4-7所示为获取到的部分小说热销榜数据,字段有名称、作者、类型和形式。其中,小说的形式有连载和完本两种,如果期望小说形式是以首字母简写的形式展现,就可以使用一个Item Pipeline来完成这个功能。下面在项目qidian_hot中实现该功能。
图4-7 部分小说热销榜数据
编写自己的Item Pipeline组件其实很简单,它只是一个实现了几个简单方法的Python类。当建立一个项目后,这个类就已经自动创建了。打开项目qidian_hot下的pipelines.py,发现自动生成了如下代码:
class QidianHotPipeline(object): def process_item(self, item, spider): return item
下面在方法process_item()中,实现数据处理的功能。
class QidianHotPipeline(object): def process_item(self, item, spider): #判断小说形式是连载还是完结 if item["form"] == "连载": #连载的情况 item["form"] = "LZ" #替换为简称 else:#其他情况 item["form"] = "WJ" return item
QidianHotPipeline是自动生成的Item Pipeline类,它无须继承特定的基类,只需要实现某些特定的方法,如process_item()、open_spider()和close_spider()。注意,方法名不可改变。
process_item()方法是Item Pipeline类的核心方法,必须要实现,用于处理Spider爬取到的每一条数据(Item)。它有两个参数:
item:待处理的Item对象。
spider:爬取此数据的Spider对象。
方法的返回值是处理后的Item对象,返回的数据会传递给下一级的Item Pipeline(如果有)继续处理。
在Scrapy中,Item Pipeline是可选组件,默认是关闭的。要想激活它,只需在配置文件settings.py中启用被注释掉的代码即可。
# Configure item pipelines # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { 'qidian_hot.pipelines.QidianHotPipeline': 300, }
ITEM_PIPELINES是一个字典,将想要启用的Item Pipeline添加到这个字典中。其中,键是Item Pipeline类的导入路径,值是一个整数值。如果启用多个Item Pipeline,这些值就决定了它们运行的顺序,数值越小,优先级越高。下面来看一下运行多个Item Pipeline的例子。
如果有这样一个需求,同一个作者只能上榜一部作品,而爬取到的数据中可能有多部同一作者的作品。因此可以实现一个去重处理的Item Pipeline,将重复数据过滤掉。在pipelines.py中,定义一个去重的Item Pipeline类DuplicatesPipeline,实现代码如下:
from scrapy.exceptions import DropItem #去除重复作者的Item Pipeline class DuplicatesPipeline(object): def __init__(self): #定义一个保存作者姓名的集合 self.author_set = set() def process_item(self, item, spider): if item['author'] in self.author_set: #抛弃重复的Item项 raise DropItem("查找到重复姓名的项目: %s"%item) else: self.author_set.add(item['author']) return item
在构造函数__init__()中定义一个保存作者姓名的集合author_set。
在process_item()方法中,判断item中的author字段是否已经存在于集合author_set中,如果不存在,则将item中的author字段存入author_set集合中;如果存在,就是重复数据,使用raise抛出一个DropItem异常,该Item就会被抛弃,不会传递给后面的Item Pipeline继续处理,更不会导出到文件中。
在配置文件settings.py中启用DuplicatesPipeline:
# Configure item pipelines # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { 'qidian_hot.pipelines.DuplicatesPipeline': 100, 'qidian_hot.pipelines.QidianHotPipeline': 300, }
DuplicatesPipeline设置的值比QidianHotPipeline小,因此优先执行DuplicatesPipeline,过滤掉重复项,再将非重复项传递给QidianHotPipeline继续处理。
之前我们都是通过命令将数据保存为CSV文件。但是,如果要将数据保存为TXT文件,并且字段之间使用其他间隔符(例如分号),使用命令就无法实现了。Scrapy中自带的支持导出的数据类型有CSV、JSON和XML等,如果有特殊要求,需要自己实现。
下面通过Item Pipeline实现将数据保存为文本文档(txt),并且字段之间使用分号(;)间隔的功能。在pipelines.py中,定义一个保存数据的Item Pipeline类SaveToTxtPipeline,实现代码如下:
#将数据保存于文本文档中的Item Pipeline class SaveToTxtPipeline(object): file_name = "hot.txt" #文件名称 file = None #文件对象 #Spider开启时,执行打开文件操作 def open_spider(self,spider): #以追加形式打开文件 self.file = open(self.file_name,"a",encoding="utf-8") #数据处理 def process_item(self, item, spider): #获取item中的各个字段,将其连接成一个字符串 # 字段之间用分号隔开 # 字符串末尾要有换行符\n novel_str = item['name']+";"+\ item["author"]+";"+\ item["type"]+";"+\ item["form"]+"\n" #将字符串写入文件中 self.file.write(novel_str) return item #Spider关闭时,执行关闭文件操作 def close_spider(self,spider): #关闭文件 self.file.close()
类SaveToTxtPipeline中多了几个方法,下面一起来了解一下。
Item Pipeline中,除了必须实现的process_item()方法外,还有3个比较常用的方法,可根据需求选择实现。
·open_spider(self,spider)方法:当Spider开启时(爬取数据之前),该方法被调用,参数spider为被开启的Spider。该方法通常用于在数据处理之前完成某些初始化工作,如打开文件和连接数据库等。
·close_spider(self,spider)方法:当Spider被关闭时(所有数据被爬取完毕),该方法被调用,参数spider为被关闭的Spider。该方法通常用于在数据处理完后,完成某些清理工作,如关闭文件和关闭数据库等。
·from_crawler(cls,crawler)方法:该方法被调用时,会创建一个新的Item Pipeline对象,参数crawler为使用当前管道的项目。该方法通常用于提供对Scrapy核心组件的访问,如访问项目设置文件settings.py。
下面再来看一下SaveToTxtPipeline实现的思路。
(1)属性file_name定义文件名称,属性file定义文件对象,便于操作文件。
(2)open_spider()方法实现文件的打开操作,在数据处理前执行一次。
(3)close_spider()方法实现文件的关闭操作。在数据处理后执行一次。
(4)process_item()方法实现数据的写入操作。首先,获取item中的所有字段,将它们连成字符串,字段之间用分号间隔,而且字符串末尾要加上换行符(\n),实现一行显示一条数据;然后,使用Python的write()函数将数据写入文件中。
在配置文件settings.py中启用SaveToTxtPipeline:
# Configure item pipelines # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { 'qidian_hot.pipelines.DuplicatesPipeline': 100, 'qidian_hot.pipelines.QidianHotPipeline': 300, 'qidian_hot.pipelines.SaveToTxtPipeline': 400, }
运行爬虫程序,查看hot.txt文件中的内容,如图4-8所示。
图4-8 生成的文本文档
为了便于管理,Scrapy中将各种配置信息放在了配置文件settings.py中。上面的文件名也可以转移到settings.py中配置,下面来看一下修改方法。
(1)在settings.py中设置文件名称。
FILE_NAME = "hot.txt"
(2)在SaveToTxtPipeline中获取配置信息。
#将数据保存于文本文档中的Item Pipeline中 class SaveToTxtPipeline(object): file = None #文件对象 @classmethod def from_crawler(cls,crawler): #获取配置文件中的FILE_NAME的值 #如果获取失败,就使用默认值"hot2.txt" cls.file_name = crawler.settings.get("FILE_NAME","hot2.txt") return cls() ……
SaveToTxtPipeline增加了方法from_crawler(),用于获取配置文件中的文件名。注意,该方法是一个类方法(@classmethod),Scrapy会调用该方法来创建SaveToTxtPipeline对象,它有两个参数:
·cls:SaveToTxtPipeline对象。
·crawler:Scrapy中的核心对象,通过它可以访问配置文件(settings.py)。
from_crawler()方法必须返回一个新生成的SaveToTxtPipeline对象(return cls())。