Python 和 API:读取公共数据的成功组合

举报
Yuchuan 发表于 2021/11/25 15:44:14 2021/11/25
【摘要】 关于 API,您还可以了解一百万种其他内容:不同的标头、不同的内容类型、不同的身份验证技术等等。但是,您在本教程中学到的概念和技术将允许您使用自己喜欢的任何 API 进行练习,并使用 Python 来满足您可能拥有的任何 API 使用需求。

目录

知道如何使用 API 是一种神奇的技能,一旦掌握,就会打开一个充满可能性的全新世界,而使用 Python 使用 API 是学习这种技能的好方法。

您每天使用的许多应用程序和系统都连接到 API。从非常简单和平凡的事情,比如早上查看天气,到更容易上瘾和耗时的操作,比如滚动你的 Instagram、TikTok 或 Twitter 提要,API 发挥着核心作用。

在本教程中,您将学习:

  • 什么的API
  • 如何使用Python 代码使用 API
  • 最重要的API 相关概念是什么
  • 如何使用 Python读取通过公共 API 提供的数据

在本教程结束时,您将能够使用 Python 来使用您遇到的大多数 API。如果您是一名开发人员,了解如何使用 Python 使用 API 将使您更加精通,尤其是在将您的工作与第三方应用程序集成时。

注意:本教程的重点是如何使用 Python使用API,而不是如何构建它们。有关使用 Python 构建 API 的信息,请查看使用 Flask、Connexion 和 SQLAlchemy 的 Python REST API

您可以通过单击下面的链接下载将在本教程中看到的示例的源代码:

了解 API

API 代表应用程序编程接口。本质上,API 充当通信层,或者顾名思义,一个接口,它允许不同的系统相互通信,而无需准确了解彼此的作用。

API 可以有多种形式或形状。它们可以是操作系统 API,用于诸如打开相机和音频以加入 Zoom 呼叫等操作。或者它们可以是网络 API,用于以网络为中心的操作,例如喜欢 Instagram 上的图像或获取最新的推文。

无论类型如何,所有 API 的功能大致相同。您通常会请求信息或数据,API 会根据您的请求返回响应。例如,每次打开 Twitter 或向下滚动 Instagram 提要时,您基本上都是向该应用程序背后的 API 发出请求并获得响应作为回报。这也称为调用API。

在本教程中,您将更多地关注跨网络通信的高级 API,也称为Web API

SOAP 与 REST 与 GraphQL

尽管上面提到的一些示例是针对较新的平台或应用程序,但 Web API 已经存在了很长时间。在 1990 年代末和 2000 年代初,两种不同的设计模型成为公开数据的常态:

  1. SOAP(简单对象访问协议)通常与企业世界相关联,具有更严格的基于契约的用法,并且主要围绕操作而设计。
  2. REST(具象状态传输)通常用于公共 API,是从 Web 获取数据的理想选择。它比 SOAP 轻得多,也更接近 HTTP 规范。

如今,镇上有一个新孩子:GraphQL。GraphQL 由 Facebook 创建,是一种非常灵活的 API 查询语言,客户端可以准确地决定他们想要从服务器获取什么,而不是服务器决定发送什么。

如果您想更多地了解这三种设计模型之间的差异,那么这里有一些很好的资源:

尽管 GraphQL 正在兴起并被越来越大的公司采用,包括GitHubShopify,但事实是大多数公共 API 仍然是 REST API。因此,就本教程而言,您将仅了解 REST API 以及如何使用 Python 使用它们。

requests 和 API:天作之合

使用 Python 使用 API 时,您只需要一个库:requests. 有了它,您应该能够执行使用任何公共 API 所需的大部分(如果不是全部)操作。

您可以requests通过在控制台中运行以下命令来安装:

$ python -m pip install requests

要遵循本教程中的代码示例,请确保您使用的是 Python 3.8.1 和requests2.24.0 或更高版本。

使用 Python 调用您的第一个 API

话不多说——是时候进行您的第一个 API 调用了!对于第一个示例,您将调用一个流行的 API 来生成随机用户数据

在整个教程中,您将看到警报块中引入的新 API,如下所示。这是一种方便的方式,您可以在之后滚动浏览并快速找到您了解的所有新 API。

Random User Generator API:这是一个生成随机用户数据的好工具。您可以使用它来生成任意数量的随机用户和相关数据,还可以指定性别、国籍和许多其他过滤器,这些过滤器在测试应用程序或在这种情况下是 API 时非常有用。

开始使用 Random User Generator API 时,您唯一需要知道的是使用哪个 URL 调用它。对于此示例,要使用的 URL 是https://randomuser.me/api/,这是您可以进行的最小的 API 调用:

>>>
>>> import requests
>>> requests.get("https://randomuser.me/api/")
<Response [200]>

在这个小例子,你导入requests库,然后从URL中随机用户生成器API获取(或获得)的数据。但是您实际上看不到任何返回的数据。你得到的是 a Response [200],在 API 术语中意味着一切正常。

如果要查看实际数据,则可以.text从返回的Response对象中使用:

>>>
>>> import requests
>>> response = requests.get("https://randomuser.me/api/")
>>> response.text
'{"results":[{"gender":"female",
"name":{"title":"Ms","first":"Isobel","last":"Wang"}...'

就是这样!这就是 API 消费的基础知识。您设法使用 Python 和requests库从随机用户生成器 API 中获取了您的第一个随机用户。

端点和资源

正如您在上面看到的,使用 API 需要知道的第一件事是 API URL,通常称为基本 URL。基本 URL 结构与您用于浏览 Google、YouTube 或 Facebook 的 URL 没有什么不同,尽管它通常包含单词api。这不是强制性的,只是更多的经验法则。

例如,以下是一些知名 API 播放器的基本 URL:

  • https://api.twitter.com
  • https://api.github.com
  • https://api.stripe.com

如您所见,以上所有内容均以https://api并包括剩余的官方域,例如.twitter.com.github.com。API 基本 URL 的外观没有特定的标准,但它模仿这种结构是很常见的。

如果您尝试打开上述任何链接,您会注意到它们中的大多数都会返回错误或要求提供凭据。这是因为 API 有时需要身份验证步骤才能使用它们。您将了解更多有关这一点以后的教程。

