【问题标题】:Scrapy: Can't restart start_requests() properlyScrapy:无法正确重启 start_requests()
【发布时间】:2019-01-10 09:30:35
【问题描述】:

我有一个启动两个页面的刮板 - 其中一个是主页,另一个是一个 .js 文件,其中包含我需要提取的经纬度坐标,因为稍后在解析过程中需要它们。我想首先处理 .js 文件,提取坐标,然后解析主页并开始抓取其链接/解析其项目。 为此,我在Request 方法中使用了priority 参数,我说我希望首先处理我的.js 页面。这有效,但只有大约 70% 的时间(一定是由于 Scrapy 的异步请求)。其余 30% 的时间我最终都在我的 parse 方法中尝试解析 .js 长/纬度坐标,但是已经通过了主网站页面,因此无法解析它们。

出于这个原因,我尝试以这种方式修复它: 在 parse() 方法中,检查第 n 个 url 是哪个,如果它是第一个而不是 .js 的,则重新启动蜘蛛。但是,当我下次重新启动蜘蛛时,它首先正确传递了 .js,但在其处理之后,蜘蛛完成工作并退出脚本而没有错误,就好像它已完成一样。 为什么会发生这种情况,我重新启动蜘蛛时页面的处理与我刚启动它时有什么不同,我该如何解决这个问题?

这是我在尝试调试正在执行的内容以及重新启动时停止的原因时在两种情况下都有示例输出的代码。

class QuotesSpider(Spider):

    name = "bot"
    url_id = 0
    home_url = 'https://website.com'
    longitude = None
    latitude = None

    def __init__(self, cat=None):
        self.cat = cat.replace("-", " ")

    def start_requests(self):
        print ("Starting spider")
        self.start_urls = [
             self.home_url,
             self.home_url+'js-file-with-long-lat.js'
        ]
        for priority, url in enumerate(self.start_urls):
            print ("Processing", url)
            yield Request(url=url, priority=priority, callback=self.parse)


    def parse(self, response):
        print ("Inside parse")
        if self.url_id == 0 and response.url == self.home_url:
            self.alert("Loaded main page before long/lat page, restarting", False)
            for _ in self.start_requests():
                yield _
        else:
            print ("Everything is good, url id is", str(self.url_id))
            self.url_id +=1
            if self.longitude is None:
                for _ in self.parse_long_lat(response):
                    yield _
            else:
                print ("Calling parse cats")
                for cat in self.parse_cats(response):
                    yield cat

    def parse_long_lat(self, response):
        print ("called long lat")
        try:
            self.latitude = re.search('latitude:(\-?[0-9]{1,2}\.?[0-9]*)', 
            response.text).group(1)
            self.longitude = re.search('longitude:(\-?[0-9]{1,3}\.?[0-9]*)', 
            response.text).group(1)
            print ("Extracted coords")
            yield None
        except AttributeError as e:
            self.alert("\nCan't extract lat/long coordinates, store availability will not be parsed. ", False)
            yield None

    def parse_cats(self, response):           
        pass
        """ Parsing links code goes here """

蜘蛛正确启动时的输出,首先获取 .js 页面,然后开始解析猫:

Starting spider
https://website.com
https://website.com/js-file-with-long-lat.js
Inside parse
Everything is good, url id is 0
called long lat
Extracted coords
Inside parse
Everything is good, url id is 1
Calling parse cats

脚本继续并解析一切正常。 蜘蛛启动不正确时输出,首先获取主页并重新启动start_requests():

Starting spider
https://website.com
https://website.com/js-file-with-long-lat.js
Inside parse
Loaded main page before long/lat page, restarting
Starting spider
https://website.com
https://website.com/js-file-with-long-lat.js
Inside parse
Everything is good, url id is 0
called long lat
Extracted coords

脚本停止执行,没有错误,就好像它已经完成一样。

附:如果这很重要,我确实提到 start_requests() 中的处理 URL 是按相反的顺序处理的,但是由于循环顺序,我发现这很自然,我希望 priority 参数能够完成它的工作(因为它在大多数情况下都是如此)时间,它应该按照 Scrapy 的文档去做)。

