1、为Item数据项添加处理器
在百度新闻爬虫案例项目中,对爬取数据的处理都在parse()函数内进行,掌握了ItemLoader类的应用后,可以把数据的处理放在ItemLoader类的子类来实现。
在items.py所在目录内,新建newsitemload.py文件,该文件用于对爬虫爬取的数据进行处理。
newsitemload.py代码如下:
# 导入默认的处理器
from scrapy.loader.processors import TakeFirst,Compose
# 导入ItemLoader类
from scrapy.loader import ItemLoader
# 定义BaiduNewsLoader类,用于对数据的清洗
# BaiduNewsLoader类继承ItemLoader类
# 定义类型转换处理器
def tosting(value):
if type(value) is list:
if len(value) > 0:
return value[0]
# 定义过滤非新闻条目的处理器
def filter(title):
# 若是新闻类别,返回title
if len(title) > 0:
if title[0] == "(":
return title
# 若title字符数大于10,返回title
if len(title) > 10:
return title
# 处理新闻类别名称和新闻条目标题
def combin(data):
if type(data) is list:
if len(data) == 2:
return [data[0] + data[1]]
else:
return data
class BaiduNewsLoader(ItemLoader):
# 定义数据项news_title输入处理器
news_title_in = Compose(tosting,filter)
# 定义数据项news_title输出处理器
news_title_out = Compose(combin)
# 定义数据项news_link的输入处理器
news_link_in = Compose(tosting)BaiduNewsLoader类继承于ItemLoader类。ItemLoader类在scrapy.loader模块内,需要导入ItemLoader类。BaiduNewsLoader类使用了Scapy内置的默认处理器Compose,内置处理器在scrapy.loader.processors模块内,需要导入内置处理器Compose。
BaiduNewsLoader类定义了三个处理函数,分别是tosting()、filter()、combin(),用于对爬取的数据进行处理。
tosting(value)函数获取列表对象value的第一个元素,并返回该元素的值,元素的值为str类型。
filter(title)函数过滤非新闻条目,参数title是字符串。过滤条件是title的长度要大于10个字符,若title的长度小于10个字符,程序会确定这不是新闻条目的标题,函数不返回任何内容。再一个过滤条件是title的第一个字符是否是“(”,爬虫在爬取分类新闻条目时,会添加新闻类别名称到数据项,新闻类别名称使用“()”括起来。
combin (data)函数用于合并新闻类别名称和新闻条目标题,参数data是一个列表对象,data若包含两个字符串类型的元素,函数会把这两个元素合并为一个字符串,并返回一个新列表对象,新列表对象唯一的元素为合并后的字符串。
在BaiduNewsLoader类内部,定义了数据项news_title的输入和输出处理器。
news_title的输入处理器为Compose(tosting,filter)。当程序在爬虫的parse()方法内或其它定义的解析方法,调用add_xpath()、add_value()等方法时,与该数据项绑定的输入处理器会被调用。
Compose(tosting,filter)处理器的处理过程为:
(1)处理器处理的数据来自add_xpath()、add_value()等方法传入或提取的数据,数据类型是列表对象;
(2)处理器调用tosting()函数,提取列表对象的第一个元素,并返回该元素,用于后续函数的处理;
(3)处理器调用filter()函数,过滤非新闻条目。参数title为tosting()函数返回的数据, 若函数返回数据,该数据会被添加到数据项,否则该数据项会被忽略。
news_title的输出处理器为Compose(combin)。当程序在爬虫的parse()方法内或其它定义的解析方法,调用ItemLoader类的load_item()方法时,与Item所有数据项绑定的输出处理器会被调用。
Compose(combin)的处理过程为:
(1)处理器处理的数据来自被输入处理器处理过的数据,若程序没有为数据项提供输入处理器,Scrapy会调用默认的内置处理器Identity(),对数据不做任何处理返回原数据;
(2)处理器调用combin ()函数,对data列表对象的元素进行合并。若data列表对象包含两个字符串类型的元素,则将这两个元素合并为一个元素,将该元素放入到一个新的列表对象,并返回这个列表对象。
在BaiduNewsLoader类内部,也定义了数据项news_link_in的输入处理器。
2、 修改爬虫代码
项目添加了BaiduNewsLoader类,在爬虫SpiderNewsbaiduSpider类内部使用BaiduNewsLoader类来处理爬取的数据。
修改后的SpiderNewsbaiduSpider类完整代码如下:
import scrapy
# 导入scrapy选择器
from scrapy.selector import Selector
# 导入NewsbaiduItem
from newsbaidu.items import NewsbaiduItem
# 导入BaiduNewsLoader
from newsbaidu.newsitemload import BaiduNewsLoader
class SpiderNewsbaiduSpider(scrapy.Spider):
name = 'spider_newsbaidu'
allowed_domains = ['https://news.baidu.com']
start_urls = ['https://news.baidu.com/']
def parse(self, response):
# 获取爬取下来的网页代码
html = response.text
# 处理新闻类别网页
# 使用xpath表达式搜寻新闻类别超链接标签
category_node = response.xpath("//div[@id='channel-all']/div/ul/li[position()>1]/a/@href").extract()
for request_node in category_node:
url = response.urljoin(request_node)
request = scrapy.Request(url,callback=self.parse_category,dont_filter=True)
yield request
# 使用xpath表达式搜寻指定的a标签节点,节点以列表方式返回
item_nodes = response.xpath("//a[contains(@mon,'ct=1')]").extract()
# 遍历节点
for item_node in item_nodes:
# 实例化BaiduNewsLoader对象
loader = BaiduNewsLoader(item=NewsbaiduItem(), selector=Selector(text=str(item_node)))
# 提取并清洗news_title数据项
print(loader.add_xpath("news_title","//text()"))
# 提取并清洗news_link数据项
loader.add_xpath("news_link","//@href")
# 获取收集的news_title数据项的值
value = loader.get_collected_values("news_title")
# 若value不为None,调用yield语句返回Item数据项
if value:
yield loader.load_item()
# 处理类别网页的回调方法
def parse_category(self, response):
html = response.text
# 解析新闻类别名称
category = response.xpath("//li[contains(@class,'current active')]/a/text()").extract()
# 使用xpath表达式搜寻指定的a标签节点,节点以列表方式返回
item_nodes = response.xpath("//a[contains(@mon,'col') and not(contains(@mon,'col=carouse'))]").extract()
# 遍历节点
for item_node in item_nodes:
# 实例化BaiduNewsLoader对象
loader = BaiduNewsLoader(item=NewsbaiduItem(), selector=Selector(text=str(item_node)))
pre_category = ["(" + category[0] + ")"]
# 添加新闻类别名称
loader.add_value("news_title",pre_category)
# 添加新闻条目标题
loader.add_xpath("news_title","//text()")
# 使用Selector选择器获取超超链接的文本
loader.add_xpath("news_link","//@href")
# 获取收集的news_title数据项的值
value = loader.get_collected_values("news_title")
# 若value不为None,调用yield语句返回Item数据项
if value:
yield loader.load_item()代码导入了BaiduNewsLoader类,在循环处理item_nodes列表对象(该对象存储了response.xpath返回的数据项)的过程中,实例化BaiduNewsLoader对象loader,传入的参数为NewsbaiduItem类的实例对象和Selector类的实例对象,Selector实例对象的输入内容来自item_nodes列表对象的元素。
loader对象分别调用add_xpath方法收集数据到news_title和news_link数据项,数据收集完成后。loader对象调用get_collected_values方法获取已收集news_title数据项的值,若值不为None,loader对象调用load_item方法最终填充数据到news_title和news_link数据项,并使用yield语句返回填充后的NewsbaiduItem实例对象。