TheDogAPI:这个 API 很有趣,但也是一个很好的例子,它是一个很好的 API 和很好的文档。有了它,你可以获取不同的狗品种和一些图像,但如果你注册,你也可以为你最喜欢的狗投票。

接下来,使用刚刚引入的TheDogAPI,您将尝试发出一个基本请求,以查看它与您在上面尝试过的 Random User Generator API 有何不同:

>>>
>>> import requests
>>> response = requests.get("https://api.thedogapi.com/")
>>> response.text
'{"message":"The Dog API"}'

在这种情况下,当调用基本 URL 时,您会收到一条通用消息,内容为The Dog API。这是因为您正在调用基本 URL,它通常用于有关 API 的非常基本的信息,而不是真实数据。

单独调用基本 URL 并不是很有趣,但这就是端点派上用场的地方。一个端点是指定哪些URL的一部分资源,你想获取。有据可查的 API 通常包含API 参考,这对于了解 API 的确切端点和资源以及如何使用它们非常有用。

您可以查看官方文档以了解有关如何使用 TheDogAPI 以及可用端点的更多信息。在那里,您会找到一个/breeds端点,您可以使用它来获取所有可用的品种资源或对象。

如果向下滚动,您将找到发送测试请求部分,您将在其中看到如下所示的表单:

使用 Python 使用 API:文档示例

这是您将在许多 API 文档中看到的内容:一种直接从文档页面快速测试 API 的方法。在这种情况下,您可以单击发送以快速获取调用该端点的结果。等等,您只需调用 API,而无需为其编写任何代码。

现在,使用品种端点和您已经拥有的一些 API 知识在本地尝试代码:

>>>
>>> response = requests.get("https://api.thedogapi.com/v1/breeds")
>>> response.text
'[{"weight":{"imperial":"6 - 13","metric":"3 - 6"},"height": ...}]'

好了,您使用狗 API 列出了您的第一个品种!

如果您是爱猫人士,请不要担心。还有一个 API 供您使用,它具有相同的端点但具有不同的基本 URL:

>>>
>>> response = requests.get("https://api.thecatapi.com/v1/breeds")
>>> response.text
'[{..."id":"abys","name":"Abyssinian"}]'

我敢打赌,您已经在考虑使用这些 API 来制作一些可爱的副项目的不同方式,这就是 API 的伟大之处。一旦您开始使用它们,没有什么能阻止您将爱好或激情变成有趣的小项目。

在你向前走,有一件事你需要了解端点之间的差别http://https://。简而言之,HTTPS 是 HTTP 的加密版本,使客户端和服务器之间的所有流量更加安全。在使用公共 API 时,您绝对应该避免向http://端点发送任何私人或敏感信息,并仅使用那些提供安全https://基本 URL 的API 。

有关在线浏览时坚持使用 HTTPS 的重要性的更多信息,请查看使用 Python 探索 HTTPS

在下一部分中,您将更深入地了解 API 调用的主要组件。

请求和响应

正如您在上面简要阅读的那样,客户端(在本例中是您的 Python 控制台)和 API 之间的所有交互都分为请求和响应:

  • 请求包含有关您的 API 请求调用的相关数据,例如基本 URL、端点、使用的方法、标头等。
  • 响应包含服务器返回的相关数据,包括数据或内容、状态代码和标头。

再次使用 TheDogAPI,您可以深入了解RequestResponse对象内部的确切内容:

>>>
>>> response = requests.get("https://api.thedogapi.com/v1/breeds")
>>> response
<Response [200]>
>>> response.request
<PreparedRequest [GET]>

>>> request = response.request
>>> request.url
'https://api.thedogapi.com/v1/breeds'
>>> request.path_url
'/v1/breeds'
>>> request.method
'GET'
>>> request.headers
{'User-Agent': 'python-requests/2.24.0', 'Accept-Encoding': 'gzip, deflate',
'Accept': '*/*', 'Connection': 'keep-alive'}

>>> response
<Response [200]>
>>> response.text
'[{"weight":{"imperial":"6 - 13","metric":"3 - 6"},
"height":{"imperial":"9 - 11.5","metric":"23 - 29"},"id":1,
"name":"Affenpinscher", ...}]'
>>> response.status_code
200
>>> response.headers
{'Cache-Control': 'post-check=0, pre-check=0', 'Content-Encoding': 'gzip',
'Content-Type': 'application/json; charset=utf-8',
'Date': 'Sat, 25 Jul 2020 17:23:53 GMT'...}

上面的示例向您展示了可用于RequestResponse对象的一些最重要的属性。

您将在本教程中了解有关其中一些属性的更多信息,但如果您想进一步挖掘,那么您可以查看 Mozilla 的HTTP 消息文档,以获得对每个属性的更深入解释。

状态代码

状态代码是在任何 API 响应中寻找的最重要的信息之一。它们会告诉您您的请求是否成功、是否缺少数据、是否缺少凭据等等

随着时间的推移,您会在没有帮助的情况下识别不同的状态代码。但就目前而言,这里列出了您会发现的一些最常见的状态代码:

状态码 描述
200 OK 您的请求成功!
201 Created 您的请求已被接受,资源已创建。
400 Bad Request 您的请求有误或缺少某些信息。
401 Unauthorized 您的请求需要一些额外的权限。
404 Not Found 请求的资源不存在。
405 Method Not Allowed 端点不允许使用该特定 HTTP 方法。
500 Internal Server Error 您的请求出乎意料,可能在服务器端破坏了某些内容。

200 OK之前在执行的示例中看到过,您甚至可以404 Not Found通过浏览网页来识别。

有趣的事实:公司倾向于将404错误页面用于私人笑话或纯粹的乐趣,如下面的示例:

但是,在 API 世界中,开发人员对这种乐趣的响应空间有限。但它们在其他地方弥补了这一点,比如 HTTP 标头。您很快就会看到一些示例!

您可以使用.status_code和来检查响应的状态.reason。该requests库还在Response对象的表示中打印状态代码:

>>>
>>> response = requests.get("https://api.thedogapi.com/v1/breeds")
>>> response
<Response [200]>
>>> response.status_code
200
>>> response.reason
'OK'

上面的请求返回200,所以你可以认为它是一个成功的请求。但是现在看看当您在端点中包含拼写错误时触发的失败请求/breedz

>>>
>>> response = requests.get("https://api.thedogapi.com/v1/breedz")
>>> response
<Response [404]>
>>> response.status_code
404
>>> response.reason
'Not Found'

