如何使用 Python Scrapy 抓取单个页面的多个部分?

How to scrape multile parts of a single page with Python Scrapy?

假设我有一个 HTML 文件,其中包含多个具有不同结构的部分,需要截然不同的抓取。 蜘蛛布局的最佳做法是什么?

我应该使用一个蜘蛛还是多个蜘蛛?我是否应该多次请求相同的 URL,每次使用不同的回调函数?或者只是按顺序解析不同的部分?我问的是能够与框架的其他部分一起玩——比如 itemspipleines——以及性能、限制和缓存方面.

那么,有什么最佳实践建议吗?社区中使用的规则或惯例?

多个请求

如果我多次请求 URL 它是否被缓存/限制?或者对引擎的每个请求都会导致对 "external web server"?

的请求
class MultiSpider(scrapy.Spider):

    """Parse the parts in parallel."""

    name = 'multispider'

    def start_requests(self):
        url = 'https://en.wikipedia.org/wiki/Main_Page'
        yield scrapy.Request(url=url, callback=self.parser_01)
        yield scrapy.Request(url=url, callback=self.parser_02)

    def parser_01(self, response):
        selector = response.xpath('//some/path')
        if selector is not None:
            # do stuff with *selector* and
            yield {}

    def parser_0(self, response):
        selector = response.xpath('//some/other/path')
        if selector is not None:
            # do very different stuff with *selector* and
            yield {}

多个解析器函数

如果我想避免庞大的 parse 函数,而是针对不同的任务/部分使用多个函数,是否有特别好的/坏的方法来构建它(例如 "how to yield from where")?

class SeqSpider(scrapy.Spider):

    """Parse the page sequentially."""

    name = 'seqspider'

    start_urls = ['https://en.wikipedia.org/wiki/Main_Page', ]

    def parse(self, response):
        selector = response.xpath('//some/path')
        if selector:
            yield from self.parser_01(response, selector):
        selector = response.xpath('//some/other/path')
        if selector:
            yield from self.parser_02(response, selector):

    def parser_01(self, response, selector):
        # do stuff with *selector* and
        yield {}

    def parser_0(self, response, selector):
        # do very different stuff with *selector* and
        yield {}

如果是单个页面,我建议使用一个蜘蛛。一次请求页面并解析您需要的所有数据(您可以为此使用一个或多个函数)。

我也推荐使用物品,例如

import scrapy

class AmazonItem(scrapy.Item):
    product_name = scrapy.Field()
    product_asin = scrapy.Field()
    product_avg_stars = scrapy.Field()
    product_num_reviews = scrapy.Field()
    pass

如果您想将抓取的数据保存到数据库中,您应该使用管道。

为了回答您关于如何构造蜘蛛/最佳实践的问题,我通常会这样做:

  1. 避免构建在同一页面上工作的多个蜘蛛 - 因为目标网站的带宽通常是瓶颈,并且在网站上创建不必要的流量不被视为有礼貌的抓取。

  2. 对同一个 URL 创建多个请求也是如此:它可能会给目标网站带来不必要的流量,这不是很好的抓取行为。默认情况下,scrapy 还会过滤重复的请求,因此您可能突然想知道为什么不是所有请求都被执行。

(注意:有很多方法可以解决这个问题,也可以使用代理等......但这会使事情不必要地复杂化)

  1. 因此,如果您想避免做很多不同事情的大型解析方法,请随意将其拆分。与您在自己的示例中建议的非常相似。我什至会更进一步,将完整的处理步骤封装到单独的解析方法中,例如

    class SomeSpider(scrapy.Spider):
    
        def parse(self, response):
            yield self.parse_widgets_type_a(response)
            yield self.parse_widgets_type_b(response)
            # ....
            yield self.follow_interesting_links(response)
    
        def parse_widgets_type_a(self, response):
            # ....
    
        def parse_widgets_type_b(self, response):
            # ....
    
        def follow_interesting_links(self, response):
            # ....
            yield Request(interesting_url)
    

基于此模板,您甚至可能希望将不同的解析方法重构为不同的 Mixin 类。