欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python爬虫是scrapy框架中ItemLoaders使用解析

程序员文章站 2022-05-05 15:41:44
...

1.5官方文档(英文)地址:https://doc.scrapy.org/en/latest/topics/loaders.html点击打开链接

0.24官方文档(中文)地址:http://scrapy-chs.readthedocs.io/zh_CN/0.24/topics/loaders.html点击打开

ItemLoaders的作用:

 #使用Item Loaders对Item数据进行提取和解析(整理)。作用:
        之前的方式(使用response.xpath()或response.css()提取数据然后直接处理数据),是将数据的提取和解析混合在一起,但是Item Loaders是将这两个部分分开处理了;
       A、 爬虫文件bole.py中只负责数据的提取;

       B、Items.py文件负责数据的整理;(可以实现数据解析代码的重用。相当于将功能相同的解析函数封装成为一个公用的函数,任何爬虫需要这个函数,都可以来调用。)

ItemLoaders的优势:

        1. 使关于数据的提取代码更加简洁,结构更加清晰;
        2. 可以实现数据解析(整理)部分的代码的重用;

        3. 提高代码的可维护性;

ItemLoaders的主要流程:

        1. 当创建item对象(item=JobboleItem())的时候,会去Items.py文件中初始化对应的input/output_processor处理器;
        2. 当item中的处理器初始化完成,回到bole.py爬虫文件中,创建item_loader对象;
        3. item_loader对象创建完成,开始通过add_xpath/add_css/add_value收集数据;
        4. 每收集到一个数据,就会将该数据传递给对应字段对应的input_processor绑定的函数进行数据的处理;数据处理完成,会暂时保存在ItemLoader中;
        5. 循环第4步,将每一个字段的数据提取并交给input_processor,直到所有数据提取完毕,所有数据都会被保存在ItemLoader中;
        6. 调用load_item()函数,给item对象进行赋值;

示例代码:

eg:bole.py(伯乐在线爬取文章)

# -*- coding: utf-8 -*-
import scrapy
from jobbole.items import JobboleItem
from urllib.parse import urljoin
from scrapy.loader import ItemLoader


