如何让 Scrapy 在 start_requests 方法完成之前执行回调?

How to make Scrapy execute callbacks before the start_requests method finishes?

我有一个很大的相对 url 文件,我想用 Scrapy 抓取它,我已经编写了一些代码来逐行读取这个文件并为我的蜘蛛构建请求来解析。下面是一些示例代码。

蜘蛛:

def start_requests(self):
    with open(self._file) as infile:
        for line in infile:
            inlist = line.replace("\n","").split(",")
            item = MyItem(data = inlist[0])

            request = scrapy.Request(
                url = "http://foo.org/{0}".format(item["data"]),
                callback = self.parse_some_page
            )
            request.meta["item"]
            yield request


def parse_some_page(self,response):
    ...
    request = scrapy.Request(
        url = "http://foo.org/bar",
        callback = self.parse_some_page2
    )
    yield request

这工作正常,但是对于一个大的输入文件,我发现 parse_some_page2start_requests 完成产生所有初始请求之前不会被调用。有什么方法可以让 Scrapy 更早地开始调用回调吗?最终,我不想等待一百万个请求才开始看到项目流经管道。

我想到了 2 个解决方案。 1) 运行 如果有太多大型网站,蜘蛛会在不同的进程中。 2) 通过 Twisted 使用延迟和回调(请不要 运行 离开,不会太可怕)。我将讨论如何使用第二种方法,因为第一种方法可以简单地用谷歌搜索。

每个执行 yield request 的函数都将 "block" 直到结果可用。因此,您的 parse_some_page() 函数会生成一个 scrapy 响应对象,并且在返回响应之前不会继续执行下一个 URL。我确实设法找到了一些需要一段时间才能获取的网站(主要是外国政府网站),希望它能模拟您遇到的类似情况。这是一个快速简单的示例:

# spider/Whosebug_spider.py

from twisted.internet import defer
import scrapy

class Whosebug(scrapy.Spider):

    name = 'Whosebug'

    def start_requests(self):
        urls = [
            'http://www.gob.cl/en/',
            'http://www.thaigov.go.th/en.html',
            'https://www.yahoo.com',
            'https://www.whosebug.com',
            'https://swapi.co/',
        ]

        for index, url in enumerate(urls):
            # create callback chain after a response is returned
            deferred = defer.Deferred()
            deferred.addCallback(self.parse_some_page)
            deferred.addCallback(self.write_to_disk, url=url, filenumber=index+1)
            # add callbacks and errorbacks as needed

            yield scrapy.Request(
                url=url,
                callback=deferred.callback)     # this func will start the callback chain AFTER a response is returned

    def parse_some_page(self, response):
        print('[1] Parsing %s' % (response.url))
        return response.body    # this will be passed to the next callback

    def write_to_disk(self, content, url, filenumber):
        print('[2] Writing %s content to disk' % (url))
        filename = '%d.html' % filenumber
        with open(filename, 'wb') as f:
            f.write(content)
        # return what you want to pass to the next callback function
        # or raise an error and start Errbacks chain

我稍微改变了一些内容,使其更易于阅读 运行。在 start_requests() 中首先要注意的是 Deferred 对象被创建并且回调函数被链接(通过 addCallback())在 urls 循环中。现在看一下 scrapy.Request:

callback 参数
yield scrapy.Request(
    url=url,
    callback=deferred.callback)

此代码段将在 scrapy.Response 从请求中可用后立即启动回调链。在 Twisted 中,Deferreds 仅在 Deferred.callback(result) 以值执行后才启动 运行ning 回调链。

提供响应后,parse_some_page() 函数将 运行 以 Response 作为参数。你要做的是在这里提取你需要的东西并将它传递给下一个回调(即 write_to_disk() 我的例子)。如有必要,您可以向循环中的 Deferred 添加更多回调。

所以这个答案和你原来做的不同的是你使用yield先等待所有响应,然后执行回调。我的方法使用 Deferred.callback() 作为每个请求的回调,以便立即处理每个响应。

希望这对您有所帮助(and/or 有效)。

参考资料

PS

我不知道这是否真的适合您,因为我找不到太大而无法解析的网站。另外,我是 Scrapy 的新手 :D 但我有多年的 Twisted 经验。