【问题标题】:Why is Scrapy not following all rules / running all callbacks?为什么 Scrapy 不遵循所有规则/运行所有回调?
【发布时间】:2022-01-20 21:38:04
【问题描述】:

我有两个从父蜘蛛类继承的蜘蛛,如下所示:

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 内部执行 parse_tournament_page 回调:



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/ 创建与https://www.oddsportal.com/tennis/argentina/atp-buenos-aires/results/#/page/2/ 相同的哈希。

通过阅读,我需要继承 RFPDupeFilter 以重新配置 request_fingerprint 的工作方式。任何关于为什么生成相同哈希的建议和/或如何正确进行子类的提示将不胜感激!

【问题讨论】:

    标签: python web-scraping scrapy


    【解决方案1】:

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

    查看DUPEFILTER_CLASS settings了解更多信息。

    来自 scrapy.utils.request 的 request_fingerprint 已经可以处理片段。子类化时通过 keep_fragments=True。

    在 SpiderOpTest 的 custom_settings 中添加你的类。

    【讨论】:

    • 谢谢!如果服务器忽略片段,那么他们如何知道用户是否想要导航到不同的页面?在我上面的示例中,访问第 2 页的唯一方法是包含 #/page/2/ 片段?
    • 在客户端完成。在客户端上运行的 JavaScript 可以获取片段并基于它发出请求。这些请求可以在浏览器的开发工具中进行监控。
    • 啊啊啊啊好吧。这可能解释了我在这里遇到的一些问题 - stackoverflow.com/q/70416076/11277108 和这里 - stackoverflow.com/q/70429830/11277108。如果您对此有解决方案,我会很感兴趣,但我猜这些问题应该是发布的合适位置......
    猜你喜欢
    • 1970-01-01
    • 2020-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-13
    • 1970-01-01
    相关资源
    最近更新 更多