class BoleSpider(scrapy.Spider):
    name = 'bole'
    allowed_domains = ['jobbole.com']
    start_urls = ['http://blog.jobbole.com/all-posts/page/559/']

    def parse(self, response):
        """
        解析列表页
        :param response:
        :return:
        """

     divs = response.xpath('//div[@id="archive"]/div[@class="post floated-thumb"]')
        # for a in all_a:
        #     # 获取详情页的地址
        #     href = a.xpath('./@href').extract_first('')
        #     # 获取文章图片地址
        #     try:
        #         # /wp-content/uploads/vb/44-thumb_0006230.jpg 图片地址可能含有相对图片地址
        #         img_src = urljoin('http://blog.jobbole.com', a.xpath('./img/@src').extract_first(''))
        #     except Exception as e:
        #         img_src = '没有图片'
        #
        #     yield scrapy.Request(href, callback=self.parse_detail_page, meta={
        #         'img_src': img_src
        #     })
        for article_div in divs:
            # 获取class="post-thumb"的标签,如果有,说明这个文章含有图片,反之没有图片。
            img_src = urljoin('http://blog.jobbole.com', article_div.xpath('./div[@class="post-thumb"]/a/img/@src').extract_first(''))
            if img_src == 'http://blog.jobbole.com':
                img_src = 'https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=%E5%BF%83%E6%80%81%E7%82%B8%E8%A3%82%E5%9B%BE%E7%89%87&hs=2&pn=0&spn=0&di=142010103620&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&ie=utf-8&oe=utf-8&cl=2&lm=-1&cs=1400778086%2C2582284514&os=1208831830%2C738191100&simid=0%2C0&adpicid=0&lpn=0&ln=30&fr=ala&fm=&sme=&cg=&bdtype=0&oriquery=%E5%BF%83%E6%80%81%E7%82%B8%E8%A3%82%E5%9B%BE%E7%89%87&objurl=http%3A%2F%2Fwww.tshyqs.com%2Fupload%2Fimg%2F14960294.jpg&fromurl=ippr_z2C%24qAzdH3FAzdH3Fooo_z%26e3Bpfiyqf_z%26e3Bv54AzdH3Fvi7xtg2AzdH3F0lml9_z%26e3Bip4s&gsm=0&islist=&querylist='

            # 获取class="post-meta"内部的详情页地址

            detail_url = article_div.xpath('./div[@class="post-meta"]/p/a[@class="archive-title"]/@href').extract_first('')

            yield scrapy.Request(detail_url, callback=self.parse_detail_page, meta={'img_src': img_src})

        # 获取下一页的url地址
        # try:
        #     next_url = response.xpath('//a[contains(@class, "next")]/@href').extract_first('')
        # except:
        #     pass
        # else:
        #     yield scrapy.Request(next_url, callback=self.parse)


    def parse_detail_page(self, response):
        """
        解析详情页数据
        :param response:
        :return:
        """
        # # 文章标题
        # title = response.xpath('//div[@class="entry-header"]/h1/text()').extract_first('')
        #
        # # 文章发布时间
        # date_time = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/text()').extract_first('').replace('·', '').strip()
        # # 将字符串类型的date_time转化成datetime类型的数据。
        #
        # # 文章所属标签, 标签中可能含有评论信息, 需要对tag中的评论内容进行过滤。
        # tags_list = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/a/text()').extract()
        # # 遍历tags_list列表,将其中的评论元素,从列表中去除。
        # # endswith(): 以什么字符结尾
        # # 如果列表tags_list中的元素,不是以 "评论" 结尾的,就将这个元素保存到新列表中。反之,不会将其放入新列表中。
        # # "评论" 字符的前后两端是包含空格的。
        # tags = '; '.join([tag for tag in tags_list if not tag.strip().endswith('评论')])
        #
        # # 获取文章的内容以及评论数,点赞数,收藏数内容
        # content = ''.join(response.xpath('//div[@class="entry"]//text()').extract()).strip()
        #
        # numbers_div = response.xpath('//div[@class="post-adds"]')[0]
        # zan_num = numbers_div.xpath('./span[contains(@class, "vote-post-up")]/h10/text()').extract_first('0')
        # keep_num = numbers_div.xpath('./span[contains(@class, "bookmark-btn")]/text()').re_first('\d+', default='0')
        # comment_num = numbers_div.xpath('.//span[contains(@class, "btn-bluet-bigger")]/text()').re_first('\d+', default='0')
        #
        # img_src = response.meta['img_src']
        #
        # item = JobboleItem()
        # item['title'] = title
        # item['date_time'] = date_time
        # item['tags'] = tags
        # item['content'] = content
        # item['zan_num'] = zan_num
        # item['keep_num'] = keep_num
        # item['comment_num'] = comment_num
        # item['img_src'] = [img_src]
        #
        # yield item
******************************以上是之前的方式(注意对比!):后面是ItemLoaders部分*******************************************************

        # 使用Item Loaders对Item数据进行提取和解析(整理)。作用:
        # 之前的方式,是将数据的提取和解析混合在一起,但是Item Loaders是将这两个部分分开处理了;
        # 爬虫文件bole.py中只负责数据的提取;
        # Items.py文件负责数据的整理;(可以实现数据解析代码的重用。相当于将功能相同的解析函数封装成为一个公用的函数,任何爬虫需要这个函数,都可以来调用。)

        # 1. 使关于数据的提取代码更加简洁,结构更加清晰;
        # 2. 可以实现数据解析(整理)部分的代码的重用;
        # 3. 提高代码的可维护性;

        """
        1. 当创建item对象(item=JobboleItem())的时候,会去Items.py文件中初始化对应的input/output_processor处理器; 
        2. 当item中的处理器初始化完成,回到bole.py爬虫文件中,创建item_loader对象;
        3. item_loader对象创建完成,开始通过add_xpath/add_css/add_value收集数据;
        4. 每收集到一个数据,就会将该数据传递给对应字段对应的input_processor绑定的函数进行数据的处理;数据处理完成,会暂时保存在ItemLoader中;
        5. 循环第4步,将每一个字段的数据提取并交给input_processor,直到所有数据提取完毕,所有数据都会被保存在ItemLoader中;
        6. 调用load_item()函数,给item对象进行赋值;
        """


        item_loader = ItemLoader(item=JobboleItem(), response=response)
        item_loader.add_xpath('title', '//div[@class="entry-header"]/h1/text()')
        item_loader.add_xpath('date_time', '//p[@class="entry-meta-hide-on-mobile"]/text()')
        item_loader.add_xpath('tags', '//p[@class="entry-meta-hide-on-mobile"]/a/text()')
        item_loader.add_xpath('content', '//div[@class="entry"]//text()')
        item_loader.add_xpath('zan_num', '//div[@class="post-adds"]/span[contains(@class, "vote-post-up")]//text()')
        item_loader.add_xpath('keep_num', '//div[@class="post-adds"]/span[contains(@class, "bookmark-btn")]/text()')
        item_loader.add_xpath('comment_num', '//div[@class="post-adds"]/a/span/text()')
        item_loader.add_value('img_src', [response.meta['img_src']])

        item = item_loader.load_item()
        yield item


