爬虫工作量由小到大的思维转变---<第八章 Scrapy之Item多级页面策略>

前言:

    如果你也是在爬虫的世界里摸爬滚打的话,那你肯定理解,抓取数据的时候,我们常常需要打交道的不只是表面的那些一级页面。很多时候,数据分散在多个层级的页面上,需要我们一层层地深挖,最后集合成一个完整的数据项。今天,咱们就聊聊在Scrapy中如何巧妙地应对这种“一对多对多”型的页面结构。

正文:

1.基本的串行:

(基础item策略,有点根基自动跳到下个目录)

在Scrapy中,处理多级页面(级联请求)并对同一个`item`填充数据是一个经典用法。您可以在解析函数内部通过生成多个请求时,使用`meta`参数来传递`item`,如此可以确保在不同页面之间保持`item`的一致性。
每一级页面的解析函数都需要接受来自上一级页面传递过来的`item`,在其中填充新的数据,并将其传递到下一级页面,直至抓取流程完成。
这里有一个简单的例子,说明了如何将`item`从一个页面传递至另一个页面,并逐级填充数据:

import scrapy

class MySpider(scrapy.Spider):
    name = 'my_spider'
    start_urls = ['http://example.com/level1/']

    def parse(self, response):
        # 解析1级页面数据
        item = {}
        item['level1_data'] = response.xpath('//div[@id="level1_data"]/text()').get()

        # 对2级页面发起请求,传递item
        level2_url = response.xpath('//a[@id="level2_page"]/@href').get()
        yield scrapy.Request(level2_url, callback=self.parse_level2, meta={'item': item})

    def parse_level2(self, response):
        # 从meta中接收传递的item
        item = response.meta['item']

        # 填充2级页面数据
        item['level2_data'] = response.xpath('//div[@id="level2_data"]/text()').get()

        # 对3级页面发起请求,传递带有新数据的item
        level3_url = response.xpath('//a[@id="level3_page"]/@href').get()
        if level3_url:
            # 如果存在3级页面的链接,则继续发送请求并调用相关的解析函数
            yield scrapy.Request(level3_url, callback=self.parse_level3, meta={'item': item})
        else:
            # 如果没有下一级页面链接则直接返回当前的item
            yield item

    def parse_level3(self, response):
        item = response.meta['item']

        # 填充3级页面数据
        item['level3_data'] = response.xpath('//div[@id="level3_data"]/text()').get()

        # 所有级别数据已填充完毕,生成Item
        yield item


 

在这个例子中,可以看到:

1. 首先在顶级(一级页面)回调`parse`函数内进行数据爬取,并初始化了一个`item`对象。
2. `item`对象通过`meta`传递给了下一级页面的回调函数`parse_level2`。
3. `parse_level2`中再次将`item`传递给下一级的回调函数`parse_level3`。
4. 最后,在三级页面的回调函数`parse_level3`中,添加最后一级的数据,并将完整的`item`通过`yield`输出。

因为Scrapy是异步框架,不同请求可能会几乎同时发生。但只要通过`meta`正确地传递`item`,每个请求都能保留其所需的状态,并且在整个爬取链路中,`item`会被顺利传递和填充,最终输出完整且结构一致的数据。故在爬虫的请求处理结束点,将收到完整填充的`item`。

2.多级页面

理论:

当我们打开数据采集的大门,迎接我们的往往是层层叠叠、环环相扣的页面结构。如何在Scrapy这个强大的异步爬虫框架中驾驭它的并行之力,同时保持整个数据收集过程秩序井然。

想象一下,我们在一棵大树下开始收集信息,每片树叶都是一条珍贵的数据。首先,我们从树干—一级页面—开始收集信息,然后它的每个枝桠—这里是二级页面—都承载着进一步的数据。问题来了,这些枝桠上的果子—我们的数据,是异步成熟的。那么,如何才能精准地收获每个成熟的果子,并将它们整合成我们所期望的成果篮呢?

Scrapy提供了一个非常巧妙的技巧,那就是使用meta参数。你可以将它想象为一块白板,随着我们一步步深入林中,每次通过一个页面就在白板上做上标记。

这里来一个场景再现,对于每个分散开的页面,我们都会在访问它的时候,给我们的数据包裹—或者我们称之为item—带上一个旅行标签。这个标签上写有"来自一级页面"的字样。当这个页面内的数据被采集完毕,在Scrapy的魔法操作中,一个新的标签会被加上,标记着“收集完毕”。

但等一下,别急着向后端炫耀你的战果。我们还得检查每个标签,确保没有一个遗漏了数据。只有当所有页面的数据都装袋待发,才是我们收工的时候。

案例:

假设我们需要从一个汇总页面获取一些链接,这是一级页面,然后跟踪这些链接到每个具体的项目细节页面,这是二级页面。我们想要组合一级页面和所有二级页面的数据,然后一起输出。

以下是如何使用Scrapy的meta特性来跟踪这个过程:

import scrapy

class MultiLevelSpider(scrapy.Spider):
    name = 'multilevel_spider'
    start_urls = ['https://example.com/summary_page']

    def parse(self, response):
        # 一级页面的分析逻辑,提取项目的初始信息
        projects = response.xpath("//div[@class='project']")
        for project in projects:
            item = {
                'project_name': project.xpath(".//h3/text()").get(),
                'project_summary': project.xpath(".//p/text()").get(),
                'detail_pages_collected': 0,  # 初始化收集计数器
                'total_detail_pages': 0,  # 总共需要收集的详情页面的数量
                'detail_data': [],  # 用来积累来自每个详情页面的数据
            }

            # 获取所有详情页面链接
            detail_pages = project.xpath(".//a[@class='detail_page_link']/@href").getall()
            item['total_detail_pages'] = len(detail_pages)  # 记录总链接数

            # 进行二级页面请求
            for url in detail_pages:
                yield scrapy.Request(response.urljoin(url),
                                     callback=self.parse_detail_page,
                                     meta={'item': item})

    def parse_detail_page(self, response):
        item = response.meta['item']
        
        # 二级页面的分析逻辑,提取具体数据
        detail_data = {
            'detail_heading': response.xpath("//h1/text()").get(),
            'detail_content': response.xpath("//article/p/text()").get(),
        }
        item['detail_data'].append(detail_data)
        item['detail_pages_collected'] += 1  # 更新收集计数器
        
        # 当所有详情页面都被访问,收集工作完成
        if item['detail_pages_collected'] == item['total_detail_pages']:
            yield item
  1. 为每一个项目创建一个item实例,并包括初始数据。
  2. 从一级页面提取链接,并对每个详情页面发出异步请求。
  3. 使用meta将item传递给每个详情页的解析函数。
  4. 详情页解析后,将结果添加到item中,并跟踪完成的详情页数量。
  5. 一旦一个项目的所有详情页都被访问并且数据收集完成,我们就输出整个item。

----补充:

当然,正常情况下,你可能需要添加错误处理逻辑,以处理网络问题或无法正常抓取页面的情况。此外,还可能需要先处理登录,维护会话,或者处理JavaScript生成的内容等问题。

你可能感兴趣的:(15天玩转高级python,爬虫,scrapy)