Python asyncio/aiohttp:ValueError:Windows 上 select() 中的文件描述符过多

Posted

技术标签:

【中文标题】Python asyncio/aiohttp:ValueError:Windows 上 select() 中的文件描述符过多【英文标题】:Python asyncio/aiohttp: ValueError: too many file descriptors in select() on Windows 【发布时间】:2017-12-06 13:23:13 【问题描述】:

注意:未来的读者请注意,这个问题很老,格式化和编程很匆忙。给出的答案可能有用,但问题和代码可能没有。

大家好,

我无法理解 asyncio 和 aiohttp 并使两者协同工作。因为我不明白自己在做什么,所以我遇到了一个我不知道如何解决的问题。

我使用的是 Windows 10 64 位。

以下代码返回 Content-Type 标头中不包含“html”的页面列表。它是使用 asyncio 实现的。

import asyncio
import aiohttp

MAXitems = 30

async def getHeaders(url, session, sema):
    async with session:
        async with sema:
            try:
                async with session.head(url) as response:
                    try:
                        if "html" in response.headers["Content-Type"]:
                            return url, True
                        else:
                            return url, False
                    except:
                        return url, False
            except:
                return url, False


def check_urls_without_html(list_of_urls):
    headers_without_html = set()
    while(len(list_of_urls) != 0):
        blockurls = []
        print(len(list_of_urls))
        items = 0
        for num in range(0, len(list_of_urls)):
            if num < MAXitems:
                blockurls.append(list_of_urls[num - items])
                list_of_urls.remove(list_of_urls[num - items])
                items += 1
        loop = asyncio.get_event_loop()
        semaphoreHeaders = asyncio.Semaphore(50)
        session = aiohttp.ClientSession()
        data = loop.run_until_complete(asyncio.gather(*(getHeaders(url, session, semaphoreHeaders) for url in blockurls)))
        for header in data:
            if not header[1]:
                headers_without_html.add(header)
    return headers_without_html


list_of_urls= ['http://www.google.com', 'http://www.reddit.com']
headers_without_html =  check_urls_without_html(list_of_urls)

for header in headers_without_html:
    print(header[0])

当我使用太多 URL(即 2000)运行它时,有时它会返回类似这样的错误:

data = loop.run_until_complete(asyncio.gather(*(getHeaders(url, session, semaphoreHeaders) for url in blockurls)))
  File "USER\AppData\Local\Programs\Python\Python36-32\lib\asyncio\base_events.py", line 454, in run_until_complete
    self.run_forever()
  File "USER\AppData\Local\Programs\Python\Python36-32\lib\asyncio\base_events.py", line 421, in run_forever
    self._run_once()
  File "USER\AppData\Local\Programs\Python\Python36-32\lib\asyncio\base_events.py", line 1390, in _run_once
    event_list = self._selector.select(timeout)
  File "USER\AppData\Local\Programs\Python\Python36-32\lib\selectors.py", line 323, in select
    r, w, _ = self._select(self._readers, self._writers, [], timeout)
  File "USER\AppData\Local\Programs\Python\Python36-32\lib\selectors.py", line 314, in _select
    r, w, x = select.select(r, w, w, timeout)
ValueError: too many file descriptors in select()

我已经读到这个问题是由 Windows 的限制引起的。我还读到除了尝试使用更少的文件描述符之外,没有什么可以做的。

我看到人们使用 asyncio 和 aiohttp 推送数千个请求,但即使使用我的分块,我也无法推送 30-50 而不会出现此错误。

我的代码是否存在根本问题,还是 Windows 的固有问题?可以修复吗?可以增加select中允许的文件描述符的最大数量限制吗?

【问题讨论】:

【参考方案1】:

默认情况下,Windows 只能在异步循环中使用 64 个套接字。这是底层select() API 调用的限制。

要增加限制请使用ProactorEventLoop,您可以使用下面的代码。在此处查看完整文档 here。

if sys.platform == 'win32':
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)

另一种解决方案是使用信号量限制整体并发性,请参阅here 提供的答案。例如,在执行 2000 个 API 调用时,您可能不希望有太多并行打开请求(它们可能会超时/更难看到单个调用时间)。这会给你

await gather_with_concurrency(100, *my_coroutines)

【讨论】:

谢谢!几个问题:a)我正在做的分块怎么可能达到极限? b) 自从我发布我最初的问题以来,我一直在尝试 from win32file import _setmaxstdio _setmaxstdio(3072),它似乎工作得很好,它和 ProactorEventLoop 一样吗? a) 是的。 aiohttp 的并发连接数限制为 100,但默认大于 64。 b) 我不使用 Windows,无法检查 _setmaxstdio,但限制是编译时宏恕我直言。它不能在运行时更改。 a) 我看不出这实际上是如何回答我的问题的。我问如果我正在分块数据,如何使选择饱和,这是我使用 aiohttp 运行的唯一脚本。除非 aiohttp 每个链接使用多个连接。 b)好吧,我稍后会尝试您的解决方案,但 _setmaxstdio 确实有所作为。没有它,每 1000 个链接给予或接受它就会被窃听,它已经完成了超过 5000 多个没有问题的问题,它停止的唯一原因是因为我打断了它。编辑:无论如何,我给了你回答我的问题的技巧,谢谢。 您写道,您处理了大约 2000 个 URL。 HTTP 连接返回到内部池但不立即释放。这就是你可以用完打开的套接字限制的方式。 我已更改:loop = asyncio.get_event_loop() for: loop = asyncio.ProactorEventLoop() asyncio.set_event_loop(loop) 并引发:raise NotImplementedError in File "\Python\Python36-32 \lib\site-packages\aiodns_init_.py”,第 85 行,在 _sock_state_cb self.loop.add_reader(fd, self._handle_event, fd, READ) 文件“\Python\Python36-32\ lib\asyncio\events.py",第 453 行,在 add_reader 编辑:我不知道如何格式化 cmets,对不起【参考方案2】:

我也有同样的问题。不能 100% 确定这可以保证工作,但请尝试替换它:

session = aiohttp.ClientSession()

用这个:

connector = aiohttp.TCPConnector(limit=60)
session = aiohttp.ClientSession(connector=connector)

默认情况下,limit 设置为 100 (docs),这意味着客户端一次可以同时打开 100 个连接。正如 Andrew 所说,Windows 一次只能打开 64 个套接字,因此我们提供了一个低于 64 的数字。

【讨论】:

猜这是修复错误的简单方法,我用这种方法做到了,谢谢酋长!【参考方案3】:
#Add to call area
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)

【讨论】:

以上是关于Python asyncio/aiohttp:ValueError:Windows 上 select() 中的文件描述符过多的主要内容,如果未能解决你的问题,请参考以下文章

Python有了asyncio和aiohttp在爬虫这类型IO任务中多线程/多进程还有存在的必要吗?

Python asyncio/aiohttp:ValueError:Windows 上 select() 中的文件描述符过多

python3异步爬虫:asyncio + aiohttp + aiofiles(python经典编程案例)

Python使用asyncio+aiohttp异步爬取猫眼电影专业版

带 tqdm 的 asyncio aiohttp 进度条

python爬虫 asyncio aiohttp aiofiles 单线程多任务异步协程爬取图片