@OleksandrDashkov 提供了一个非常有用的指南的链接,以便能够使用 aiohttp 和 asyncio 相当有效地发送数百万个请求
我将尝试将这些信息浓缩为可以帮助您解决问题的内容。
我强烈建议您查看asyncio 文档和其他博客文章,以便在使用它进行编程之前对它有一个很好的了解(或者阅读代码并尝试了解它在做什么)。
我们将从aiohttp 中的基本提取工作原理开始。和requests很像。
import asyncio
import aiohttp
async def main():
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
dostuffwithresponse() # To mimic your code.
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# If you're on Python 3.7 :o
asyncio.run(main())
相当简单。如果您使用请求的session 对象,除了async 语法之外,它应该几乎相同。
现在,我们想要获取大量 URL。我们也不希望每次都重新创建会话对象。
async def fetch(session, url):
async with session.get(url) as response:
dostuffwithresponse()
async def main():
async with aiohttp.ClientSession() as session:
for file in list_files:
for link in open(file).readlines():
await fetch(session, url)
现在我们正在获取所有 URL。它仍然是相同的行为,仍然是同步的,因为我们正在等待 fetch() 完成,然后再转到下一个链接。
async def fetch(session, url):
...
async def main():
tasks = []
async with aiohttp.ClientSession() as session:
for file in list_files:
for link in open(file).readlines():
task = asyncio.ensure_future(fetch(session, url))
tasks.append(fut)
results = await asyncio.gather(*tasks)
# results is a list of everything that returned from fetch().
# do whatever you need to do with the results of your fetch function
在这里,我建议您尝试了解asyncio.ensure_future() 和asyncio.gather() 的作用。 Python 3.7 有一个新的修改文档,并且有很多关于这个的博客文章。
最后,您不能同时获取 300,000 个链接。你的操作系统很可能会给你错误,告诉你如何无法打开那么多文件描述符或类似的东西。
因此,您可以通过使用信号量来解决这个问题。对于这种情况,您需要使用asyncio.Semaphore(max_size) 或asyncio.BoundedSemaphore(max_size)
async def fetch(session, url):
...
async def bounded_fetch(sem, url, session):
async with sem:
await fetch(url, session)
async def main():
tasks = []
sem = asyncio.Semaphore(1000) # Generally, most OS's don't allow you to make more than 1024 sockets unless you personally fine-tuned your system.
async with aiohttp.ClientSession() as session:
for file in list_files:
for link in open(file).readlines():
# Notice that I use bounded_fetch() now instead of fetch()
task = asyncio.ensure_future(bounded_fetch(sem, session, url))
tasks.append(fut)
results = await asyncio.gather(*tasks)
# do whatever you need to do with the results of your fetch function
为什么这一切都更快?
因此,asyncio 的工作原理主要是当您向 Web 服务器发送请求时,您不想浪费时间等待响应。相反,当响应到达时,会创建一个事件来告诉事件循环。在等待 1 个响应发生时,您继续发出另一个请求(也就是向事件循环询问下一个任务),然后继续。
我绝对不是最擅长解释这一切的,但我希望这能帮助您基本了解如何加快网页抓取速度。祝你好运!
编辑:回顾一下,您可能必须添加 asyncio.sleep() 才能在循环时实际开始。但是这段代码也使用了open().readlines(),这可能会阻塞事件循环。