使用 API

应用程序接口是网站的一部分,旨在与程序进行交互。这些程序使用非常具体的 URL 来请求某些信息。这种请求称为 API 调用。请求的数据将以 JSON 或 CSV 等易于处理的格式返回。大多数使用外部数据源的应用程序(如与社交媒体网站集成的应用程序)都依赖于 API 调用。

Git 和 GitHub

我们的可视化将以 GitHub( https://github.com )上的信息为基础,GitHub 是一个允许程序员就编码项目进行协作的网站。我们将使用 GitHub 的 API 来请求网站上 Python 项目的信息,然后使用 Plotly 生成这些项目相对受欢迎程度的交互式可视化。

GitHub 的名字来源于分布式版本控制系统 Git。Git 可以帮助人们管理项目中的工作,防止一个人所做的改动干扰其他人正在做的改动。当你在项目中实施一项新功能时,Git 会跟踪你对每个文件所做的改动。当新代码正常运行时,你提交所做的改动,Git 就会记录下项目的新状态。如果你犯了错误,想要恢复修改,你可以很容易地返回到之前的工作状态。(要了解使用 Git 进行版本控制的更多信息,请参阅附录 D。)GitHub 上的项目存储在资源库中,其中包含与项目相关的所有内容:代码、协作者信息、任何问题或错误报告等。

当 GitHub 上的用户喜欢某个项目时,他们可以给该项目打 "星" 以示支持,并跟踪他们可能想使用的项目。在本章中,我们将编写一个程序,自动下载 GitHub 上被加星最多的 Python 项目的信息,然后创建这些项目的可视化信息。

使用 API 调用请求数据

GitHub 的 API 可让你通过 API 调用请求各种信息。要查看 API 调用的外观,请在浏览器地址栏中输入以下内容,然后按 ENTER 键:

https://api.github.com/search/repositories?q=language:python+sort:stars

该调用会返回当前托管在 GitHub 上的 Python 项目数量,以及有关最受欢迎的 Python 仓库的信息。让我们来看看这个调用。第一部分 https://api.github.com/ 将请求指向 GitHub 中响应 API 调用的部分。下一部分 search/repositories 告诉 API 在 GitHub 上的所有版本库中进行搜索。

repositories 后面的问号表示我们要传递一个参数。q 代表查询,等号(=)让我们开始指定一个查询 (q=)。通过使用 language:python,我们表示只想要以 Python 为主要语言的版本库信息。最后,+sort:stars 按项目的星级排序。

下面的代码段显示了响应的前几行:

{
    "total_count": 8961993, // 1
    "incomplete_results": true, // 2
    "items": [ // 3
    {
        "id": 54346799,
        "node_id": "MDEwOlJlcG9zaXRvcnk1NDM0Njc5OQ==",
        "name": "public-apis",
        "full_name": "public-apis/public-apis",
        --snip--

从响应中可以看出,这个 URL 主要不是供人输入的,因为它的格式是供程序处理的。截至本文撰写时,GitHub 发现了近 900 万个 Python 项目❶。"incomplete_results" 的值为 true,这说明 GitHub 没有完全处理查询❷。GitHub 限制了每次查询的运行时间,以保证 API 对所有用户的响应速度。在这种情况下,它找到了一些最流行的 Python 仓库,但没有时间找到所有的;我们稍后会解决这个问题。返回的 "items" 显示在下面的列表中,其中包含 GitHub ❸ 上最受欢迎的 Python 项目的详细信息。

安装 Requests

Requests 软件包允许 Python 程序轻松地从网站请求信息并检查响应。使用 pip 安装 Requests:

$ python -m pip install --user requests

如果使用 python 以外的命令运行程序或启动终端会话,例如 python3,命令将如下所示:

$ python3 -m pip install --user requests

处理 API 响应

现在,我们将编写一个程序,自动发出 API 调用并处理结果:

python_repos.py
import requests

# Make an API call and check the response.
url = "https://api.github.com/search/repositories" # 1
url += "?q=language:python+sort:stars+stars:>10000"

headers = {"Accept": "application/vnd.github.v3+json"} # 2
r = requests.get(url, headers=headers) # 3
print(f"Status code: {r.status_code}") # 4

# Convert the response object to a dictionary.
response_dict = r.json() # 5

# Process results.
print(response_dict.keys())

我们首先导入请求模块。然后,我们将 API 调用的 URL 赋值给 url 变量 ❶。这是一个很长的 URL,因此我们将其分成两行。第一行是 URL 的主要部分,第二行是查询字符串。我们在原始查询字符串中加入了一个条件:stars:>10000,它告诉 GitHub 只查找拥有超过 10,000 个 stars 的 Python 仓库。这应该能让 GitHub 返回一组完整、一致的结果。

GitHub 目前使用的是第三版 API,因此我们为 API 调用定义了头文件,明确要求使用该版本的 API,并以 JSON 格式❷ 返回结果。然后,我们使用请求来调用 API ❸。我们调用 get(),将我们定义的 URL 和标头传递给它,然后将响应对象赋值给变量 r。

响应对象有一个名为 status_code 的属性,它会告诉我们请求是否成功。(状态代码为 200 表示响应成功。)我们打印 status_code 的值,以确保调用成功❹。我们要求 API 以 JSON 格式返回信息,因此我们使用 json() 方法将信息转换为 Python 字典❺。我们将生成的字典赋值给 response_dict。

最后,我们从 response_dict 中打印键值,并看到以下输出:

Status code: 200
dict_keys(['total_count', 'incomplete_results', 'items'])

因为状态代码是 200,所以我们知道请求成功了。响应字典只包含三个键:"total_count"、"incomplete_results "和 "items"。让我们看看响应字典的内部。

使用响应字典

有了以字典形式表示的 API 调用信息,我们就可以处理其中存储的数据了。让我们生成一些汇总信息的输出。这是确保我们收到预期信息并开始检查我们感兴趣的信息的好方法:

python_repos.py
import requests

# Make an API call and store the response.
--snip--

# Convert the response object to a dictionary.
response_dict = r.json()
print(f"Total repositories: {response_dict['total_count']}") # 1
print(f"Complete results: {not response_dict['incomplete_resu
lts']}")

# Explore information about the repositories.
repo_dicts = response_dict['items'] # 2
print(f"Repositories returned: {len(repo_dicts)}")

# Examine the first repository.
repo_dict = repo_dicts[0] # 3
print(f"\nKeys: {len(repo_dict)}") # 4
for key in sorted(repo_dict.keys()): # 5
    print(key)

我们从打印与 "total_count" 相关联的值开始探索响应字典,该值表示该 API 调用 ❶返回的 Python 仓库总数。我们还使用了与 "incomplete_results" 相关的值,这样我们就能知道 GitHub 是否能完全处理查询。我们不会直接打印这个值,而是打印它的相反值:值为 True 表示我们收到了一组完整的结果。

与 "items "相关联的值是一个包含多个字典的列表,每个字典都包含一个 Python 仓库的数据。我们将这个字典列表分配给 repo_dicts ❷。然后,我们打印 repo_dicts 的长度,以查看有多少个版本库的信息。

为了仔细查看返回的每个版本库的信息,我们从 repo_dicts 中取出第一个项,并将其赋值给 repo_dict ❸。然后,我们打印字典中的键的数量,看看我们有多少信息❹。最后,我们打印字典的所有键,看看其中包含了哪些信息❺。

这些结果让我们对实际数据有了更清晰的了解:

Status code: 200
Total repositories: 248 // 1
Complete results: True // 2
Repositories returned: 30
Keys: 78 // 3
allow_forking
archive_url
archived
--snip--
url
visiblity
watchers
watchers_count

在撰写本文时,只有 248 个 Python 仓库拥有超过 10,000 颗星 ❶。我们可以看到,GitHub 能够完全处理 API 调用 ❷。在这个响应中,GitHub 返回了符合我们查询条件的前 30 个资源库的信息。如果我们想要更多的资源库,可以请求更多页的数据。

GitHub 的 API 会返回每个版本库的大量信息:repo_dict ❸ 中有 78 个键。当你查看这些键值时,你就能了解你能提取到的项目信息种类。(要知道通过 API 可以获得哪些信息,唯一的方法就是阅读文档或通过代码来检查信息,就像我们在这里所做的那样)。

让我们取出 repo_dict 中一些键的值:

python_repos.py
--snip--
# Examine the first repository.
repo_dict = repo_dicts[0]
print("\nSelected information about first repository:")
print(f"Name: {repo_dict['name']}") # 1
print(f"Owner: {repo_dict['owner']['login']}") # 2
print(f"Stars: {repo_dict['stargazers_count']}") # 3
print(f"Repository: {repo_dict['html_url']}")
print(f"Created: {repo_dict['created_at']}") # 4
print(f"Updated: {repo_dict['updated_at']}") # 5
print(f"Description: {repo_dict['description']}")

在这里,我们打印第一个版本库字典中若干键的值。我们从项目名称❶ 开始。整个 dictionary 代表项目的所有者,因此我们使用 key owner 访问代表所有者的 dictionary,然后使用 key login 获取所有者的登录名 ❷。接下来,我们会打印出该项目获得了多少颗星❸ 以及项目 GitHub 仓库的 URL。然后,我们会显示它的创建时间❹ 和最后更新时间❺。最后,我们打印该版本库的描述。

输出结果应该是这样的:

Status code: 200
Total repositories: 248
Complete results: True
Repositories returned: 30

Selected information about first repository:
Name: public-apis
Owner: public-apis
Stars: 191493
Repository: https://github.com/public-apis/public-apis
Created: 2016-03-20T23:49:42Z
Updated: 2022-05-12T06:37:11Z
Description: A collective list of free APIs

我们可以看到,截至本文撰写时,GitHub 上星级最高的 Python 项目是 public-apis。它的所有者是一个同名组织,有近 20 万 GitHub 用户给它加了星。我们可以看到项目仓库的 URL、创建日期(2016 年 3 月)以及最近的更新。此外,描述还告诉我们 public-apis 包含程序员可能感兴趣的免费 API 列表。

总结顶级存储库

当我们对这些数据进行可视化时,我们会希望包含多个版本库。让我们编写一个循环,打印 API 调用返回的每个版本库的选定信息,这样我们就可以在可视化中包含所有版本库:

python_repos.py
--snip--
# Explore information about the repositories.
repo_dicts = response_dict['items']
print(f"Repositories returned: {len(repo_dicts)}")

print("\nSelected information about each repository:") # 1
for repo_dict in repo_dicts: # 2
    print(f"\nName: {repo_dict['name']}")
    print(f"Owner: {repo_dict['owner']['login']}")
    print(f"Stars: {repo_dict['stargazers_count']}")
    print(f"Repository: {repo_dict['html_url']}")
    print(f"Description: {repo_dict['description']}")

我们首先打印一条介绍性信息❶。然后循环浏览 repo_dicts ❷ 中的所有字典。在循环中,我们会打印每个项目的名称、所有者、星级、GitHub 上的 URL 以及项目描述:

Status code: 200
Total repositories: 248
Complete results: True
Repositories returned: 30
Selected information about each repository:
Name: public-apis
Owner: public-apis
Stars: 191494
Repository: https://github.com/public-apis/public-apis
Description: A collective list of free APIs
Name: system-design-primer
Owner: donnemartin
Stars: 179952
Repository: https://github.com/donnemartin/system-design-primer
Description: Learn how to design large-scale systems. Prep for the system
design interview. Includes Anki flashcards.
--snip--

Name: PayloadsAllTheThings
Owner: swisskyrepo
Stars: 37227
Repository: https://github.com/swisskyrepo/PayloadsAllTheThings
Description: A list of useful payloads and bypass for Web Application Security and Pentest/CTF

这些结果中出现了一些有趣的项目,也许值得一看。但不要在这里花太多时间,因为我们即将创建一个可视化工具,它将使结果更容易阅读。

监控 API 速率限制

大多数应用程序接口都有速率限制,这意味着在一定时间内可以发出的请求数量是有限制的。要查看是否接近 GitHub 的限制,请在网页浏览器中输入 https://api.github.com/rate_limit 。你应该会看到这样开头的响应:

{
    "resources": {
        --snip--
        "search": { // 1
        "limit": 10, // 2
        "remaining": 9, // 3
        "reset": 1652338832, // 4
        "used": 1,
        "resource": "search"
    },
    --snip--

我们感兴趣的信息是搜索 API ❶的速率限制。我们可以看到,限制是每分钟 10 个请求❷,而当前一分钟❸ 中还剩余 9 个请求。与关键字 "reset "相关联的值表示我们的配额重置❹的 Unix 或纪元时间(1970 年 1 月 1 日午夜后的秒数)。如果您达到了配额,您会收到一个简短的回复,告诉您已达到 API 限制。如果达到限额,请等待配额重置。

许多 API 都要求注册并获得 API 密钥或访问令牌才能调用 API。截至目前,GitHub 没有此类要求,但如果你获得了访问令牌,你的限制就会高得多。