使用asyncio进行多次调用并将结果添加到字典中

ant*_*rea 12 python python-3.x python-asyncio

我无法绕过Python 3的Asyncio库.我有一个zipcodes列表,我正在尝试对API进行异步调用,以使每个zipcodes对应城市和州.我可以通过for循环顺序成功完成,但我希望在大型邮政编码列表的情况下使其更快.

这是我的原创作品的一个例子

import urllib.request, json

zips = ['90210', '60647']

def get_cities(zipcodes):
    zip_cities = dict()
    for idx, zipcode in enumerate(zipcodes):
        url = 'http://maps.googleapis.com/maps/api/geocode/json?address='+zipcode+'&sensor=true'
        response = urllib.request.urlopen(url)
        string = response.read().decode('utf-8')
        data = json.loads(string)
        city = data['results'][0]['address_components'][1]['long_name']
        state = data['results'][0]['address_components'][3]['long_name']
        zip_cities.update({idx: [zipcode, city, state]})
    return zip_cities

results = get_cities(zips)
print(results)
# returns {0: ['90210', 'Beverly Hills', 'California'],
#          1: ['60647', 'Chicago', 'Illinois']}
Run Code Online (Sandbox Code Playgroud)

这是我尝试使其异步的可怕的非功能性尝试

import asyncio
import urllib.request, json

zips = ['90210', '60647']
zip_cities = dict()

@asyncio.coroutine
def get_cities(zipcodes):
    url = 'http://maps.googleapis.com/maps/api/geocode/json?address='+zipcode+'&sensor=true'
    response = urllib.request.urlopen(url)
    string = response.read().decode('utf-8')
    data = json.loads(string)
    city = data['results'][0]['address_components'][1]['long_name']
    state = data['results'][0]['address_components'][3]['long_name']
    zip_cities.update({idx: [zipcode, city, state]})

loop = asyncio.get_event_loop()
loop.run_until_complete([get_cities(zip) for zip in zips])
loop.close()
print(zip_cities) # doesnt work
Run Code Online (Sandbox Code Playgroud)

任何帮助深表感谢.我在网上遇到的所有教程似乎都让我头疼.

注意:我看过一些使用的例子aiohttp.如果可能的话,我希望坚持使用本机Python 3库.

dan*_*ano 15

如果您使用urllibHTTP请求,则无法获得任何并发性,因为它是同步库.包含调用urlliba的coroutine函数不会改变它.您必须使用集成到的异步HTTP客户端asyncio,例如aiohttp:

import asyncio
import json
import aiohttp

zips = ['90210', '60647']
zip_cities = dict()

@asyncio.coroutine
def get_cities(zipcode,idx):
    url = 'https://maps.googleapis.com/maps/api/geocode/json?key=abcdfg&address='+zipcode+'&sensor=true'
    response = yield from aiohttp.request('get', url)
    string = (yield from response.read()).decode('utf-8')
    data = json.loads(string)
    print(data)
    city = data['results'][0]['address_components'][1]['long_name']
    state = data['results'][0]['address_components'][3]['long_name']
    zip_cities.update({idx: [zipcode, city, state]})

if __name__ == "__main__":        
    loop = asyncio.get_event_loop()
    tasks = [asyncio.async(get_cities(z, i)) for i, z in enumerate(zips)]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()
    print(zip_cities)
Run Code Online (Sandbox Code Playgroud)

我知道你更喜欢只使用stdlib,但是asyncio库不包含HTTP客户端,所以你必须基本上重新实现一些部分aiohttp来重新创建它提供的功能.我想另一种选择是urllib在后台线程中进行调用,这样它们就不会阻塞事件循环,但是当aiohttp它可用时会有一些愚蠢的行为(并且有点挫败asyncio了首先使用的目的) :

import asyncio
import json
import urllib.request
from concurrent.futures import ThreadPoolExecutor

zips = ['90210', '60647']
zip_cities = dict()

@asyncio.coroutine
def get_cities(zipcode,idx):
    url = 'https://maps.googleapis.com/maps/api/geocode/json?key=abcdfg&address='+zipcode+'&sensor=true'
    response = yield from loop.run_in_executor(executor, urllib.request.urlopen, url)
    string = response.read().decode('utf-8')
    data = json.loads(string)
    print(data)
    city = data['results'][0]['address_components'][1]['long_name']
    state = data['results'][0]['address_components'][3]['long_name']
    zip_cities.update({idx: [zipcode, city, state]})

if __name__ == "__main__":
    executor = ThreadPoolExecutor(10)
    loop = asyncio.get_event_loop()
    tasks = [asyncio.async(get_cities(z, i)) for i, z in enumerate(zips)]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()
    print(zip_cities)
Run Code Online (Sandbox Code Playgroud)

  • @ anthony-dandrea如果您的邮政编码列表将是巨大的,我还建议将同时请求的数量限制为一个理智的数字,如100个连接左右,否则您可能最终被阻止.*不*从经验中说话; - ] (2认同)