如您所见,/breedz端点不存在,因此 API 返回一个404 Not Found状态代码。

您可以使用这些状态代码快速查看您的请求是否需要更改,或者您是否应该再次检查文档以查找任何拼写错误或缺失的部分。

HTTP 标头

HTTP 标头用于定义一些管理请求和响应的参数:

HTTP 标头 描述
Accept 客户端可以接受什么类型的内容
Content-Type 服务器将响应什么类型的内容
User-Agent 客户端使用什么软件与服务器通信
Server 服务器使用什么软件与客户端通信
Authentication 谁在调用 API 以及他们拥有哪些凭据

在检查请求或响应时,您还可以找到许多其他标头。如果您对每个的具体用途感到好奇,请查看Mozilla 的扩展列表

要检查响应的标头,您可以使用response.headers

>>>
>>> response = requests.get("https://api.thedogapi.com/v1/breeds/1")
>>> response.headers
{'Content-Encoding': 'gzip',
'Content-Type': 'application/json; charset=utf-8',
'Date': 'Sat, 25 Jul 2020 19:52:07 GMT'...}

要对请求标头执行相同操作,您可以使用response.request.headers因为requestResponse对象的属性:

>>>
>>> response = requests.get("https://api.thedogapi.com/v1/breeds/1")
>>> response.request.headers
{'User-Agent': 'python-requests/2.24.0',
'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*',
'Connection': 'keep-alive'}

在这种情况下,您在发出请求时没有定义任何特定的标头,因此会返回默认标头。

自定义标题

您在使用 API 时可能会遇到的另一个标准是使用自定义标头。这些通常以 开头X-,但不是必需的。API 开发人员通常使用自定义标头向客户端发送或请求其他自定义信息。

有趣的事实:一些公司不遗余力地变得有趣和创新,以一种他们不应该使用的方式使用 HTTP 标头,例如征求工作申请。

您可以使用字典来定义标头,并且可以使用 的headers参数将它们与您的请求一起发送.get()

例如,假设您想向 API 服务器发送一些请求 ID,并且您知道可以使用X-Request-Id

>>>
>>> headers = {"X-Request-Id": "<my-request-id>"}
>>> response = requests.get("https://example.org", headers=headers)
>>> response.request.headers
{'User-Agent': 'python-requests/2.24.0', 'Accept-Encoding': 'gzip, deflate',
'Accept': '*/*', 'Connection': 'keep-alive',
'X-Request-Id': '<my-request-id>'}

如果你翻阅request.headers字典,你会X-Request-Id在最后找到一些其他的标头,这些标头是任何 API 请求的默认标头。

响应可能有许多有用的标头,但最重要的标头之一是Content-Type,它定义了响应中返回的内容类型。

Content-Type

现在,大多数 API 使用JSON作为默认内容类型,但您可能需要使用返回 XML 或其他媒体类型(例如图像或视频)的 API。在这种情况下,内容类型会有所不同。

如果您回顾之前使用 TheDogAPI 的示例之一并尝试检查Content-Type标头,那么您会注意到它是如何定义为application/json

>>>
>>> response = requests.get("https://api.thedogapi.com/v1/breeds/1")
>>> response.headers.get("Content-Type")
'application/json; charset=utf-8'

除了特定类型的内容(在本例中application/json)之外,标头还可能返回响应内容的指定编码

PlaceGOAT API:这是一个非常愚蠢的 API,它返回不同大小的山羊图像,您可以将其用作网站中的占位符图像。

例如,如果您尝试从PlaceGOAT API获取山羊的图像,那么您会注意到内容类型不再是application/json,而是被定义为image/jpeg

>>>
>>> response = requests.get("http://placegoat.com/200/200")
>>> response
<Response [200]>
>>> response.headers.get("Content-Type")
'image/jpeg'

在这种情况下,Content-Type标题声明返回的内容是 JPEG 图像。您将在下一节中了解如何查看此内容。

Content-Type头部是非常重要的,你要知道如何处理的响应和如何处理它的内容。还有数百种其他可接受的内容类型,包括音频、视频、字体等。

回复内容

正如您刚刚了解到的,您在 API 响应中找到的内容类型将根据Content-Type标头而有所不同。为了根据不同的Content-Type标头正确读取响应内容,该requests包附带了几个不同的Response属性,您可以使用它们来操作响应数据:

您已经使用了.text上面的属性。但是对于某些特定类型的数据,例如图像和其他非文本数据,使用.content通常是更好的方法,即使它返回的结果与 非常相似.text

>>>
>>> response = requests.get("https://api.thedogapi.com/v1/breeds/1")
>>> response.headers.get("Content-Type")
'application/json; charset=utf-8'
>>> response.content
b'{"weight":{"imperial":"6 - 13","metric":"3 - 6"}...'

如您所见,.content与之前使用的.text.

但是,通过查看响应的Content-Type标头,您可以看到内容是application/json;JSON 对象。对于此类内容,该requests库包含一种特定.json()方法,您可以使用该方法将 API 字节响应立即转换为Python 数据结构

>>>
>>> response = requests.get("https://api.thedogapi.com/v1/breeds/1")
>>> response.headers.get("Content-Type")
'application/json; charset=utf-8'
>>> response.json()
{'weight': {'imperial': '6 - 13', 'metric': '3 - 6'},
'height': {'imperial': '9 - 11.5', 'metric': '23 - 29'}
...}
>>> response.json()["name"]
'Affenpinscher'

如您所见,在执行 之后response.json(),您将获得一个字典,您可以像使用 Python 中的任何其他字典一样使用它。

现在,回顾最近使用 PlaceGOAT API 运行的示例,尝试获取相同的山羊图像并查看其内容:

>>>
>>> response = requests.get("http://placegoat.com/200/200")
>>> response
<Response [200]>
>>> response.headers.get("Content-Type")
'image/jpeg'
>>> response.content
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\...'

在这种情况下,因为您请求的是图像,.content所以不是很有帮助。事实上,这几乎是不可能理解的。但是,您知道这是一个 JPEG 图像,因此您可以尝试将其存储到一个文件中,看看会发生什么:

