Python 更快地从API中抓取JSON:异步还是?

Python 更快地从API中抓取JSON:异步还是?,python,asynchronous,web-scraping,python-requests,Python,Asynchronous,Web Scraping,Python Requests,我需要尽快从网站API中获取大约30GB的JSON数据。我不需要解析它——我只需要保存每个API URL上显示的所有内容 我一次可以请求相当多的数据——比如1MB甚至50MB的“块”(API参数编码在URL中,允许我选择每个请求需要多少数据) API限制每秒1个请求 我想在笔记本电脑和100MB/秒的互联网连接上实现这一点 目前,我正在通过以下方式实现这一点(同步且速度太慢): -预先计算所有我想要抓取的(编码的)URL -使用Python3的请求库请求每个URL,并将生成的JSON逐个保存

我需要尽快从网站API中获取大约30GB的JSON数据。我不需要解析它——我只需要保存每个API URL上显示的所有内容

  • 我一次可以请求相当多的数据——比如1MB甚至50MB的“块”(API参数编码在URL中,允许我选择每个请求需要多少数据)
  • API限制每秒1个请求
  • 我想在笔记本电脑和100MB/秒的互联网连接上实现这一点
目前,我正在通过以下方式实现这一点(同步且速度太慢): -预先计算所有我想要抓取的(编码的)URL -使用Python3的请求库请求每个URL,并将生成的JSON逐个保存在单独的.txt文件中

基本上,我的同步、太慢的解决方案如下所示(稍微简化):

有什么更好/更快的方法可以做到这一点?是否有一种直接的方法可以异步完成这项工作,但要遵守每秒1个请求的阈值?我读过关于grequests(不再维护?)、twisted、asyncio等的文章,但没有足够的经验知道其中一种方法是否正确

编辑 基于Kardaj下面的回复,我决定尝试一下AsyncTornado。这是我目前的Tornado版本(主要基于他们文档中的一个示例)。它成功地限制了并发性

问题是,我如何在全球范围内对所有员工执行每秒1个请求的总体速率限制?(Kardaj,异步睡眠使工作人员在工作前睡眠,但不检查其他工作人员是否同时“醒来”和请求。当我测试它时,所有工作人员抓取一页并打破速率限制,然后同时进入睡眠状态)


您正在解析内容,然后再次序列化它。您可以直接将内容写入文件

curr_url_request = requests.get(encoded_URL_i, timeout=timeout_secs)
if curr_url_request.ok:
    with open('json_output.txt', 'w') as outfile:
        outfile.write(curr_url_request.content)

这可能会消除大部分处理开销。

您正在解析内容,然后再次序列化它。您可以直接将内容写入文件

curr_url_request = requests.get(encoded_URL_i, timeout=timeout_secs)
if curr_url_request.ok:
    with open('json_output.txt', 'w') as outfile:
        outfile.write(curr_url_request.content)

这可能会消除大部分处理开销。

tornado有一个非常强大的异步客户端。下面是一个基本代码,可以实现这一点:

from tornado.httpclient import AsyncHTTPClient
import tornado

URLS = []
http_client = AsyncHTTPClient()
loop = tornado.ioloop.IOLoop.current()


def handle_request(response):
    if response.code == 200:
        with open('json_output.txt', 'a') as outfile:
            outfile.write(response.body)


@tornado.gen.coroutine
def queue_requests():
    results = []
    for url in URLS:
        nxt = tornado.gen.sleep(1)  # 1 request per second
        res = http_client.fetch(url, handle_request)
        results.append(res)
        yield nxt
    yield results  # wait for all requests to finish
    loop.add_callback(loop.stop)
loop.add_callback(queue_requests)
loop.start()
这是一种直接的方法,可能会导致与远程服务器的连接过多。在对请求排队时,您可能必须使用滑动窗口来解决此问题


如果请求超时或需要特定的标题,请随意阅读

tornado
有一个非常强大的异步客户端。下面是一个基本代码,可以实现这一点:

from tornado.httpclient import AsyncHTTPClient
import tornado

URLS = []
http_client = AsyncHTTPClient()
loop = tornado.ioloop.IOLoop.current()


def handle_request(response):
    if response.code == 200:
        with open('json_output.txt', 'a') as outfile:
            outfile.write(response.body)


@tornado.gen.coroutine
def queue_requests():
    results = []
    for url in URLS:
        nxt = tornado.gen.sleep(1)  # 1 request per second
        res = http_client.fetch(url, handle_request)
        results.append(res)
        yield nxt
    yield results  # wait for all requests to finish
    loop.add_callback(loop.stop)
loop.add_callback(queue_requests)
loop.start()
这是一种直接的方法,可能会导致与远程服务器的连接过多。在对请求排队时,您可能必须使用滑动窗口来解决此问题


如果请求超时或需要特定标题,请随意阅读

您是否考虑过简单地尝试一下并找出答案?我使用过并且发现它很有用,它几乎是一个异步的
请求
。一个非常容易实现的结果是使用一个-现在,您正在为每个请求建立一个新的TCP连接。会话将使您保持单个TCP连接的活动状态并重新使用它(请参阅)。^^感谢您的请求。会话提示--这肯定是最容易做到的。我将根据我最终得到的任何异步解决方案对其进行基准测试。您是否考虑过简单地尝试一下并找出答案?我使用过并且发现它很有用,它几乎是一个异步的
请求
。一个非常容易实现的结果是使用一个-现在,您正在为每个请求建立一个新的TCP连接。会话将使您保持单个TCP连接的活动状态并重新使用它(请参阅)。^^感谢您的请求。会话提示--这肯定是最容易做到的。我将根据我最终得到的任何异步解决方案对其进行基准测试。谢谢!为了让它运行,我必须将
loop.add\u callback(queue\u requests)
更改为
loop.add\u callback(lambda:queue\u requests)
,这样它就不会调用未来。另外,睡眠也是有帮助的,但实际上我发现所有的工作人员都在同一时间抓取一页,然后在同一时间睡觉,等等……所以他们不遵守每秒1次的限制。我在
python-2.7
上测试了这一点,因为它工作正常,在他们的网站上,他们说
tornado
python-3
的支持,但也许有一些细微的差别。谢谢!为了让它运行,我必须将
loop.add\u callback(queue\u requests)
更改为
loop.add\u callback(lambda:queue\u requests)
,这样它就不会调用未来。另外,睡眠也是有帮助的,但实际上我发现所有的工作人员都在同一时间抓取一页,然后在同一时间睡觉,等等……所以他们不遵守每秒1次的限制。我在
python-2.7
上测试了这一点,因为它工作正常,在他们的网站上,他们说
tornado
python-3
的支持,但也许有一些细微的差别。