【问题标题】:Scrapy i/o block when downloading files下载文件时的 Scrapy i/o 块
【发布时间】:2016-08-06 05:41:17
【问题描述】:

我使用 Scrapy 抓取网页并下载一些文件。由于我得到的 file_url 将重定向到另一个 url(302 重定向)。所以我使用另一种方法 handle_redirect 来获取重定向的 url。我像这样自定义文件管道。

class MyFilesPipeline(FilesPipeline):

    def handle_redirect(self, file_url):
        response = requests.head(file_url)
        if response.status_code == 302:
            file_url = response.headers["Location"]
        return file_url

    def get_media_requests(self, item, info):
        redirect_url = self.handle_redirect(item["file_urls"][0])
        yield scrapy.Request(redirect_url)

    def item_completed(self, results, item, info):
        file_paths = [x['path'] for ok, x in results if ok]
        if not file_paths:
            raise DropItem("Item contains no images")
        item['file_urls'] = file_paths
        return item

使用上面的代码,我可以下载文件,但是下载时进程被阻塞,所以整个项目变得很慢。

我在蜘蛛中尝试了另一种解决方案,首先使用请求获取重定向 url 然后传递给另一个函数。并使用默认文件管道。

yield scrapy.Request(
            download_url[0],
            meta={
                "name": name,
                },
            dont_filter=True,
            callback=self.handle_redirect)

    def handle_redirect(self, response):
        logging.warning("respon %s" % response.meta)
        download_url = response.headers["Location"].decode("utf-8")

        return AppListItem(
            name=response.meta["name"],
            file_urls=[download_url],
            )

仍然阻塞进程。

从这里的dos

Using the Files Pipeline

当项目到达 FilesPipeline 时,file_urls 中的 URL 使用标准的 Scrapy 调度程序安排下载字段 和下载器(这意味着调度器和下载器中间件 被重用),但具有更高的优先级,在其他之前处理它们 页面被刮掉。该项目仍然“锁定”在那个特定的 管道阶段,直到文件完成下载(或失败 某种原因)

这是否意味着在文件下载之前我无法抓取下一个网址? (我的设置中没有设置download_delay)

编辑

我一开始已经添加了这个:

handle_httpstatus_list = [302]

所以我不会被重定向到重定向的 url,我的第一个解决方案使用 requests 是因为我认为 yield 会像这样工作:

  1. 我爬了一个页面,保持yield回调,然后调用return item
  2. 项目传递到管道,如果遇到一些 i/o,它将让蜘蛛像正常的异步 i/o 一样抓取下一页。

或者我必须等待下载的文件才能抓取下一页?这是 Scrapy 的缺点吗?我不关注的第二部分是如何计算爬取页面的速度。例如,3s 为一个完整的页面,默认并发为 16。我猜@neverlastn 使用 16/2/3 获取 2.5 个页面/ s.并发16不是意味着我可以同时处理16个请求吗?那么速度应该是16页/秒?如有错误请指正。

编辑2

谢谢你的回答,我现在明白怎么计算了,但是我还是不明白第二部分。在302上我第一次遇到这个问题。 Error 302 Downloading File in Scrapy 我有一个类似的网址

http://example.com/first

将使用 302 并重定向到

http://example.com/second

但 Scrapy 不会自动重定向到第二个,并且无法下载已连接的文件。从这里的代码Scrapy-redirect 和这里的dos RedirectMiddleware 指出scrapy 应该默认处理重定向。这就是为什么我做了一些技巧并尝试修复它。我的第三个解决方案将尝试像这样使用Celery

class MyFilesPipeline(FilesPipeline):
    @app.task
    def handle_redirect(self, file_url):
        response = requests.head(file_url)
        if response.status_code == 302:
            file_url = response.headers["Location"]
        return file_url

    def get_media_requests(self, item, info):
        redirect_url = self.handle_redirect.delay(item["file_urls"][0])
        yield scrapy.Request(redirect_url)

    def item_completed(self, results, item, info):
        file_paths = [x['path'] for ok, x in results if ok]
        if not file_paths:
            raise DropItem("Item contains no images")
        item['file_urls'] = file_paths
        return item

由于我已经有很多蜘蛛,我不想使用第二种解决方案覆盖它们。所以我在管道中处理它们,这个解决方案会更好吗?

