测试pok_使用Python和PokéAPI对繁重的API请求进行多处理

测试pok

当我从事一个最近的项目时,我意识到通过python的多处理库可以使像报废这样繁重的python进程变得更容易 从事多处理的文档和社区非常稀疏,因此我想通过一个废弃PokéAPI的示例项目来分享我的一些经验 。 在下面,我写了一些代码来提取所有可用的神奇宝贝,同时注意API每60秒的限制100次调用。 您会看到迭代非常慢,因为API返回了964个神奇宝贝。

多重处理之前

我类似地创建了三个调用get_number_pokemonget_pokemonget_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尽快完成此操作,因此可以确保程序以最佳状态运行,而不会占用我们的所有CPUsmax(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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值