如何在抓取时加快请求模块的速度?

时间:2018-10-02 22:57:31

标签: python multithreading web-scraping python-requests

首先,我不是问这个问题,因为我找不到答案,但是因为我无法理解找到的答案。

人们很容易回答一个问题,即“如果您不理解,这是您自己的错,我回答了您的问题”,所以现在我需要一些帮助来理解或简化流程。

我有一个使用pythons请求模块访问的大约300,000个URL的列表。我认为,获取/加载URL所花费的时间非常痛苦,这是因为位于URL上的内容数量很大。每个请求的时间大概是15-20秒。我正在尝试以任何方式可以大大减少这种时间。

我的第一个想法是,是否可以禁用/过滤掉图像以及我不需要使用请求之前可以知道的其他信息。我不确定如何实现它,甚至不确定是否可以实现。

我的第二个想法是发送“批处理请求”,在我看来这就像同时发送多个请求。我真的不确定这是否真的更快,因为无法使我的代码正常工作,所以我无法得到我的请求的准确响应。我的假设是,我可以一次性发送X个请求,获得X个响应,然后单独处理每个请求。我尝试使用的解决方法如下。

def getpage(list_urls):
    for url in list_urls:
        r = requests.get(url)
        dostuffwithresponse()

for file in list_files:
    list_links = open(file).readlines()
    pool = multiprocessing.Pool(processes = 10)
    pool_outputs = pool.map(getpage(), list_links)
    pool.close()
    pool.join()
    print('*')
    print(pool_outputs)

在可能的情况下,通过减少多个响应和发送多个请求来减小响应的大小。我的目标是将15秒以上的等待时间缩短至5秒及以下(或尽我所能)。

有人对采用更简单,更直接的方式提出建议吗?

2 个答案:

答案 0 :(得分:0)

发送大量异步请求是解决之道。如@NinjaKitty所述,您可以考虑使用aiohttp。我最近不得不做类似的事情,发现使用requests_futures对我来说更容易。您可以设置一个循环,以使用每个回调函数发出N个异步请求。然后等待所有N完成,然后继续下一个N。

答案 1 :(得分:0)

@OleksandrDashkov提供了一个非常有用的指南的链接,该指南能够使用aiohttpasyncio相当有效地发送数百万个请求

我将尝试将这些信息压缩为可以帮助您解决问题的信息。

我强烈建议您查看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

为什么这一切都更快?

因此,当您将请求发送到Web服务器时,asyncio通常可以工作,您不想浪费时间等待响应。而是创建一个事件以告知事件循环响应何时到达。在等待1个响应发生时,您继续进行另一个请求(也可以向事件循环询问下一个任务),然后继续。

我绝对不是最擅长解释所有这些内容的人,但是我希望这能帮助您基本了解如何加快网络抓取速度。祝你好运!