【问题讨论】:

    标签: python python-3.x scrapy generator scrapy-spider


    【解决方案1】:

    为什么你的 Spider 在“重启”的情况下不继续;您可能会遇到被过滤/丢弃的重复请求。由于该页面已经被访问过,Scrapy 认为它已经完成了。
    因此,您必须使用 dont_filter=True 参数重新发送这些请求:

    for priority, url in enumerate(self.start_urls):
        print ("Processing", url)
        yield Request(url=url, dont_filter=True, priority=priority, callback=self.parse)
        #                      ^^^^^^^^^^^^^^^^  notice us forcing the Dupefilter to
        #                                        ignore duplicate requests to these pages
    

    对于更好的解决方案而不是这种 hacky 方法,请考虑使用InitSpider(例如,存在其他方法)。这可以保证您的“初始”工作已经完成并且可以依赖。
    (由于某种原因,该类从未在Scrapy docs 中记录,但它是一个相对简单的Spider 子类:在开始实际运行之前做一些初步工作。)

    这是一个代码示例:

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.spiders.init import InitSpider
    
    class QuotesSpider(InitSpider):
        name = 'quotes'
        allowed_domains = ['website.com']
        start_urls = ['https://website.com']
    
        # Without this method override, InitSpider behaves like Spider.
        # This is used _instead of_ start_requests. (Do not override start_requests.)
        def init_request(self):
            # The last request that finishes the initialization needs
            # to have the `self.initialized()` method as callback.
            url = self.start_urls[0] + '/js-file-with-long-lat.js'
            yield scrapy.Request(url, callback=self.parse_long_lat, dont_filter=True)
    
        def parse_long_lat(self, response):
            """ The callback for our init request. """
            print ("called long lat")
    
            # do some work and maybe return stuff
            self.latitude = None
            self.longitude = None
            #yield stuff_here
    
            # Finally, start our run.
            return self.initialized()
            # Now we are "initialized", will process `start_urls`
            # and continue from there.
    
        def parse(self, response):
            print ("Inside parse")
            print ("Everything is good, do parse_cats stuff here")
    

    这将导致如下输出:

    2019-01-10 20:36:20 [scrapy.core.engine] INFO: Spider opened
    2019-01-10 20:36:20 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
    2019-01-10 20:36:20 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://127.0.0.1/js-file-with-long-lat.js> (referer: None)
    called long lat
    2019-01-10 20:36:20 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://127.0.0.1> (referer: http://127.0.0.1/js-file-with-long-lat.js/)
    Inside parse
    Everything is good, do parse_cats stuff here
    2019-01-10 20:36:21 [scrapy.core.engine] INFO: Closing spider (finished)
    

    【讨论】:

    • 非常感谢您对此进行调查,我可以查看InitSpider。我仍然不明白原因是否是 Scrapy 不再访问已经访问过的 URL,为什么它仍然访问第一个并在第二个之前停止,该规则应适用于所有情况 IMO。
    • @NikolayShindarov,您可以使用 LOG_LEVEL="DEBUG" 运行 Spider,然后您应该看到,如果您收到有关 dupefilter 丢弃您的请求的行。我会尝试在我的回答中澄清这一点。
    • 非常感谢。我终于设法通过解决方法解决了这个问题 - 使请求同步。所以首先我用 parse() 解析第一页,然后从那里调用第二页并随后解析它,另外我验证 response.url 是正确的。如果我再次遇到类似问题,我会记住您的想法并可能会应用它。
    • @NikolayShindarov,这看起来确实是一个更好的逻辑 :) 随意将其写为您的答案并接受它(如果我的不适合),但如果是,请将其标记为已回答。
    • 完成。对不起,我的错,这和我给你描述的不完全一样,parse() 没有发送第二个Request,你可以看看解决方案。再次感谢您的意见。
    【解决方案2】:

    所以我终于用一种解决方法来处理它: 我检查在parse() 中收到的response.url 是什么,并在此基础上将进一步的解析发送到相应的方法:

    def start_requests(self):
            self.start_urls = [
                self.home_url,
                self.home_url + 'js-file-with-long-lat.js'
            ]
            for priority, url in enumerate(self.start_urls):
                yield Request(url=url, priority=priority, callback=self.parse)
    
    def parse(self, response):
        if response.url != self.home_url:
            for _ in self.parse_long_lat(response):
                yield _
        else:
            for cat in self.parse_cats(response):
                yield cat
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多