>>>
>>> response = requests.get("http://placegoat.com/200/200")
>>> response
<Response [200]>
>>> response.headers.get("Content-Type")
'image/jpeg'
>>> file = open("goat.jpeg", "wb")
>>> file.write(response.content)
>>> file.close()

现在,如果您打开正在工作的文件夹,则会找到一个goat.jpeg文件,该文件是您刚刚使用 API 获取的山羊的随机图像。是不是很神奇?

HTTP 方法

调用 API 时,有几种不同的方法,也称为动词,可用于指定要执行的操作。例如,如果你想获取一些数据,你会使用方法GET,如果你想创建一些数据,那么你会使用方法POST

当纯粹使用 API 消费数据时,您通常会坚持使用GET请求,但这里列出了最常见的方法及其典型用例:

HTTP 方法 描述 请求方法
POST 创建新资源。 requests.post()
GET 阅读现有资源。 requests.get()
PUT 更新现有资源。 requests.put()
DELETE 删除现有资源。 requests.delete()

这四种方法通常被称作CRUD操作,因为他们让你ç reate,[R EAD,ü PDATE和d elete资源。

注意:还有一个额外的PATCH方法也与 CRUD 操作相关联,但它比上面的四个方法稍微少一些。它用于进行部分修改,而不是使用 完全替换资源PUT

您可以阅读更多关于两者之间PUTPATCH差异了解他们不同需求的信息。

如果您对其余的 HTTP 方法感到好奇,或者您只是想了解更多关于已经提到的方法,那么请查看Mozilla 的文档

到目前为止,您只用于.get()获取数据,但您也可以将requests包用于所有其他 HTTP 方法:

>>>
>>> requests.post("https://api.thedogapi.com/v1/breeds/1")
>>> requests.get("https://api.thedogapi.com/v1/breeds/1")
>>> requests.put("https://api.thedogapi.com/v1/breeds/1")
>>> requests.delete("https://api.thedogapi.com/v1/breeds/1")

如果您在控制台上尝试这些,那么您会注意到它们中的大多数都会返回405 Method Not Allowed 状态代码。这是因为不是所有的终端将允许POSTPUTDELETE方法。特别是当您使用公共 API 读取数据时,您会发现大多数 API 只允许GET请求,因为您不允许创建或更改现有数据。

查询参数

有时,当您调用 API 时,您会获得大量不需要或不需要的数据。例如,在调用 TheDogAPI 的/breeds端点时,您会获得有关给定品种的大量信息。但在某些情况下,您可能只想提取有关给定品种的某些信息。这就是查询参数的用武之地!

在线浏览时,您可能已经看到或使用过查询参数。例如,在观看 YouTube 视频时,您有一个类似https://www.youtube.com/watch?v=aL5GK2LVMWI. 将v=在URL就是你所说的查询参数。它通常在基本 URL 和端点之后。

要将查询参数添加到给定的 URL,您必须?在第一个查询参数之前添加一个问号 ( )。如果你想在你的请求中有多个查询参数,那么你可以用 & 号 ( &)将它们分开。

同样的YouTube网址上面有多个查询参数是这样的:https://www.youtube.com/watch?v=aL5GK2LVMWI&t=75

在 API 世界中,查询参数用作过滤器,您可以随 API 请求一起发送,以进一步缩小响应范围。例如,回到 Random User Generator API,您知道如何生成随机用户:

>>>
>>> requests.get("https://randomuser.me/api/").json()
{'results': [{'gender': 'male', 'name':
{'title': 'Mr', 'first': 'Silvijn', 'last': 'Van Bekkum'},
'location': {'street': {'number': 2480, 'name': 'Hooijengastrjitte'},
'city': 'Terherne', 'state': 'Drenthe',
'country': 'Netherlands', 'postcode': 59904...}

但是,假设您特别想只生成随机的女性用户。根据文档,您可以使用查询参数gender=

>>>
>>> requests.get("https://randomuser.me/api/?gender=female").json()
{'results': [{'gender': 'female', 'name':
{'title': 'Mrs', 'first': 'Marjoleine', 'last': 'Van Huffelen'},
'location': {'street': {'number': 8993, 'name': 'De Teebus'},
'city': 'West-Terschelling', 'state': 'Limburg',
'country': 'Netherlands', 'postcode': 24241...}

那太棒了!现在假设您只想生成来自德国的女性用户。再次查看文档,您会找到关于nationality的部分,您可以使用查询参数nat=

>>>
>>> requests.get("https://randomuser.me/api/?gender=female&nat=de").json()
{'results': [{'gender': 'female', 'name':
{'title': 'Ms', 'first': 'Marita', 'last': 'Hertwig'},
'location': {'street': {'number': 1430, 'name': 'Waldstraße'},
'city': 'Velden', 'state': 'Rheinland-Pfalz',
'country': 'Germany', 'postcode': 30737...}

使用查询参数,您可以开始从 API 获取更具体的数据,从而使整个体验更加符合您的需求。

为了避免一遍又一遍地重建 URL,您可以使用该params属性发送所有查询参数的字典以附加到 URL:

>>>
>>> query_params = {"gender": "female", "nat": "de"}
>>> requests.get("https://randomuser.me/api/", params=query_params).json()
{'results': [{'gender': 'female', 'name':
{'title': 'Ms', 'first': 'Janet', 'last': 'Weyer'},
'location': {'street': {'number': 2582, 'name': 'Meisenweg'},
'city': 'Garding', 'state': 'Mecklenburg-Vorpommern',
'country': 'Germany', 'postcode': 56953...}

您可以将上述内容应用于您喜欢的任何其他 API。如果您返回 TheDogAPI,文档中有一种方法可以让您过滤品种端点以仅返回与特定名称匹配的品种。例如,如果您想查找拉布拉多犬品种,那么您可以使用查询参数来实现q

>>>
>>> query_params = {"q": "labradoodle"}
>>> endpoint = "https://api.thedogapi.com/v1/breeds/search"
>>> requests.get(endpoint, params=query_params).json()
[{'weight': {'imperial': '45 - 100', 'metric': '20 - 45'},
'height': {'imperial': '14 - 24', 'metric': '36 - 61'},
'id': 148, 'name': 'Labradoodle', 'breed_group': 'Mixed'...}]

你有它!通过发送q带有 value的查询参数labradoodle,您可以过滤与该特定值匹配的所有品种。

提示:当您重用相同的端点时,最佳做法是将其定义为代码顶部的变量。当您反复与 API 交互时,这将使您的生活更轻松。

在查询参数的帮助下,您可以进一步缩小请求的范围并准确指定您要查找的内容。您可以在网上找到的大多数 API 都有某种查询参数,您可以使用它们来过滤数据。请记住查看文档和 API 参考以找到它们。

学习高级 API 概念

现在您已经很好地了解了使用 Python 使用 API 的基础知识,还有一些更高级的主题值得探讨,即使是简短的,例如身份验证分页速率限制

验证

API 身份验证可能是本教程中涵盖的最复杂的主题。尽管许多公共 API 是免费且完全公开的,但在某种形式的身份验证背后还有更多的 API 可用。有许多需要身份验证的 API,但这里有一些很好的例子:

身份验证方法的范围从简单明了(例如使用 API 密钥或基本身份验证的方法)到更复杂和更安全的技术(例如 OAuth)。

通常,在没有凭据或使用错误凭据的情况下调用 API 将返回401 Unauthorized403 Forbidden状态代码。

API 密钥

最常见的身份验证级别是API 密钥。这些密钥用于将您识别为 API 用户或客户并跟踪您对 API 的使用。API 密钥通常作为请求标头或查询参数发送。

NASA API:最酷的公开 API 集合之一是由NASA提供的 API 。您可以找到 API 来获取当天天文图片地球多色成像相机 (EPIC)拍摄的图片等。

对于此示例,您将使用 NASA 的Mars Rover Photo API,并获取 2020 年 7 月 1 日拍摄的照片。出于测试目的,您可以使用DEMO_KEYNASA 默认提供的API 密钥。否则,您可以通过转到 NASA 的主 API 页面并单击开始使用来快速生成您自己的API

您可以通过附加api_key=查询参数将 API 密钥添加到您的请求中:

>>>
>>> endpoint = "https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos"
>>> # Replace DEMO_KEY below with your own key if you generated one.
>>> api_key = "DEMO_KEY"
>>> query_params = {"api_key": api_key, "earth_date": "2020-07-01"}
>>> response = requests.get(endpoint, params=query_params)
>>> response
<Response [200]>

到现在为止还挺好。您设法向 NASA 的 API 发出经过身份验证的请求并获得200 OK响应。

现在看看这个Response对象并尝试从中提取一些图片:

>>>
>>> response.json()
{'photos': [{'id': 754118,
   'sol': 2809,
   'camera': {'id': 20,
    'name': 'FHAZ',
    'rover_id': 5,
    'full_name': 'Front Hazard Avoidance Camera'},
   'img_src': 'https://mars.nasa.gov/msl-raw-images/...JPG',
   'earth_date': '2020-07-01',
   'rover': {'id': 5,
    'name': 'Curiosity',
    'landing_date': '2012-08-06',
    'launch_date': '2011-11-26',
    'status': 'active'}},
  ...
}
>>> photos = response.json()["photos"]
>>> print(f"Found {len(photos)} photos")
Found 12 photos
>>> photos[4]["img_src"]
'https://mars.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/02809/opgs/edr/rcam/RRB_646869036EDR_F0810628RHAZ00337M_.JPG'

使用.json()将响应转换为 Python 字典,然后photos从响应中获取字段,您可以遍历所有Photo对象,甚至获取特定照片的图像 URL。如果您在浏览器中打开该网址,然后你会看到火星由一个采取了以下图片火星探测器

使用 Python 使用 API:Mars Rover Picture

对于此示例,您从响应字典 ( ) 中选择了一个特定的earth_date2020-07-01),然后选择了一张特定的照片4。在继续之前,尝试更改日期或从不同的相机获取照片,看看它如何改变最终结果。

OAuth:入门

API 身份验证中另一个非常常见的标准是OAuth。在本教程中,您将只学习 OAuth 的基本知识,因为这是一个非常广泛的主题。

即使您不知道它是 OAuth 的一部分,您也可能已经多次看到并使用过 OAuth 流程。每当应用程序或平台具有登录方式继续方式选项时,这就是 OAuth 流程的起点:

使用 Python 使用 API:OAuth 登录示例

以下是单击继续使用 Facebook会发生的情况的分步细分:

  1. Spotify 应用程序将要求 Facebook API 启动身份验证流程。为此,Spotify 应用程序将发送其应用程序 ID ( client_id) 和 URL ( redirect_uri) 以在成功或错误后重定向用户。

  2. 您将被重定向到 Facebook 网站,并要求您使用您的凭据登录。Spotify 应用程序不会看到或无法访问这些凭据。这是 OAuth 最重要的好处。

  3. Facebook 将向您显示 Spotify 应用程序从您的个人资料中请求的所有数据,并要求您接受或拒绝共享该数据。

  4. 如果您接受让 Spotify 访问您的数据,那么您将被重定向回已登录的 Spotify 应用程序。

在执行第 4 步时,Facebook 将向 Spotify 提供一个特殊凭据 ( access_token),可以重复使用它来获取您的信息。这个特定的 Facebook 登录令牌的有效期为 60 天,但其他应用程序可能有不同的有效期。如果您好奇,那么 Facebook 有一个设置页面,您可以查看哪些应用程序已获得您的 Facebook 访问令牌。

现在,从更技术的角度来看,以下是您在使用 OAuth 使用 API 时需要了解的事项:

  • 您需要创建一个具有 ID(app_idclient_id)和机密(app_secretclient_secret)的应用程序。
  • 您需要有一个重定向 URL ( redirect_uri),API 将使用它向您发送信息。
  • 您将获得一个作为身份验证结果的代码,您需要用它来交换访问令牌。

上面有一些变体,但一般来说,大多数 OAuth 流程都有与这些类似的步骤。

提示:当您只是进行测试并且需要某种重定向 URL 来获取 . 时code,您可以使用名为httpbin的服务。

更具体地说,您可以将其https://httpbin.org/anything用作重定向 URL,因为它只会输出它作为输入获取的任何内容。您可以通过导航到该 URL 来自行测试。

接下来,您将深入研究使用 GitHub API 的示例!

OAuth:一个实际例子

正如您在上面看到的,您需要做的第一件事是创建一个应用程序。您可以参考GitHub 文档中关于如何执行此操作的详细分步说明。唯一要记住的是将https://httpbin.org/anything上面提到的URL 用于授权回调 URL字段。

GitHub API:您可以将GitHub API用于许多不同的用例,例如获取您所属的存储库列表、获取您拥有的关注者列表等等。

创建应用程序后,将 和Client_ID以及Client_Secret您选择的重定向 URL复制并粘贴到名为 的 Python 文件中github.py

import requests

# REPLACE the following variables with your Client ID and Client Secret
CLIENT_ID = "<REPLACE_WITH_CLIENT_ID>"
CLIENT_SECRET = "<REPLACE_WITH_CLIENT_SECRET>"

# REPLACE the following variable with what you added in the
# "Authorization callback URL" field
REDIRECT_URI = "<REPLACE_WITH_REDIRECT_URI>"

现在您已准备好所有重要变量,您需要能够创建一个链接以将用户重定向到他们的 GitHub 帐户,如GitHub 文档中所述

def create_oauth_link():
    params = {
        "client_id": CLIENT_ID,
        "redirect_uri": REDIRECT_URI,
        "scope": "user",
        "response_type": "code",
    }

    endpoint = "https://github.com/login/oauth/authorize"
    response = requests.get(endpoint, params=params)
    url = response.url
    return url

在这段代码中,您首先定义 API 期望的必需参数,然后使用requests包和.get().

当您向/login/oauth/authorize端点发出请求时,API 会自动将您重定向到 GitHub 网站。在这种情况下,您希望url从响应中获取参数。此参数包含 GitHub 将您重定向到的确切 URL。

授权流程的下一步是将您获得的代码交换为访问令牌。同样,按照GitHub 文档中的步骤,您可以为它创建一个方法:

def exchange_code_for_access_token(code=None):
    params = {
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "redirect_uri": REDIRECT_URI,
        "code": code,
    }

    headers = {"Accept": "application/json"}
    endpoint = "https://github.com/login/oauth/access_token"
    response = requests.post(endpoint, params=params, headers=headers).json()
    return response["access_token"]

在这里,您POST请求交换访问令牌的代码。在此请求中,您必须发送您的CLIENT_SECRETandcode以便 GitHub 可以验证此特定代码最初是由您的应用程序生成的。只有这样,GitHub API 才会生成有效的访问令牌并将其返回给您。

现在您可以将以下内容添加到您的文件中并尝试运行它:

link = create_oauth_link()
print(f"Follow the link to start the authentication with GitHub: {link}")
code = input("GitHub code: ")
access_token = exchange_code_for_access_token(code)
print(f"Exchanged code {code} with access token: {access_token}")

如果一切按计划进行,那么您应该获得一个有效的访问令牌作为奖励,您可以使用它来调用 GitHub API,模拟经过身份验证的用户。

现在尝试添加以下代码以使用User API获取您的用户配置文件并打印您的姓名、用户名和私有存储库的数量:

def print_user_info(access_token=None):
    headers = {"Authorization": f"token {access_token}"}
    endpoint = "https://api.github.com/user"
    response = requests.get(endpoint, headers=headers).json()
    name = response["name"]
    username = response["login"]
    private_repos_count = response["total_private_repos"]
    print(
        f"{name} ({username}) | private repositories: {private_repos_count}"
    )

现在您拥有有效的访问令牌,您需要使用Authorization标头在所有 API 请求中发送它。对您的请求的响应将是一个包含所有用户信息的 Python 字典。从该字典,你想获取的字段namelogin以及total_private_repos。您还可以打印response变量以查看其他可用字段。

好吧,应该是这样!剩下唯一要做的就是把它们放在一起并尝试一下:

 1import requests
 2
 3# REPLACE the following variables with your Client ID and Client Secret
 4CLIENT_ID = "<REPLACE_WITH_CLIENT_ID>"
 5CLIENT_SECRET = "<REPLACE_WITH_CLIENT_SECRET>"
 6
 7# REPLACE the following variable with what you added in
 8# the "Authorization callback URL" field
 9REDIRECT_URI = "<REPLACE_WITH_REDIRECT_URI>"
10
11def create_oauth_link():
12    params = {
13        "client_id": CLIENT_ID,
14        "redirect_uri": REDIRECT_URI,
15        "scope": "user",
16        "response_type": "code",
17    }
18    endpoint = "https://github.com/login/oauth/authorize"
19    response = requests.get(endpoint, params=params)
20    url = response.url
21    return url
22
23def exchange_code_for_access_token(code=None):
24    params = {
25        "client_id": CLIENT_ID,
26        "client_secret": CLIENT_SECRET,
27        "redirect_uri": REDIRECT_URI,
28        "code": code,
29    }
30    headers = {"Accept": "application/json"}
31    endpoint = "https://github.com/login/oauth/access_token"
32    response = requests.post(endpoint, params=params, headers=headers).json()
33    return response["access_token"]
34
35def print_user_info(access_token=None):
36    headers = {"Authorization": f"token {access_token}"}
37    endpoint = "https://api.github.com/user"
38    response = requests.get(endpoint, headers=headers).json()
39    name = response["name"]
40    username = response["login"]
41    private_repos_count = response["total_private_repos"]
42    print(
43        f"{name} ({username}) | private repositories: {private_repos_count}"
44    )
45
46link = create_oauth_link()
47print(f"Follow the link to start the authentication with GitHub: {link}")
48code = input("GitHub code: ")
49access_token = exchange_code_for_access_token(code)
50print(f"Exchanged code {code} with access token: {access_token}")
51print_user_info(access_token=access_token)

运行上面的代码时会发生以下情况:

  1. 会生成一个链接,要求您转到 GitHub 页面进行身份验证。
  2. 按照该链接并使用您的 GitHub 凭据登录后,您将被重定向到您定义的回调 URL,其中包含code查询参数中的一个字段:

使用 Python 使用 API:Github OAuth 代码

  1. 在您的控制台中粘贴该代码后,您可以将代码交换为可重用的访问令牌。

  2. 您的用户信息是使用该访问令牌获取的。打印您的姓名、用户名和私有存储库计数。

如果您按照上述步骤操作,那么您应该得到与此类似的最终结果:

$ John Doe (johndoe) | number of private repositories: 42

这里有很多步骤需要执行,但重要的是您要花时间真正理解每个步骤。大多数使用 OAuth 的 API 将共享许多相同的行为,因此当您从 API 读取数据时,充分了解此过程将释放很多潜力。

随意改进此示例并添加更多功能,例如获取您的公开和已加星标的存储库或遍历您的关注者以识别最受欢迎的存储库。

网上有很多关于 OAuth 的优秀资源,如果使用 OAuth 背后的 API 是您真正需要的,那么我建议您专门对该主题进行更多研究。以下是一些不错的起点:

从 API 消费的角度来看,当您与公共 API 交互时,了解 OAuth 肯定会派上用场。大多数 API 都采用 OAuth 作为其身份验证标准,这是有充分理由的。

分页

在客户端和服务器之间来回发送大量数据是有代价的:带宽。为了确保服务器可以处理大量请求,API 通常使用分页

用非常简单的术语来说,分页是将大量数据分成多个更小的部分的行为。例如,每当您转到Stack Overflow 中的问题页面时,您都会在底部看到如下内容:

使用 Python 使用 API:分页示例

您可能从许多其他网站上认识到这一点,并且不同网站的概念大致相同。具体对于API来说,这通常是借助查询参数来处理的,主要有以下两个:

  1. page,您目前正在请求定义哪些页面属性
  2. size定义每个页的大小属性

具体的查询参数名称可能因 API 开发人员的不同而有很大差异,但概念是相同的。一些 API 播放器也可能使用 HTTP 标头或 JSON 响应来返回当前的分页过滤器。

再次使用 GitHub API,您可以在包含分页查询参数的文档中找到事件端点。该参数per_page=定义要返回的项目数,并page=允许您对多个结果进行分页。以下是如何使用这些参数:

>>>
>>> response = requests.get("https://api.github.com/events?per_page=1&page=0")
>>> response.json()[0]["id"]
'14345572615'
>>> response = requests.get("https://api.github.com/events?per_page=1&page=1")
>>> response.json()[0]["id"]
'14345572808'
>>> response = requests.get("https://api.github.com/events?per_page=1&page=2")
>>> response.json()[0]["id"]
'14345572100'

使用第一个 URL,您只能获取一个事件。但是使用page=查询参数,您可以继续对结果进行分页,确保您能够获取所有事件而不会使 API 过载。

限速

鉴于 API 是面向公众的,任何人都可以使用,怀有恶意的人经常试图滥用它们。为防止此类攻击,您可以使用一种称为速率限制的技术,该技术限制用户在给定时间范围内可以发出的请求数量。

如果您经常超过定义的速率限制,某些 API 实际上可能会阻止您的 IP 或 API 密钥。注意不要超过 API 开发人员设置的限制。否则,您可能需要等待一段时间才能再次调用该 API。

对于下面的示例,您将再次使用 GitHub API 和/events端点。根据其文档,GitHub 每小时允许大约 60 个未经身份验证的请求。如果超过该值,那么您将获得 403 状态代码,并且在相当长的一段时间内将无法再进行任何 API 调用。

警告:运行下一段代码确实会在一段时间内阻止您调用 GitHub,因此在运行它之前,请确保您暂时不需要访问 GitHub 的 API。

为了演示起见,您将有目的地尝试超过 GitHub 的速率限制,看看会发生什么。在下面的代码中,您将请求数据,直到获得除 之外的状态代码200 OK

>>>
>>> endpoint = "https://api.github.com/events"
>>> for i in range(100):
>>>   response = requests.get(endpoint)
>>>   print(f"{i} - {response.status_code}")
>>>   if response.status_code != 200:
>>>     break
0 - 200
1 - 200
2 - 200
3 - 200
4 - 200
5 - 200
...
55 - 200
56 - 200
57 - 403
>>> response
<Response [403]>
>>> response.json()
{'message': "API rate limit exceeded for <ip-address>.",
 'documentation_url': 'https://developer.github.com/v3/#rate-limiting'}

你有它:在大约 60 个请求之后,API 停止返回200 OK响应并返回一个403 Forbidden响应,通知您超出了 API 速率限制。

某些 API(如 GitHub 的)甚至可能在标头中包含有关当前速率限制和剩余请求数的附加信息。这些对您避免超过定义的限制非常有帮助。看看最新的response.headers,看看你是否能找到那些特定的速率限制头。

使用 Python 使用 API:实际示例

既然您已经了解了所有理论,并且已经尝试了一些 API,那么您可以通过一些更实际的示例来巩固这些知识。您可以修改下面的示例以根据您自己的目的定制它们。

您可以通过下载以下链接中提供的源代码来学习这些示例:

搜索和获取热门 GIF

如何制作一个小脚本,从GIPHY网站获取前三名的热门 GIF动图?为此,您需要创建一个应用程序并从 GIPHY 获取 API 密钥。您可以通过展开下面的框找到说明,也可以查看 GIPHY 的快速入门文档

创建 GIPHY 应用程序显示隐藏

在您掌握了 API 密钥后,您就可以开始编写一些代码来使用该 API。但是,有时您想在实现大量代码之前运行一些测试。我知道我知道。问题是,一些 API 实际上会为您提供直接从文档或其仪表板中获取 API 数据的工具。

在这种特殊情况下,GIPHY 为您提供了一个API Explorer,在您创建应用程序后,它允许您开始使用 API,而无需编写一行代码。

其他一些 API 将在文档本身中为您提供资源管理器,这就是TheDogAPI在每个 API 参考页面底部所做的。

无论如何,您始终可以使用代码来使用 API,这就是您在这里要做的。从仪表板中获取 API 密钥,通过替换API_KEY以下变量的值,您可以开始使用 GIPHY API:

 1import requests
 2
 3# Replace the following with the API key generated.
 4API_KEY = "API_KEY"
 5endpoint = "https://api.giphy.com/v1/gifs/trending"
 6
 7params = {"api_key": API_KEY, "limit": 3, "rating": "g"}
 8response = requests.get(ENDPOINT, params=params).json()
 9for gif in response["data"]:
10    title = gif["title"]
11    trending_date = gif["trending_datetime"]
12    url = gif["url"]
13    print(f"{title} | {trending_date} | {url}")

在文件顶部的第 4 行和第 5 行,您定义了您API_KEY的 API 和 GIPHY API,endpoint因为它们不会像其他部分那样经常更改。

在第 7 行,利用您在查询参数部分中学到的知识,定义params并添加您自己的 API 密钥。您还包括一些其他过滤器:limit获取3结果并rating仅获取适当的内容

最后,在得到响应后,您在第 9 行遍历结果。对于每个 GIF,您在第 13 行打印其标题、日期和 URL。

在控制台中运行这段代码将输出一个有点结构化的 GIF 列表:

Excited Schitts Creek GIF by CBC | 2020-11-28 20:45:14 | https://giphy.com/gifs/cbc-schittscreek-schitts-creek-SiGg4zSmwmbafTYwpj
Saved By The Bell Shrug GIF by PeacockTV | 2020-11-28 20:30:15 | https://giphy.com/gifs/peacocktv-saved-by-the-bell-bayside-high-school-dZRjehRpivtJsNUxW9
Schitts Creek Thank You GIF by CBC | 2020-11-28 20:15:07 | https://giphy.com/gifs/cbc-funny-comedy-26n79l9afmfm1POjC

现在,假设您要制作一个脚本,允许您搜索特定单词并获取该单词的第一个 GIPHY 匹配项。上面代码的不同端点和轻微变化可以很快做到这一点:

import requests

# Replace the following with the API key generated.
API_KEY = "API_KEY"
endpoint = "https://api.giphy.com/v1/gifs/search"

search_term = "shrug"
params = {"api_key": API_KEY, "limit": 1, "q": search_term, "rating": "g"}
response = requests.get(endpoint, params=params).json()
for gif in response["data"]:
    title = gif["title"]
    url = gif["url"]
    print(f"{title} | {url}")

你有它!现在您可以根据自己的喜好修改此脚本并按需生成 GIF。尝试从您最喜欢的节目或电影中获取 GIF,向您的终端添加快捷方式以按需获取最流行的 GIF,或者与您最喜欢的消息传递系统(WhatsApp、Slack,等等)中的另一个 API 集成。然后开始向您的朋友和同事发送 GIF!

获得每个国家/地区的 COVID-19 确诊病例

尽管您现在可能已经厌倦了这件事,但有一个免费的 API,其中包含最新的全球 COVID-19 数据。此 API 不需要身份验证,因此立即获取一些数据非常简单。您将在下面使用的免费版本具有速率限制和一些数据限制,但对于小型用例来说已经足够了。

对于此示例,您将获得截至前一天的确诊病例总数。我再次随机选择了德国作为国家,但您可以选择任何您喜欢的国家/地区

 1import requests
 2from datetime import date, timedelta
 3
 4today = date.today()
 5yesterday = today - timedelta(days=1)
 6country = "germany"
 7endpoint = f"https://api.covid19api.com/country/{country}/status/confirmed"
 8params = {"from": str(yesterday), "to": str(today)}
 9
10response = requests.get(endpoint, params=params).json()
11total_confirmed = 0
12for day in response:
13    cases = day.get("Cases", 0)
14    total_confirmed += cases
15
16print(f"Total Confirmed Covid-19 cases in {country}: {total_confirmed}")

在第 1 行和第 2 行,您导入必要的模块。在这种情况下,您必须导入datetimedelta对象才能获得今天和昨天的日期。

在第 6 行到第 8 行,您定义要使用的国家/地区 slug、端点和 API 请求的查询参数。

响应是天数列表,对于每一天,您都有一个Cases包含该日期确诊病例总数的字段。在第 11 行,您创建了一个变量来保存已确诊病例的总数,然后在第 14 行,您遍历所有天数并将它们相加。

打印最终结果将显示所选国家/地区的确诊病例总数:

Total Confirmed Covid-19 cases in germany: 1038649

在此示例中,您查看的是整个国家/地区的确诊病例总数。但是,您也可以尝试查看文档并获取特定城市的数据。为什么不让它更彻底一点并获得一些其他数据,例如已恢复的病例数?

搜索 Google 图书

如果您对书籍充满热情,那么您可能需要一种快速搜索特定书籍的方法。您甚至可能希望将它连接到您当地图书馆的搜索,以查看使用该书的ISBN是否可以找到给定的书。

对于此示例,您将使用Google Books API和公共卷端点来执行简单的书籍搜索。

这是moby dick在整个目录中查找单词的简单代码:

 1import requests
 2
 3endpoint = "https://www.googleapis.com/books/v1/volumes"
 4query = "moby dick"
 5
 6params = {"q": query, "maxResults": 3}
 7response = requests.get(endpoint, params=params).json()
 8for book in response["items"]:
 9    volume = book["volumeInfo"]
10    title = volume["title"]
11    published = volume["publishedDate"]
12    description = volume["description"]
13    print(f"{title} ({published}) | {description}")

此代码示例与您之前看到的代码示例非常相似。从第 3 行和第 4 行开始,定义重要变量,例如端点,在本例中为查询。

发出 API 请求后,在第 8 行开始迭代结果。然后,在第 13 行,为与初始查询匹配的每本书打印最有趣的信息:

Moby-Dick (2016-04-12) | "Call me Ishmael." So begins the famous opening...
Moby Dick (1892) | A literary classic that wasn't recognized for its...
Moby Dick; Or, The Whale (1983-08-16) | The story of Captain Ahab's...

您可以book在循环内打印变量以查看您还有哪些其他字段可用。以下是一些可能有助于进一步改进此代码的内容:

  • industryIdentifiers
  • averageRating 和 ratingsCount
  • imageLinks

使用此 API 的一个有趣挑战是使用您的OAuth 知识并创建您自己的书架应用程序,以记录您阅读或想要阅读的所有书籍。之后您甚至可以将其连接到您最喜欢的书店或图书馆,以便从您的愿望清单中快速找到您附近有售的书籍。这只是一个想法——我相信你可以想出更多。

结论

关于 API,您还可以了解一百万种其他内容:不同的标头、不同的内容类型、不同的身份验证技术等等。但是,您在本教程中学到的概念和技术将允许您使用自己喜欢的任何 API 进行练习,并使用 Python 来满足您可能拥有的任何 API 使用需求。

在本教程中,您学习了:

  • 什么的API是什么,你可以用它来
  • 什么状态码HTTP头HTTP方法
  • 如何使用 Python使用 API来消费公共数据
  • 使用Python 使用 API 时如何使用身份验证

继续尝试使用一些您喜欢的公共 API 来尝试这项新的魔法技能吧!您还可以通过从以下链接下载源代码来查看您在本教程中看到的示例:

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200