items.py代码:

注意:1、input_processor = MapCompose(函数名),output_process = Join()/TakeFirst()

           2、input_processor = MapCompose(函数名)调用的处理数据的函数中参数值value

      3、自定义ItemsLoaders的使用,主要针对output_process(若使用自定义的ItemsLoaders则无需再定义output_processor = ....默认使用自定义默认的default_output_processor,否则不使用自定义的)


# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy,re
from datetime import datetime
from scrapy.contrib.loader.processor import Join, MapCompose, TakeFirst


# 函数中的参数value值,是add_xpath/add_css/add_value传过来的列表数据中的每一个元素。
# def input_test_title(value):
#     return value + '===='
#
# def output_test_title(value):
#     return '---' + value
#
# def result(value):
#     # 这里面join拼接的大列表里的一个元素
#     return ''.join(value)


def convert_datetime(value):
    # 将字符串类型转化成datetime类型
    value = value.replace('·', '').strip()
    try:
        # strptime(时间字符串,转化后的格式): 函数返回值是datetime类型的对象
        date_time = datetime.strptime(value, '%Y/%m/%d')
    except:
        # 如果转化失败,将当前时间作为默认值。
        date_time = datetime.now()

    return date_time

def convert_tags(value):
    # ['*职业', '1 评论', '职业']
    # 过滤 "评论"
    if "评论" in value:
        return ""
    return value

def zan_number(value):
    if value.strip() != "":
        pattern = re.compile(r'\d+')
        num = re.findall(pattern, value)
        if num:
            num = int(num[0])
        else:
            num = 0
        return num

def get_number(value):
    # 提取评论、点赞数
    pattern = re.compile(r'\d+')
    num = re.findall(pattern, value)
    if num:
        num = int(num[0])
    else:
        num = 0
    return num

def process_image(value):
    # 拼接图片地址
    return value


from scrapy.contrib.loader import ItemLoader
class CustomItemloader(ItemLoader):
    """
    实现自定义的ItemLoader,可以指定默认的output_processor的值。可以避免在每一个字段中,设置重复的值。
    """
    default_output_processor = TakeFirst()


class JobboleItem(scrapy.Item):
    title = scrapy.Field(
        # MapCompose映射类,可以将ItemLoader传递过来的列表中的元素,依次作用到test_title函数上,类似于map()函数。
        # input_processor=MapCompose(input_test_title),
        # Join(): 对列表进行合并,add_xpath/add_css/add_value传过来的列表数据。        
        # TakeFirst(): 获取列表中的首个元素
        
      # output_processor=TakeFirst()
)
        date_time = scrapy.Field(
        input_processor=MapCompose(convert_datetime),
        # output_processor=TakeFirst()
    )
        tags = scrapy.Field(
            input_processor=MapCompose(convert_tags),
            # 覆盖默认的default_output_processor = TakeFirst()

            output_processor=Join()
    )
        content = scrapy.Field(
            output_processor=Join()
    )
        zan_num = scrapy.Field(
            # ['', '1', ' 赞']
            input_processor=MapCompose(zan_number),
            # output_processor=TakeFirst()
    )
     keep_num = scrapy.Field(
            input_processor=MapCompose(get_number),
            # output_processor=TakeFirst()
    )
        comment_num = scrapy.Field(
            input_processor=MapCompose(get_number),
            # output_processor=TakeFirst()
    )
        # 图片的源地址
       img_src = scrapy.Field()
        # 图片在本地的下载路径, 该字段只有在图片下载完成以后,才能进行赋值。
       img_path = scrapy.Field()