【问题标题】:aiohttp: How to efficiently check HTTP headers before downloading response body?aiohttp:如何在下载响应正文之前有效地检查 HTTP 标头?
【发布时间】:2019-02-02 19:56:46
【问题描述】:

我正在使用 asyncio/aiohttp 编写一个网络爬虫。我希望爬虫只想下载 HTML 内容,而跳过其他所有内容。我编写了一个简单的函数来根据扩展名过滤 URL,但这并不可靠,因为许多下载链接中不包含文件名/扩展名。

我可以使用 aiohttp.ClientSession.head() 发送 HEAD 请求,检查 Content-Type 字段以确保它是 HTML,然后发送单独的 GET 请求。但这会增加延迟,因为每页需要两个单独的请求(一个 HEAD,一个 GET),如果可能的话,我想避免这种情况。

是否可以只发送一个常规的 GET 请求,并将 aiohttp 设置为“流”模式以仅下载标头,然后仅在 MIME 类型正确的情况下继续下载正文?或者是否有一些(快速)替代方法可以过滤掉我应该考虑的非 HTML 内容?


更新

根据 cmets 中的要求,我包含了一些示例代码,说明我的意思是发出两个单独的 HTTP 请求(一个 HEAD 请求和一个 GET 请求):

import asyncio
import aiohttp

urls = ['http://www.google.com', 'http://www.yahoo.com']
results = []

async def get_urls_async(urls):
    loop = asyncio.get_running_loop()

    async with aiohttp.ClientSession() as session:
        tasks = []

        for u in urls:
            print(f"This is the first (HEAD) request we send for {u}")
            tasks.append(loop.create_task(session.get(u)))

        results = []
        for t in asyncio.as_completed(tasks):
            response = await t
            url = response.url

            if "text/html" in response.headers["Content-Type"]:
                print("Sending the 2nd (GET) request to retrive body")
                r = await session.get(url)
                results.append((url, await r.read()))
            else:
                print(f"Not HTML, rejecting: {url}")

        return results

results = asyncio.run(get_urls_async(urls))

【问题讨论】:

  • 这是一个(我认为)做我想做的事的例子,但它使用的是 Requests 库,我需要使用 aiohttp 因为 Requests 与 asyncio 不兼容:stackoverflow.com/a/13198035/176410
  • 添加发送两个单独请求的代码示例。
  • 我添加了代码来说明我的意思 这会发送第一个 HEAD 请求,检查响应头,然后如果 Content-Type 字段为 'text/html',则发送另一个 GET 请求。我想知道是否有某种方法可以在继续下载响应正文之前使用单个 GET 请求并检查其标头。我知道 aiohttp 可以对响应进行流式读取,但我不知道如何使用StreamReader API 来完成我所描述的操作。

标签: python http-headers web-crawler mime-types aiohttp


【解决方案1】:

这是一个协议问题,如果你做一个GET,服务器想要发送body。如果您不检索正文,则必须丢弃连接(实际上,如果您在响应上没有在__aexit__ 之前执行read(),它会这样做)。

所以上面的代码应该做更多你想做的事。注意服务器可能在第一个块中发送的不仅仅是标头

【讨论】:

  • 上面的代码确实做了我想要的,但我想知道我是否可以在不发出两个单独请求的情况下以某种方式做同样的事情。也就是说,当您说 “如果您执行 GET,服务器想要发送正文。如果您不检索正文,则必须丢弃连接” ...这就是我的意思我问怎么做:即我怎么能发送一个 GET 请求,读取标题,然后如果它不是我想要的(即 Content-Type: text/html)则断开连接
  • 总是做一个获取,然后检查标题,然后只在需要正文时才进行读取。如果您不进行读取,则连接将被删除并从池中拾取一个新连接。如果您进行读取,则连接将在读取后返回到池中。
  • 所以如果我不执行 read(),那么 body 就永远不会真正下载?
  • 正如 amohr 所说,不使用 read() 将丢弃连接。正如他所指出的,服务器可能会发送第一个块。例如,当响应是 30x 状态代码时,情况就是如此。
猜你喜欢
  • 2013-06-27
  • 2013-07-27
  • 1970-01-01
  • 2011-09-06
  • 2010-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多