【问题标题】:Scrapy error:User timeout caused connection failureScrapy错误:用户超时导致连接失败
【发布时间】:2018-07-04 14:40:21
【问题描述】:

我正在使用 scrapy 抓取 adidas 网站:http://www.adidas.com/us/men-shoes。 但它总是显示错误:

用户超时导致连接失败:获取http://www.adidas.com/us/men-shoes 耗时超过 180.0 秒..

它重试了 5 次,然后完全失败。

我可以访问 chrome 上的 url,但它不适用于 scrapy。
我尝试过使用自定义用户代理并模拟标头请求,但仍然无法正常工作。

下面是我的代码:

import scrapy


class AdidasSpider(scrapy.Spider):
    name = "adidas"

    def start_requests(self):

        urls = ['http://www.adidas.com/us/men-shoes']

        headers = {
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
            "Accept-Encoding": "gzip, deflate",
            "Accept-Language": "en-US,en;q=0.9",
            "Cache-Control": "max-age=0",
            "Connection": "keep-alive",
            "Host": "www.adidas.com",
            "Upgrade-Insecure-Requests": "1",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
        }

        for url in urls:
            yield scrapy.Request(url, self.parse, headers=headers)

    def parse(self, response):
        yield(response.body)

抓取日志:

{'downloader/exception_count': 1,
 'downloader/exception_type_count/twisted.web._newclient.ResponseNeverReceived': 1,
 'downloader/request_bytes': 224,
 'downloader/request_count': 1,
 'downloader/request_method_count/GET': 1,
 'finish_reason': 'shutdown',
 'finish_time': datetime.datetime(2018, 1, 25, 10, 59, 35, 57000),
 'log_count/DEBUG': 2,
 'log_count/INFO': 9,
 'retry/count': 1,
 'retry/reason_count/twisted.web._newclient.ResponseNeverReceived': 1,
 'scheduler/dequeued': 1,
 'scheduler/dequeued/memory': 1,
 'scheduler/enqueued': 2,
 'scheduler/enqueued/memory': 2,
 'start_time': datetime.datetime(2018, 1, 25, 10, 58, 39, 550000)}

更新

在使用 fiddler 查看请求标头并进行一些测试后,我发现了导致问题的原因。 Scrapy 默认发送Connection: close 标头,因此我没有收到来自阿迪达斯网站的任何响应。

通过发出相同的请求但没有Connection: close 标头对提琴手进行测试后,我得到了正确的响应。现在的问题是如何删除Connection: close 标头?

【问题讨论】:

  • 是时候离开tcpdump或wireshark了
  • 我通过使用scrapy-splash解决了这个问题,Connection 标头可以在splash中被覆盖。 Scrapy 应该添加这个功能。
  • 也许......但这看起来像是阿迪达斯网络服务器中的错误,而不是scrapy中的错误。
  • 可能是,但我发现很少有其他人也有同样的问题。你试过了吗?你能更改/删除Connection: close 标头吗?
  • 这似乎与错误是在客户端还是在服务器中无关。阅读 HTTP RFC。由于Connection: close 的存在,您能找到服务器随机断开连接而不发送响应的理由吗?

标签: python scrapy twisted


【解决方案1】:

因为 scrapy 不允许您编辑 Connection: close 标头。我使用 scrapy-splash 代替使用 splash 发出请求。

现在Connection: close 标头可以被覆盖,现在一切正常。不利的一面是,现在网页必须加载所有资产,然后才能从启动中获得响应,速度较慢但可以。

Scrapy 应该添加编辑默认Connection: close 标头的选项。它在库中是硬编码的,不能轻易覆盖。

下面是我的工作代码:

headers = {
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.9",
        "Host": "www.adidas.com",
        "Connection": "keep-alive",
        "Upgrade-Insecure-Requests": "1",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
    }

    def start_requests(self):
        url = "http://www.adidas.com/us/men-shoes?sz=120&start=0"
        yield SplashRequest(url, self.parse, headers=self.headers)

