发布时间:2023-10-24 12:30
通过前面的学习,我们知道,对于一些简单的、不需要太多处理的数据,Item可以被很容的构建。但是相对复杂一些的呢?比如获取的日期为字符串形式,我们想要日期对象格式;想要数字,但是获取的数据中夹杂字符等等。
Item Loader可以帮我们很好的解决上面的问题。
下面我们以爬取csdn个人博客信息为例,爬取的内容如下
1、博客标题: title
2、发布时间: publish
3、点赞量: approval
4、踩: unlike
5、评论量: comment
6、收藏量: collection
详细代码地址在文章末尾,这里就展示不使用ItemLoader spider代码及部分结果,代码:
import datetime
import re
import scrapy
\'\'\'
爬取目标:csdn上的个人博客
爬取信息-item:
1、博客标题: title
2、发布时间: publish
3、点赞量: approval
4、踩: unlike
5、评论量: comment
6、收藏量: collection
\'\'\'
class PblogSpider1(scrapy.Spider):
name = \'pblog1\'
allowed_domains = [\'blog.csdn.net\']
start_urls = [\'https://blog.csdn.net/gaogzhen/article/list/1\']
def parse(self, response):
# 提取全部连接
links = response.css(\'#articleMeList-blog>.article-list h4>a::attr(href)\').getall()
# 解析每个连接详情页
for link in links:
yield response.follow(link, self.parse_detail)
def parse_detail(self, response):
item = {}
# 解析文章标题
item[\'title\'] = response.css(\'.main_father .container main .blog-content-box .article-title-box>h1::text\').get()
# 解析发布时间
# 获取的时间有额外的信息需要正则过滤下
publish_string = re.search(\'于 (.*) 发布\', response.css(\'.main_father .container main .blog-content-box .bar-content>span.time::text\').get()).group(1)
if publish_string is None or publish_string == \'\':
publish = datetime.datetime.now()
else:
publish = datetime.datetime.strptime(publish_string, \"%Y-%m-%d %H:%M:%S\")
item[\'publish\'] = publish
# 解析点赞
item[\'approval\'] = response.css(\'span#spanCount::text\').get()
# 解析踩
item[\'unlike\'] = response.css(\'span#unlikeCount::text\').get()
# 解析评论
item[\'comment\'] = response.css(\'.main_father .container main .more-toolbox-new .toolbox-middle li.tool-item-comment a span.count::text\').get()
# 解析收藏
item[\'collection\'] = response.css(\'span#get-collection::text\').get()
return item
结果:
2022-05-28 18:02:41 [scrapy.core.scraper] DEBUG: Scraped from <200 https://blog.csdn.net/gaogzhen/article/details/122220825>
{\'title\': \'模块和包-python\', \'publish\': datetime.datetime(2021, 12, 29, 17, 25, 37), \'approval\': \'\\n 0\\n \', \'unlike\': None, \'comment\': \'\\n 1\\n \', \'collectio
n\': \'\\n 0\\n \'}
2022-05-28 18:02:41 [scrapy.core.scraper] DEBUG: Scraped from <200 https://blog.csdn.net/gaogzhen/article/details/122178419>
{\'title\': \'继承和多态-面向对象-python\', \'publish\': datetime.datetime(2021, 12, 27, 18, 55, 51), \'approval\': \'\\n 0\\n \', \'unlike\': None, \'comment\': \'\\n 0\\n \',
\'collection\': \'\\n 0\\n \'}
发现什么问题了吗?
1、获取数据的css冗长
2、我们想要获取的点赞量等数字需要额外处理,去掉回车换行以及转换为数字
3、unlike字段None需要转为0
4、发布时间逻辑处理写在代码中不美观
现在我们获取的数据字段比较少,如果要获取的字段比较多且规则复杂,那么维护工作是一场噩梦。有什么办法解决吗?
所以scrapy就提供了ItemLoader这样一个容器,在这个容器里面可以配置item中各个字段的提取规则。可以通过函数分析原始数据,并对Item字段进行赋值,非常的便捷。
ItemLoader提供了一种构建Item简便的机制。尽管可以直接构建Item,ItemLoader提供了更加简洁的API来构建Item,从一个爬取程序,通过自动完成一些常规的任务比如在分配之前解析未加工的提取的数据。
Item是装载抓取数据的容器,Item Loader则提供了一种更简洁方便的机制来构建Item。
使用Item Loader之前需要实例化改类,有2种方式:
1、 使用默认的ItemLoader(),使用与简单应用场景。
2、定义ItemLoader的子类,使用于逻辑更为复杂的场景。
第一种方式示例代码:
from scrapy.loader import ItemLoader
from myproject.items import Product
def parse(self, response):
l = ItemLoader(item=Product(), response=response)
l.add_xpath(\'name\', \'//div[@class=\"product_name\"]\')
l.add_xpath(\'name\', \'//div[@class=\"product_title\"]\')
l.add_xpath(\'price\', \'//p[@id=\"price\"]\')
l.add_css(\'stock\', \'p#stock\')
l.add_value(\'last_updated\', \'today\') # you can also use literal values
return l.load_item()
第二种方式示例代码:
from itemloaders.processors import TakeFirst, MapCompose, Join
from scrapy.loader import ItemLoader
class ProductLoader(ItemLoader):
default_output_processor = TakeFirst()
name_in = MapCompose(str.title)
name_out = Join()
price_in = MapCompose(str.strip)
# ...
IitemLoader主要成员:
名称 | 描述 |
---|---|
add_css(field_name, css,*processors,**kw) | 以Css选择器的方式选取内容 |
add_xpath(field_name, xpath,*processors,**kw) | 以Xpath选择器的方式选取内容 |
add_value(field_name, value,*processors,**kw) | 直接设置值 |
load_item() | 构建Item并返回 |
add_css、add_xpath和add_value为ItemLoader填充数据的三种方式,关于填充数据一些说明:
学习上面的知识之后,下面我们来改造之前的parse_detail代码如下:
def parse_detail(self, response):
# 构建itemloader
loader = ItemLoader(item=CsdnpersionalblogItem(), response=response)
# 解析文章标题
loader.add_css(\'title\', \'.main_father .container main .blog-content-box .article-title-box>h1::text\')
# 解析发布时间
# 获取的时间有额外的信息需要正则过滤下
content_loader.add_css(\'publish\', \'.main_father .container main .blog-content-box .bar-content>span.time::text\', TakeFirst(), re=\'于 (.*) 发布\')
toolbox_loader = loader.nested_css(\'.main_father .container main .more-toolbox-new .toolbox-middle\')
# 解析点赞
toolbox_loader.add_css(\'approval\', \'span#spanCount::text\')
# 解析踩
toolbox_loader.add_css(\'unlike\', \'span#unlikeCount::text\')
# 解析评论
toolbox_loader.add_css(\'comment\', \'.main_father .container main .more-toolbox-new .toolbox-middle li.tool-item-comment a span.count::text\')
# 解析收藏
toolbox_loader.add_css(\'collection\', \'span#get-collection::text\')
item = pblog_loader.load_item()
print(item)
输出结果:
...
{\'approval\': [\'\\n 0\\n \'],
\'collection\': [\'\\n 0\\n \'],
\'comment\': [\'\\n 0\\n \'],
\'publish\': [\'2021-12-27 18:55:51\'],
\'title\': [\'继承和多态-面向对象-python\']}
{\'approval\': [\'\\n 0\\n \'],
\'collection\': [\'\\n 0\\n \'],
\'comment\': [\'\\n 1\\n \'],
\'publish\': [\'2021-12-29 17:25:37\'],
\'title\': [\'模块和包-python\']}
...
发现什么问题了吗?
1、我们想要获取的点赞量等数字需要额外处理,去掉回车换行以及转换为数字
2、发布时间逻辑处理写在代码中不美观
3、每个字段的值都是数组形式
为了解决上述问题,下面轮到输入输出处理器登场了。
一个ItemLoader对象的每个字段都有一个输入和输出处理器。
工作流程:
当输入处理器通过add_xpath()、add_css()或者add_value()一接收到数据,它就开始处理提取到的数据,处理结果被收集和保存在ItemLoader中。收集所有的数据后,调用ItemLoader.load_item()方法来构建Item对象。此时将使用之前收集的数据调用输出处理器。输出处理器的结果就是最终要被分配给Item对象的值。
输入输出处理器声明方式也有2中:一种直接声明在ItemLoader子类中;另外一种声明在Item字段中。
方式一代码在上面。
方式2代码:
import scrapy
from itemloaders.processors import Join, MapCompose, TakeFirst
from w3lib.html import remove_tags
def filter_price(value):
if value.isdigit():
return value
class Product(scrapy.Item):
name = scrapy.Field(
input_processor=MapCompose(remove_tags),
output_processor=Join(),
)
price = scrapy.Field(
input_processor=MapCompose(remove_tags, filter_price),
output_processor=TakeFirst(),
)
分类:以是否自定义处理器分类
默认处理器和非默认处理器
默认处理器: ItemLoader.default_input_processor()和ItemLoader.default_out_processor(),具体里面做了什么,自己查阅相关文档。
非默认处理器:自定义
那么它们在什么时候生效呢?
说明:
常用
关于内置处理器更多详细的介绍和案例可以参考官方文档或者最后列举的文档。
示例部分源代码:
<footer>
<a class=\"social\" href=\"https://facebook.com/whatever\">Like Usa>
<a class=\"social\" href=\"https://twitter.com/whatever\">Follow Usa>
<a class=\"email\" href=\"mailto:whatever@example.com\">Email Usa>
footer>
不是有嵌套loaders,需要每个字段选择器列举完整的路径,提取代码:
loader = ItemLoader(item=Item())
# load stuff not in the footer
loader.add_xpath(\'social\', \'//footer/a[@class = \"social\"]/@href\')
loader.add_xpath(\'email\', \'//footer/a[@class = \"email\"]/@href\')
loader.load_item()
使用嵌套loaders代码如下:
loader = ItemLoader(item=Item())
# load stuff not in the footer
footer_loader = loader.nested_xpath(\'//footer\')
footer_loader.add_xpath(\'social\', \'a[@class = \"social\"]/@href\')
footer_loader.add_xpath(\'email\', \'a[@class = \"email\"]/@href\')
# no need to call footer_loader.load_item()
loader.load_item()
说明:
#
def parse_detail(self, response):
# 构建itemloader
pblog_loader = PblogLoader(item=CsdnpersionalblogItem(), response=response)
loader = pblog_loader.nested_css(\'.main_father .container main\')
content_loader = pblog_loader.nested_css(\'.blog-content-box\')
# 解析文章标题
content_loader.add_css(\'title\', \'h1#articleContentId::text\')
# 解析发布时间
# 获取的时间有额外的信息需要正则过滤下
content_loader.add_css(\'publish\', \'.bar-content>span.time::text\', TakeFirst(), re=\'于 (.*) 发布\')
toolbox_loader = loader.nested_css(\'.more-toolbox-new .toolbox-middle\')
# 解析点赞
toolbox_loader.add_css(\'approval\', \'span#spanCount::text\')
# 解析踩
toolbox_loader.add_css(\'unlike\', \'span#unlikeCount::text\')
# 解析评论
toolbox_loader.add_css(\'comment\', \'li.tool-item-comment a span.count::text\')
# 解析收藏
toolbox_loader.add_css(\'collection\', \'span#get-collection::text\')
item = pblog_loader.load_item()
print(item)
我们的改造代码如上所示,随着学习深入,我们会继续完善。整个scrapy代码,在下面代码仓库中。
参考文档:
代码仓库:https://gitee.com/gaogzhen/python-study.git
QQ群:433529853