为什么 Scrapy 不遵循所有规则/运行 所有回调?

Why is Scrapy not following all rules / running all callbacks?

我有两个从父蜘蛛继承的蜘蛛 class 如下:

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy.crawler import CrawlerProcess


class SpiderOpTest(CrawlSpider):

    custom_settings = {
        "USER_AGENT": "*",
        "LOG_LEVEL": "WARNING",
        "DOWNLOADER_MIDDLEWARES": {'scraper_scrapy.odds.middlewares.SeleniumMiddleware': 543},
    }
    httperror_allowed_codes = [301]
        
    def parse_tournament(self, response):
        print(f"Parsing tournament - {response.url}")

    def parse_tournament_page(self, response):
        print(f"Parsing tournament page - {response.url}")


class SpiderOpTest1(SpiderOpTest):

    name = "test_1"
    start_urls = ["https://www.oddsportal.com/tennis/argentina/atp-buenos-aires/results/"]

    rules = (Rule(LinkExtractor(allow="/page/"), callback="parse_tournament_page"),)


class SpiderOpTest2(SpiderOpTest):

    name = "test_2"
    start_urls = ["https://www.oddsportal.com/tennis/results/"]

    rules = (
        Rule(LinkExtractor(allow="/atp-buenos-aires/results/"), callback="parse_tournament", follow=True),
        Rule(LinkExtractor(allow="/page/"), callback="parse_tournament_page"),
    )

process = CrawlerProcess()
process.crawl(<spider_class>)
process.start()

第一个蜘蛛中 Ruleparse_tournament_page 回调工作正常。

然而,第二个蜘蛛只运行第一个 Ruleparse_tournament 回调,尽管第二个 Rule 与第一个蜘蛛相同并且正在运行同一页。

我显然遗漏了一些非常简单的东西,但我终究无法弄清楚它是什么......

由于页面的关键位是通过 Javascript 加载的,因此包含我正在使用的 Selenium 中间件可能对我有用:

from scrapy import signals
from scrapy.http import HtmlResponse
from selenium import webdriver


class SeleniumMiddleware:

    @classmethod
    def from_crawler(cls, crawler):
        middleware = cls()
        crawler.signals.connect(middleware.spider_opened, signals.spider_opened)
        crawler.signals.connect(middleware.spider_closed, signals.spider_closed)
        return middleware

    def process_request(self, request, spider):
        self.driver.get(request.url)
        return HtmlResponse(
            self.driver.current_url,
            body=self.driver.page_source,
            encoding='utf-8',
            request=request,
        )

    def spider_opened(self, spider):
        options = webdriver.FirefoxOptions()
        options.add_argument("--headless")
        self.driver = webdriver.Firefox(options=options)

    def spider_closed(self, spider):
        self.driver.close()

编辑:

所以我设法创建了第三个蜘蛛,它能够从内部执行 parse_tournament_page 回调 parse_tournament:



class SpiderOpTest3(SpiderOpTest):
    
    name = "test_3"
    start_urls = ["https://www.oddsportal.com/tennis/results/"]
    httperror_allowed_codes = [301]
    
    rules = (
        Rule(
            LinkExtractor(allow="/atp-buenos-aires/results/"),
            callback="parse_tournament",
            follow=True,
        ),
    )

    def parse_tournament(self, response):
        print(f"Parsing tournament - {response.url}")
        xtr = LinkExtractor(allow="/page/")
        links = xtr.extract_links(response)
        for p in links:
            yield response.follow(p.url, dont_filter=True, callback=self.parse_tournament_page)

    def parse_tournament_page(self, response):
        print(f"Parsing tournament PAGE - {response.url}")

这里的关键似乎是 dont_filter=True - 如果将其保留为默认 False 则不会执行 parse_tournament_page 回调。这表明 Scrapy 以某种方式将第二页解释为重复,据我所知不是。除此之外,根据我读过的内容,如果我想解决这个问题,那么我需要将 unique=False 添加到 LinkExtractor。但是,这样做不会导致 parse_tournament_page 回调执行 :(


更新:

所以我想我已经找到了问题的根源。据我所知,RFPDupeFilterrequest_fingerprint 方法为 https://www.oddsportal.com/tennis/argentina/atp-buenos-aires/results/ as https://www.oddsportal.com/tennis/argentina/atp-buenos-aires/results/#/page/2/.

创建了相同的散列

通过阅读,我需要子class RFPDupeFilter 来重新配置request_fingerprint 的工作方式。任何关于为什么生成相同散列的建议 and/or 如何正确执行 subclass 的提示将不胜感激!

更新中提到的两个 URL 之间的区别在于片段#/page/2/。 Scrapy 默认忽略它们:另外,服务器在处理请求时通常会忽略 urls 中的片段,因此在计算指纹时也会默认忽略它们。如果要包含它们,请将 keep_fragments 参数设置为 True(例如,在使用无头浏览器处理请求时)。 (来自 scrapy/utils/request.py

查看 DUPEFILTER_CLASS settings 了解更多信息。

来自scrapy.utils.request的request_fingerprint已经可以处理片段了。当 subclassing 通过 keep_fragments=True.

在 SpiderOpTest 的 custom_settings 中添加您的 class。