ItemLoader-scrapy框架6-python

1、简介

通过前面的学习,我们知道,对于一些简单的、不需要太多处理的数据,Item可以被很容的构建。但是相对复杂一些的呢?比如获取的日期为字符串形式,我们想要日期对象格式;想要数字,但是获取的数据中夹杂字符等等。

Item Loader可以帮我们很好的解决上面的问题。

2、案例分析

下面我们以爬取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字段进行赋值,非常的便捷。

2、ItemLoader

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()
  • ItemLoader构造器中item对象,为我们要构建的Item对象
  • response:提取数据的源数据

第二种方式示例代码:

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并返回
  • field_name:Item里面的设置的字段名称
  • css:css表达式
  • xpath:xpath表达式
  • *processors:处理器,下面会讲解
  • **kw:关键字参数,常用的有正则表达式

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、每个字段的值都是数组形式

为了解决上述问题,下面轮到输入输出处理器登场了。

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(),具体里面做了什么,自己查阅相关文档。

    • 非默认处理器:自定义

      • XXX_in:xxx为item中定义的字段名称,为该字段的输入处理器
      • XXX_out:xxx为item中定义的字段名称,为该字段的输出处理器

那么它们在什么时候生效呢?

  • 输入处理器:在获取内容的时候,比如调用add_css、add_xpath、add_value
  • 输出处理器:调用load_item的时候

说明:

  1. 输入输出处理器只是可被调用的对象,可以说任何函数。
  2. 输入输出处理器需要接收一个可迭代的对象作为第一个参数
  3. 输入处理器的的结果被追加到该字段列表(存放收集值)中,然后传递给输出处理器来组成Item中改字段的值。
  4. Itemloader内置许多处理器

4、内置处理器

常用

  1. TakeFirst():返回第一个非空(non-null/ non-empty)值,常用于单值字段的输出处理器,无参数。
  2. Join()
    1. 返回用分隔符连接后的值。分隔符默认为空格。不接受Loader contexts。
    2. 当使用默认分隔符的时候,这个处理器等同于如下这个:
  3. Compose()
    1. 用给定的多个函数的组合,来构造的处理器。list对象(注意不是指list中的元素),依次被传递到第一个函数,然后输出,再传递到第二个函数,一个接着一个,直到最后一个函数返回整个处理器的输出。
    2. 默认情况下,当遇到None值(list中有None值)的时候停止处理。可以通过传递参数stop_on_none = False改变这种行为。
  4. MapCompose():与Compose处理器类似,区别在于各个函数结果在内部传递的方式(会涉及到list对象解包的步骤):
    1. 输入值是被迭代的处理的,List对象中的每一个元素被单独传入,第一个函数进行处理,然后处理的结果被连接起来形成一个新的迭代器,并被传入第二个函数,以此类推,直到最后一个函数。最后一个函数的输出被连接起来形成处理器的输出。
    2. 每个函数能返回一个值或者一个值列表,也能返回None(会被下一个函数所忽略)
      这个处理器提供了很方便的方式来组合多个处理单值的函数。因此它常用于输入处理器,因为传递过来的是一个List对象。

关于内置处理器更多详细的介绍和案例可以参考官方文档或者最后列举的文档。

5、Loader嵌套

示例部分源代码:

<footer>
    <a class="social" href="https://facebook.com/whatever">Like Us</a>
    <a class="social" href="https://twitter.com/whatever">Follow Us</a>
    <a class="email" href="mailto:whatever@example.com">Email Us</a>
</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()

说明:

  1. css选择器和xpath选择器都可以使用
  2. 可以简化代码时,尽量使用;不要过分的使用,避免造成可阅读性变差。

6、改造

# 
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代码,在下面代码仓库中。

7、总结

参考文档:

代码仓库:https://gitee.com/gaogzhen/python-study.git

QQ群:433529853

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gaog2zh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值