【讨论】:

    【解决方案2】:

    你可以使用这个工具https://curl.trillworks.com/

    • 从 Chrome 获取 curl 命令
    • 运行转换后的 python 代码(我通过请求从您的 URL 获得响应 200)
    • 为您的 scrapy.Request 复制标头和 cookie

    【讨论】:

    • 您能否准确说明如何执行每个步骤?至少第一个
    • @OstapDidenko alexkras.com/…
    【解决方案3】:

    使用您的代码,第一个连接对我来说效果很好 - 它使用您提供的标头并获得正确的响应。我修改了您的 parse 方法以跟随产品链接并从收到的页面打印 <title> 标记的内容,并且效果也很好。示例日志和打印输出如下。我怀疑由于请求过多,您的速度变慢了。

    2018-01-27 16:48:23 [scrapy.core.engine] INFO: Spider opened
    2018-01-27 16:48:23 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
    2018-01-27 16:48:23 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
    2018-01-27 16:48:24 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://www.adidas.com/us/men-shoes> (referer: None)
    2018-01-27 16:48:25 [scrapy.dupefilters] DEBUG: Filtered duplicate request: <GET http://www.adidas.com/> - no more duplicates will be shown (see DUPEFILTER_DEBUG to show all duplicates)
    2018-01-27 16:48:25 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (301) to <GET https://www.adidas.com/us/alphabounce-beyond-shoes/DB1126.html> from <GET http://www.adidas.com/us/alphabounce-beyond-shoes/DB1126.html>
    2018-01-27 16:48:25 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (301) to <GET https://www.adidas.com/us/ultraboost-laceless-shoes/BB6137.html> from <GET http://www.adidas.com/us/ultraboost-laceless-shoes/BB6137.html>
    
    <snipped a bunch>
    
    2018-01-27 16:48:26 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://www.adidas.com/us/> (referer: http://www.adidas.com/us/men-shoes)
    2018-01-27 16:48:26 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://www.adidas.com/us/nmd_cs2-primeknit-shoes/BY3012.html> (referer: http://www.adidas.com/us/men-shoes)
    adidas Alphabounce Beyond Shoes - White | adidas US
    adidas UA&SONS NMD R2 Shoes - Grey | adidas US
    adidas NMD_C2 Shoes - Brown | adidas US
    adidas NMD_CS2 Primeknit Shoes - Grey | adidas US
    adidas NMD_Racer Primeknit Shoes - Black | adidas US
    adidas Official Website | adidas US
    adidas NMD_CS2 Primeknit Shoes - Black | adidas US
    2018-01-27 16:48:26 [scrapy.core.engine] INFO: Closing spider (finished)
    2018-01-27 16:48:26 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
    

    【讨论】:

    • 有时它会突然出现。但如果 Connection: close 标头不存在,则它每次都有效。尝试多次运行蜘蛛,看看连续运行是否有效。
    • 我在测试过程中至少做了八到十次。一直工作。 :shrug: 很高兴你找到了解决方案!
    【解决方案4】:

    好吧,至少您应该通过将“headers=headers”添加到您的scrapy.Request 来使用您编写的标头。但是,即使我尝试产生 scrapy.Request(url, self.parse, headers=headers)

    ,它仍然无法正常工作

    接下来我将 settings.py 中的 User-Agent 更改为您的标头中的用户代理,即 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome /63.0.3239.132 Safari/537.36" 并且没有使用您在 scrapy.Request 中编写的标头,它有效。

    也许标题中有问题。但我很确定这与 cookie 无关。

    【讨论】:

      【解决方案5】:

      我尝试使用curl 访问该站点,但连接挂起。

      curl -v -L http://www.adidas.com/us/men-shoes
      

      所以我跳进浏览器的调试器,发现请求中有一个Cookie 标头。然后我从标题中复制了整个值并将其粘贴到curl --headers 命令中。

      curl -v -L -H 'Cookie:<cookie value here>' http://www.adidas.com/us/men-shoes
      

      现在 HTML 内容返回。因此,该站点有时会设置访问站点其余部分所需的 cookie。不幸的是,我不确定在哪里或如何以编程方式获取 cookie。让我们知道你是否知道。希望这会有所帮助。

      更新

      看起来有一些方法可以在 Scrapy 中使用持久会话数据(即 cookie)(到目前为止我从未使用过它:))。看看this answerthis doc。我想也许该站点正在重定向请求以设置 cookie,但事实并非如此。所以解决起来应该是比较简单的问题。

      【讨论】: