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

Ci3*_*Ci3 4 twisted scrapy scrapy-spider

我有一个很大的相对 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
Run Code Online (Sandbox Code Playgroud)

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

not*_*.no 5

我想出了2个解决方案。1)如果大型站点太多,请在单独的进程中运行蜘蛛。2)通过Twisted使用deferreds和callbacks (请不要跑掉,不会太吓人)。我将讨论如何使用第二种方法,因为第一种方法可以简单地用谷歌搜索。

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

# spider/stackoverflow_spider.py

from twisted.internet import defer
import scrapy

class StackOverflow(scrapy.Spider):

    name = 'stackoverflow'

    def start_requests(self):
        urls = [
            'http://www.gob.cl/en/',
            'http://www.thaigov.go.th/en.html',
            'https://www.yahoo.com',
            'https://www.stackoverflow.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
Run Code Online (Sandbox Code Playgroud)

我稍微改变了一些东西,使其更易于阅读和运行。需要注意的第一件事start_requests()是在循环Deferred中创建对象和链接(通过addCallback())回调函数urls。现在看一下callback参数scrapy.Request

yield scrapy.Request(
    url=url,
    callback=deferred.callback)
Run Code Online (Sandbox Code Playgroud)

此代码段的作用是scrapy.Response在请求可用后立即启动回调链。在 Twisted 中,Deferreds只有在Deferred.callback(result)使用值执行后才开始运行回调链。

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

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

希望这会有所帮助(和/或有效)。

参考

聚苯乙烯

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