【问题标题】:Is there some way to speed up requests and/or timrout errors when using the python requests library?使用 python 请求库时,是否有某种方法可以加快请求和/或超时错误?
【发布时间】:2021-02-22 23:37:53
【问题描述】:

我正在使用 HEAD 方法在 Python (3) 中向 1000 个特定网站(其中一些似乎不再存在)发送请求,并报告有关其响应标头的统计信息。脚本必须在五分钟内完成。显然,您可以通过减少超时来使请求花费更少的时间,但是您减少的超时越多,超时错误就越多,并且捕获它们似乎非常昂贵。例如,当超时为 0.3 秒时,有 700 个好的请求和 300 个超时错误,而捕获超时错误的总时间本身就超过了 5 分钟。减少超时确实会减少捕获每个超时错误的时间,因为请求在抛出错误之前必须等待超时,但超时次数也会增加。在 timeout=0.05 和 timeout=0.03 时,我只能获得低于 5 分钟捕获超时错误的总时间,但包括花在请求上的时间在内的总时间仍然大于 5 分钟。 timeout=0.02 导致只有 20 个站点可访问,总错误处理时间为 5:17,timeout=0.01 导致没有站点可访问。分配任务的人坚持认为这是可能的,所以我一定是做错了什么。我尝试使用 requests.Session 对象,但这并没有导致任何明显的加速。我还能做些什么来加快速度?

【问题讨论】:

  • 您是否允许/应该使用线程?一个简单的concurrent.futures.ThreadPoolExecutor 可能会用于并行化您的查询,这样您就不会在最慢的服务器上等待;它的map 方法会很容易。

标签: python-3.x optimization python-requests


【解决方案1】:

真正的答案是使用异步 HTTP 请求。但为了从道德上回答这个问题,我必须坚持每个域同时请求的下限,否则您可能会使服务器超载(并被列入黑名单)。

以下是使用aiohttp 的(未经测试的)示例实现,它支持可配置的最大并行数以及每个域的最大并行数。

import aiohttp
import asyncio
from collections import Counter

NUM_PARALLEL = 64
MAX_PARALLEL_PER_DOMAIN = 4
TIMEOUT = aiohttp.ClientTimeout(total=60)



async def fetch_url(url, session):
    try:
        async with session.get(url) as response:
            # Whatever you want.
            return {
                "url": url,
                "status": response.status,
                "content-type": response.headers["content-type"]
            }
    except aiohttp.ServerTimeoutError:
        return {"url": url, "status": "timeout"}
    except Exception as e:
        return {"url": url, "status": "uncaught_exception", "exception": e}


domain_num_inflight = Counter()
domain_semaphore = {}


async def worker(urls, results):
    async with aiohttp.ClientSession(timeout=TIMEOUT) as session:
        while urls:
            url = urls.pop()
            domain = urlparse(url).netloc

            if domain_num_inflight[domain] == 0:
                domain_semaphore[domain] = asyncio.Semaphore(MAX_PARALLEL_PER_DOMAIN)
            domain_num_inflight[domain] += 1
            async with domain_semaphore[domain]:
                results.append(await fetch_url(url, session))
            domain_num_inflight[domain] -= 1
            if domain_num_inflight[domain] == 0:  # Prevent memory leak.
                del domain_semaphore[domain]
                del domain_num_inflight[domain]


urls = [...]
worklist = urls[:]
results = []
workers = [worker(worklist, results) for _ in range(NUM_PARALLEL)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(*workers))
print(results)

【讨论】:

  • 谢谢,我会研究这些库。如果我只点击每个 URL 一次(例如,为每个工作人员分配一个完全不同的 URL 列表),是否需要信号量?
  • @faiuwle 重要的部分是您只访问每个服务器一次。例如。如果您的程序每秒多次从服务器请求内容,您很可能会被列入黑名单(或更糟的是,向执法部门报告)。
  • 不过,这些都是普通域,而且完全不同 - 可以安全地假设不同的域不在同一台服务器上,对吧?
  • @faiuwle 不,但它“足够好”。如果您正在编写工业级状态检查器,您可能还会包含一个单独的 IP 解析模块,并根据 IP 地址(块)进行更多的速率限制。
  • 嗨,我还在为此苦苦挣扎。当我使用 loop.run_until_complete(asyncio.gather(...)) 它立即超时。我什至尝试使用 wait_for 设置五分钟的特定超时,但它仍然会立即超时。是否有一些我必须以某种方式覆盖的默认超时长度?
猜你喜欢
  • 2015-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-17
  • 2021-10-28
  • 2011-02-26
  • 2023-04-04
相关资源
最近更新 更多