测试pok
当我从事一个最近的项目时,我意识到通过python的多处理库可以使像报废这样繁重的python进程变得更容易。 从事多处理的文档和社区非常稀疏,因此我想通过一个废弃PokéAPI的示例项目来分享我的一些经验 。 在下面,我写了一些代码来提取所有可用的神奇宝贝,同时注意API每60秒的限制100次调用。 您会看到迭代非常慢,因为API返回了964个神奇宝贝。
多重处理之前
我类似地创建了三个调用get_number_pokemon
, get_pokemon
和get_all_pokemon
。 第一个是get_number_pokemon
,它简单地从api返回所有URL,以进行下一个过程get_all_pokemon
,以遍历这些URL并通过使用get_pokemon
提取每个URL的响应数据来将信息汇总在一起。
import requests as req
import timeit
import time
import pandas as pd
from IPython.display import Image, HTML
import random
from tqdm import tqdm
from ratelimit import limits, sleep_and_retry
## Rate limit to help with overcalling
## pokemon api is 100 calls per 60 seconds max
@sleep_and_retry
@limits(calls=100, period=60)
def call_api (url) :
response = req.get(url)
if response.status_code == 404 :
return 'Not Found'
if response.status_code != 200 :
print( 'here' , status_code, url)
raise Exception( 'API response: {}' .format(response.status_code))
return response
API_POKEMON = 'https://pokeapi.co/api/v2/pokemon/{pokemon}'
def get_number_pokemon () :
res = req.get(API_POKEMON.format(pokemon= '' ))
number_pokemon = res.json()[ 'count' ]
res_url = call_api(API_POKEMON.format(pokemon= '?offset=0&limit={limit}' .format(limit=str(number_pokemon))))
pokemon_links_values = [link[ 'url' ] for link in res_url.json()[ 'results' ]]
return pokemon_links_values
def get_pokemon (link= '' ) :
info = None
resolved = False
try :
while not resolved:
res = None
tooManyCalls = False
try :
res = call_api(link)
if res == 'Not Found' :
resolved = True
break
except Exception as e:
print(e)
if e == 'too many calls' :
tooManyCalls = True
if tooManyCalls:
time.sleep( 60 )
elif res.status_code < 300 :
pokemon_info = res.json()
info = {
'Image' : pokemon_info[ 'sprites' ][ 'front_default' ],
'id' : pokemon_info[ 'id' ],
'name' : pokemon_info[ 'name' ],
'height' : pokemon_info[ 'height' ],
'base_experience' : pokemon_info[ 'base_experience' ],
'weight' : pokemon_info[ 'weight' ],
'species' : pokemon_info[ 'species' ][ 'name' ]
}
resolved = True
elif res.status_code == 429 :
time.sleep( 60 )
else :
sleep_val = random.randint( 1 , 10 )
time.sleep(sleep_val)
except Exception as e:
print(e)
return info
finally :
return info
def get_all_pokemon (links_pokemon=None) :
list_pokemon = []
for link in tqdm(links_pokemon):
pokemon = get_pokemon(link)
if pokemon != None :
list_pokemon.append(pokemon)
time.sleep( 0.3 )
pd.set_option( 'display.max_colwidth' , None )
df_pokemon = pd.DataFrame(list_pokemon)
return df_pokemon
def image_formatter (im) :
return f'<img src=" {im} ">'
def main_pokemon_run () :
links_pokemon = get_number_pokemon()
df_pokemon = get_all_pokemon(links_pokemon=links_pokemon)
df_pokemon.sort_values([ 'id' ],inplace= True )
return df_pokemon, HTML(df_pokemon.iloc[ 0 : 4 ].to_html(formatters={ 'Image' : image_formatter}, escape= False ))
从下面的响应中可以看到,API调用以大约1.69次迭代/秒的速度花费了大约9分30秒。 我们可以通过添加多处理来大幅度改善这一点。
使用多处理
现在,通过多处理,我们可以将get_all_pokemon
函数分离为一个多处理池函数。 我们使用内置在多处理函数中的cpu_count()
来定义所需的工作程序数量。 由于我们希望使用完整的cpu_count - 1
尽快完成此操作,因此可以确保程序以最佳状态运行,而不会占用我们的所有CPUs
。 max(cpu_count() -1, 1)
确保该函数永远不会为0。接下来,我使用多重处理的内置Manager
工具共享我们返回的Pokémon的全局内存。
虽然可以使用多进程映射的返回值,但使用管理器是对其他更复杂的任务使用多处理的好方法。 查看文档以获取可用的多处理程序。
经理类型。 所需的另一步骤是创建局部函数。 在pool.imap
函数传递数组进行迭代时,我们需要在迭代之前将参数更具体地添加到get_pokemon_multiprocess
函数中。 functools.partial
库允许我们通过创建要发送到池图的局部函数来执行此操作。 在本示例中,我使用tqdm查看池的进度。
我还在代码中显示了没有它的相同功能。 既然池已经运行了,我们需要确保它已关闭并已加入,您将看到我在finally
块之后和之后立即执行此操作。 这样做是为了验证即使有错误,流程工作者也会被关闭并终止。
连接功能对于将池合并在一起之后的进程是必需的。 这也验证了管理器已完全完成绑定。
import requests as req
import timeit
import time
import pandas as pd
from IPython.display import Image, HTML
import random
from tqdm import tqdm
from ratelimit import limits, sleep_and_retry
from multiprocessing import Pool, Manager, cpu_count
from functools import partial
API_POKEMON = 'https://pokeapi.co/api/v2/pokemon/{pokemon}'
# To see how it ran
# def infoDebugger(title):
# print(title)
# print('module name:', __name__)
# if hasattr(os, 'getppid'):
# print('parent process:', os.getppid())
# print('process id:', os.getpid())
@sleep_and_retry
@limits(calls=100, period=60)
def call_api (url) :
response = req.get(url)
if response.status_code == 404 :
return 'Not Found'
if response.status_code != 200 :
raise Exception( 'API response: {}' .format(response.status_code))
return response
# https://docs.python.org/2/library/multiprocessing.html
def get_number_pokemon () :
res = req.get(API_POKEMON.format(pokemon= '' ))
number_pokemon = res.json()[ 'count' ]
res_url = call_api(API_POKEMON.format(pokemon= '?offset=0&limit={limit}' .format(limit=str(number_pokemon))))
pokemon_links_values = [link[ 'url' ] for link in res_url.json()[ 'results' ]]
return pokemon_links_values
def get_pokemon_multiprocess (listManager=None, links_pokemon=None, process= 0 ) :
# print('Called Pokemon', process)
link = links_pokemon[process]
info = None
resolved = False
# print(link)
try :
while not resolved:
res = None
tooManyCalls = False
try :
res = call_api(link)
if res == 'Not Found' :
resolved = True
break
except Exception as e:
print(e)
if e == 'too many calls' :
tooManyCalls = True
if tooManyCalls:
time.sleep( 60 )
elif res.status_code < 300 :
pokemon_info = res.json()
info = {
'Image' : pokemon_info[ 'sprites' ][ 'front_default' ],
'id' : pokemon_info[ 'id' ],
'name' : pokemon_info[ 'name' ],
'height' : pokemon_info[ 'height' ],
'base_experience' : pokemon_info[ 'base_experience' ],
'weight' : pokemon_info[ 'weight' ],
'species' : pokemon_info[ 'species' ][ 'name' ]
}
resolved = True
elif res.status_code == 429 :
print(res.status_code)
time.sleep( 60 )
else :
print(res.status_code)
sleep_val = random.randint( 1 , 10 )
time.sleep(sleep_val)
except Exception as e:
print(e)
finally :
if info != None :
listManager.append(info)
time.sleep( 0.5 )
return
def image_formatter (im) :
return f'<img src=" {im} ">'
def main_pokemon_run_multiprocessing () :
## cannot be 0, so max(NUMBER,1) solves this
workers = max(cpu_count() -1 , 1 )
## create the pool
manager = Manager()
## Need a manager to help get the values async, the values will be updated after join
listManager = manager.list()
pool = Pool(workers)
try :
links_pokemon = get_number_pokemon()
part_get_clean_pokemon = partial(get_pokemon_multiprocess, listManager, links_pokemon)
# could do this the below is visualize the rate success /etc
# pool.imap(part_get_clean_pokemon, list(range(0, len(links_pokemon))))
# using tqdm to see progress imap works
for _ in tqdm(pool.imap(part_get_clean_pokemon, list(range( 0 , len(links_pokemon)))), total=len(links_pokemon)):
pass
pool.close()
pool.join()
finally :
pool.close()
pool.join()
pokemonList = list(listManager)
df_pokemon = pd.DataFrame(pokemonList)
df_pokemon.sort_values([ 'id' ],inplace= True )
return df_pokemon, HTML(df_pokemon.iloc[ 0 : 4 ].to_html(formatters={ 'Image' : image_formatter}, escape= False ))
您将在下面的快照中看到池成功运行,并将运行时间和迭代时间从9分钟大幅提高到了1分钟,并将每秒迭代次数提高到了每秒14.97迭代次数。
希望这会有所帮助。 要查看完整的代码并在github上查看我的笔记本。
如果您有其他有用的用途,请告诉我,如果有,请分享对我的样本的任何反馈。
格雷森·内文斯·阿彻
翻译自: https://hackernoon.com/multiprocessing-for-heavy-api-requests-with-python-and-the-pokeapi-3u4h3ypn
测试pok