【问题讨论】:

    标签: python scrapy


    【解决方案1】:

    您使用同步/阻塞的requests API。这意味着您将并发(CONCURRENT_REQUESTS_PER_DOMAIN)从(默认情况下)8 变为有效的 1。似乎它支配了你的延迟。不错的技巧,你在第二次尝试时所做的。这不使用requests,因此它应该比使用requests 更快(不是吗?)现在,你当然会增加额外的延迟......如果你的第一个(HTML)请求需要1s,第二个(图片)请求 2 秒,总体而言,完整页面需要 3 秒。默认并发数为 16,这意味着您将抓取大约 2.5 个页面/秒。当您的重定向失败并且您没有抓取图像时,该过程将需要 aprox。 1 秒,即 8 页/秒。因此,您可能会看到 3 倍的减速。一种解决方案可能是通过增加CONCURRENT_REQUESTS_PER_DOMAIN 和/或CONCURRENT_REQUESTS 将允许并行运行的并发请求数提高3 倍。如果您现在在带宽有限和/或延迟增加的地方运行它,另一种解决方案可能是从更靠近托管图像服务器的区域(例如 EC2 美国东部)的云服务器运行它。

    编辑

    通过“小定律”更好地理解性能。第一个CONCURRENT_REQUESTS_PER_DOMAINCONCURRENT_REQUESTS 通常并行工作。 CONCURRENT_REQUESTS_PER_DOMAIN = 8 默认情况下,我猜您通常从单个域下载,因此您的实际并发限制为 8。并发级别(即 8)不是每秒,而是一个固定数字,就像说“那个烤箱最多可以烤8个羊角面包”。您的羊角面包烘烤速度有多快是延迟(这是网络响应时间),您感兴趣的指标是它们的比率,即 8 个羊角面包可以并行烘烤/每个羊角面包 3 秒 = 我将烘烤 2.5 个羊角面包/秒。

    在 302 上,我不确定您到底想做什么。我认为您只是在关注他们-只是您手动进行。我认为在扩展允许的代码时,scrapy 会为您执行此操作。 FilesPipeline 可能无法从 handle_httpstatus_list 获取值,但全局设置 HTTPERROR_ALLOWED_CODES 也会影响 FilesPipeline

    无论如何,requests 是一个糟糕的选择,因为它阻塞 = 绝对是非常糟糕的性能。 yielding Scrapy Requests 将“让他们离开”(暂时),但您将再次“遇到他们”,因为他们使用相同的资源、调度程序和下载程序进行实际下载。这意味着它们很可能会减慢您的表现……这是一件好事。我知道您在这里需要快速爬行,但是scrapy希望您意识到自己在做什么以及何时设置并发限制,例如8 或 16,您相信 scrapy 不会以高于该速率的速度“淹没”您的目标站点。 Scrapy 将采取悲观的假设,即您由同一服务器/域提供的媒体文件是到其 Web 服务器(而不是某些 CDN)的流量,并将应用相同的限制以保护目标站点和您。否则,想象一个页面中恰好有 1000 张图像。如果您以某种方式“免费”获得这 1000 次下载,您将并行向服务器发出 8000 个请求,并发设置为 8 - 这不是一件好事。

    如果您想“免费”获得一些下载,即不遵守并发限制的下载,您可以使用treq。这是 Twisted 框架的请求包。 Here 的如何在管道中使用它。使用它来访问我拥有的 API 或 Web 服务器,而不是第 3 方服务器,我会觉得更自在。

    【讨论】:

    • 我已经编辑了我的问题,请看一下。
    • 我编辑了答案。我也不知道你可以在这里访问/查看第 10 章的扩展:books.google.co.uk/…
    • 请再看看。
    • 在您的第二次编辑...我现在看到了 302 问题,但考虑到这个问题的主题,仍然认为它们是题外话。我的感觉是有一种更干净的方式来处理它。 Celery 是一个糟糕的解决方法,虽然我不是专家,但我几乎可以肯定它也是一个阻塞 API(即与 requests.head 一样糟糕)。我们需要您做的是确认在使用scrapy.Requests 时,如果您将CONCURRENT_REQUESTS_PER_DOMAINCONCURRENT_REQUESTS 都设置为例如200 你会得到更好的性能。这将是扩展此蜘蛛的提议方法中更合适的方法。
    【解决方案2】:

    警告:有更好的无黑客解决方案

    将此添加到设置中:MEDIA_ALLOW_REDIRECTS = True

    https://doc.scrapy.org/en/latest/topics/media-pipeline.html#allowing-redirections

    请注意,在 item_completed results 您将获得旧的未重定向 URL。 file_path 也不会被重定向 request。因此文件名将根据未重定向的数据计算。如果您想添加重定向信息,您可能应该在文件管道中实现自己的media_to_download 方法,并将response.meta 包含到results,因为它应该包含重定向信息:

    https://doc.scrapy.org/en/latest/topics/downloader-middleware.html#module-scrapy.downloadermiddlewares.redirect

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-01
      • 2010-09-25
      • 1970-01-01
      • 2012-05-01
      • 2010-12-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多