python treq
Twisted Requests( treq )包是基于流行的Twisted库构建的HTTP客户端,该库用于异步请求。 异步库提供了并行执行大量网络请求的功能,而对CPU的影响相对较小。 这在需要获得多个所需信息之前需要发出多个请求的HTTP客户端中很有用。 在本文中,我们将通过一个示例进行工作,该示例进行异步调用以使用treq进行探索。
定义要解决的问题
我喜欢玩实时策略游戏《皇家大逃杀》。 虽然它不是开源的,但它确实具有一个公共API,我们可以使用它来显示异步请求如何派上用场。
Clash Royale是一款移动战略玩家与玩家的游戏,玩家在竞技场中玩纸牌赢。 每张卡具有不同的优缺点,并且不同的玩家更喜欢不同的卡。 Clash Royale会记住玩家玩得最多的一张牌; 这是他们的“收藏夹”卡片。 玩家们聚集在一起,可以互相帮助。 Supercell是Clash Royale的开发人员,发布了基于HTTP的API,可在其中查询不同的统计信息。
您可以注册一个帐户来跟随本教程,但是如果您不这样做,仍然可以了解我们正在构建的内容。 如果您确实想注册帐户,请通过Clash Royale 开发人员门户创建API令牌。 然后,在个人资料下选择“创建新密钥”,然后输入名称,描述和有效的IP地址。 (需要一个确切的地址,因此我使用此站点来查找我的地址。)由于您永远不应在代码中保存API密钥,因此请将其作为单独的文件保存在〜/ .crtoken中 :
$
ls ~
/ .crtoken
/ home
/ moshez
/ .crtoken
扭曲的程序
运行基于Twisted的程序需要许多其他程序包,以使体验尽可能流畅。 我不会在本教程中介绍所有这些内容,但是每个内容都值得探索以了解更多信息。
为了更容易了解发生了什么,让我们从打印Hello World的介绍性程序开始,然后我们将讨论其功能:
import
collections
, json
,
os
,
sys
,
urllib .
parse
from twisted.
internet
import task
, defer
import treq
with
open
(
os .
path .
expanduser
(
"~/.crtoken"
)
)
as fpin:
token
= fpin.
read
(
) .
strip
(
)
def main
( reactor
) :
print
(
"Hello world"
)
return defer.
succeed
(
None
)
task.
react
( main
,
sys .
argv
[
1 :
]
)
这将导入比“ Hello world”示例所需的模块更多的模块。 我们将在程序的最终版本中使用这些模块,这些模块将完成异步查询API的更为复杂的任务。 导入后,程序将从文件中读取令牌并将其存储在变量token中 。 (我们现在不打算对令牌做任何事情,但是很高兴看到该语法。)接下来是一个接受Twisted React器的main函数。 React堆有点像扭曲包装的复杂机械的接口。 在这种情况下, main函数将作为参数发送,并被提供一个附加参数。
主体返回defer.succeed(None) 。 这就是它返回正确类型的值的方式:一个延迟的值,但是已经被“触发”或“调用”了。 因此,根据需要,该程序将在打印Hello world之后立即退出。
接下来,我们将研究异步函数的概念并确保 :
async
def get_clan_details
( clan
) :
print
(
"Hello world"
, clan
)
def main
( reactor
, clan
) :
return defer.
ensureDeferred
( get_clan_details
( clan
)
)
task.
react
( main
,
sys .
argv
[
1 :
]
)
在该程序中,应从相同的导入开始,我们将所有逻辑移至异步函数get_clan_details 。 就像常规函数一样, 异步函数在最后具有隐式返回None 。 但是,异步函数(有时也称为协同例程)与Deferred是不同的类型 。 为了让Twisted(自Python 1.5.2开始存在)使用此现代功能,我们必须使用sureDeferred来修改协例 。
尽管我们可以不用协例程来编写所有逻辑,但是使用异步语法将使我们能够编写更易于理解的代码,并且需要将更少的代码移入嵌入式回调中。
下一个要引入的概念是等待 。 稍后,我们将等待网络呼叫,但为简单起见,现在,我们将等待计时器。 Twisted具有一个特殊的功能task.deferLater ,它将在经过一段时间后调用具有给定参数的功能。
以下程序将花费五秒钟来完成:
async
def get_clan_details
( clan
, reactor
) :
out
= await task.
deferLater
(
reactor
,
5
,
lambda clan: f
"Hello world {clan}"
,
clan
)
print
( out
)
def main
( reactor
, clan
) :
return defer.
ensureDeferred
( get_clan_details
( clan
, reactor
)
)
task.
react
( main
,
sys .
argv
[
1 :
]
)
关于类型的注释: task.deferLater返回Deferred ,大多数不具有该值的Twisted函数也是如此。 在运行Twisted事件循环时,我们可以等待 Deferred值和协同例程。
函数task.deferLater将等待五秒钟,然后调用lambda ,计算要打印的字符串。
现在,我们有了编写高效的氏族分析程序所需的所有Twisted构建基块!
带有treq的异步调用
由于我们将使用全局React堆,因此我们不再需要将React堆作为计算这些统计数据的函数中的参数来接受:
async def get_clan_details ( clan ) :
使用令牌的方法是作为标头中的“承载”令牌:
headers = { b 'Authorization' : b 'Bearer ' + token . encode ( 'ascii' ) }
我们希望发送氏族标签,它将是字符串。 氏族标记以#开头,因此在将其放入URL之前必须对其加引号。 这是因为#具有特殊含义“ URL片段”:
clan = urllib . parse . quote ( clan )
第一步是获取氏族的详细信息,包括氏族成员:
res
= await treq.
get
(
"https://api.clashroyale.com/v1/clans/" + clan
,
headers
= headers
)
注意,我们必须等待 treq.get调用。 由于它是异步网络调用,因此我们必须明确说明何时等待和获取信息。 只是用的await语法来调用一个函数递延 不会让我们以异步的全功率(我们会看到以后怎么办吧)。
接下来,获取标题后,我们需要获取内容。 treq库为我们提供了一个直接解析JSON的辅助方法:
content = await res. json ( )
内容包括有关氏族的一些元数据(对于我们当前的目的而言并不有趣),以及包含氏族成员的memberList字段。 请注意,尽管其中包含有关玩家的一些数据,但当前收藏夹卡并不包含在其中。 它确实包含了我们可以用来检索更多数据的唯一“玩家标签”。
我们收集所有玩家标签,并且由于它们也以#开头,因此我们用URL引用它们:
player_tags
=
[
urllib .
parse .
quote
( player
[
'tag'
]
)
for player
in content
[
'memberList'
]
]
最后,我们了解了treq和Twisted的真正功能:一次生成对播放器数据的所有请求! 确实可以加快此类任务的执行速度,该任务一遍又一遍地查询API。 在具有速率限制的API的情况下,这可能会出现问题。
有时候,我们需要考虑我们的API所有者,而不要遇到任何速率限制。 在Twisted中有明确支持速率限制的技术,但它们超出了本教程的范围。 (一个重要的工具是defer.DeferredSemaphore 。)
requests
=
[ treq.
get
(
"https://api.clashroyale.com/v1/players/" + tag
,
headers
= headers
)
for tag
in player_tags
]
旁白:等待,延迟和回调
对于那些对返回的对象的细节感到好奇的人,下面仔细看看发生了什么。
请记住,请求不会直接返回JSON正文。 之前,我们使用了wait,这样我们就不必担心请求返回的确切信息了。 他们实际上返回了Deferred 。 Deferred可以具有附加的回调 ,该回调将修改Deferred。 如果回调返回 Deferred,则Deferred的最终值将是返回的Deferred的值 。
因此,对于每个延迟的对象,我们都附加一个回调,该回调将检索正文的JSON:
for request
in requests:
request.
addCallback
(
lambda result: result.
json
(
)
)
将回调附加到Deferreds是一种更手动的技术,它使代码难以遵循,但更有效地使用了异步功能。 具体来说,由于我们要同时附加所有回调,因此无需等待网络调用(可能需要很长时间)来指示如何对结果进行后处理。
从递延到价值观
在收集所有结果之前,我们无法计算最受欢迎的收藏卡。 我们有一个Deferred列表,但是我们想要的是一个Deferred ,它获得一个list值 。 这种反转正是Twisted函数defer.gatherResults所做的:
all_players = await defer. gatherResults ( requests )
这个看似无辜的电话是我们充分利用Twisted力量的地方。 该defer.gatherResults函数立即返回一个deferred当所有的成分都Deferreds解雇只会火 ,将与结果火。 它甚至为我们提供了免费的错误处理:如果出现任何Deferreds错误,它将立即返回失败的deferred,这将导致await引发异常。
现在我们已经掌握了所有玩家的详细信息,我们需要获取一些数据。 我们将使用Python最酷的内置插件之一collections.Counter 。 此类记录了事物的清单,并统计了每件事物出现的次数,这正是我们进行点票或人气竞赛所需要的:
favorite_card
=
collections .
Counter
(
[ player
[
"currentFavouriteCard"
]
[
"name"
]
for player
in all_players
]
)
最后,我们打印它:
print ( json. dumps ( favorite_card. most_common ( ) , indent = 4 ) )
放在一起
因此,将它们放在一起,我们有:
import
collections
, json
,
os
,
sys
,
urllib .
parse
from twisted.
internet
import task
, defer
import treq
with
open
(
os .
path .
expanduser
(
"~/.crtoken"
)
)
as fpin:
token
= fpin.
read
(
) .
strip
(
)
async
def get_clan_details
( clan
) :
headers
= headers
=
{ b
'Authorization' : b
'Bearer ' +
token .
encode
(
'ascii'
)
}
clan
=
urllib .
parse .
quote
( clan
)
res
= await treq.
get
(
"https://api.clashroyale.com/v1/clans/" + clan
,
headers
= headers
)
content
= await res.
json
(
)
player_tags
=
[
urllib .
parse .
quote
( player
[
'tag'
]
)
for player
in content
[
'memberList'
]
]
requests
=
[ treq.
get
(
"https://api.clashroyale.com/v1/players/" + tag
,
headers
= headers
)
for tag
in player_tags
]
for request
in requests:
request.
addCallback
(
lambda result: result.
json
(
)
)
all_players
= await defer.
gatherResults
( requests
)
favorite_card
=
collections .
Counter
(
[ player
[
"currentFavouriteCard"
]
[
"name"
]
for player
in all_players
]
)
print
( json.
dumps
( favorite_card.
most_common
(
)
, indent
=
4
)
)
def main
( reactor
, clan
) :
return defer.
ensureDeferred
( get_clan_details
( clan
)
)
task.
react
( main
,
sys .
argv
[
1 :
]
)
得益于Twisted和treq的高效性和表达语法,这就是我们对API进行异步调用所需的全部代码。 而且,如果您想知道结果如何,我的战队中最喜欢的卡片列表是降序排列的Wizard,Mega Knight,Valkyrie和Royal Giant。
希望您喜欢使用Twisted编写更快的API调用!
python treq