跳过导航,直达内容

第27章:GitHub API 详解 🔌

想开发 GitHub 工具?想自动化操作?GitHub REST API 是你的好帮手!

📋 目录


什么是 GitHub API?

基本定义

GitHub REST API 是 GitHub 提供的 HTTP 接口,让你可以编程式地操作 GitHub。

可以做什么?

操作 API 端点
获取用户信息 GET /users/用户名
列出所有仓库 GET /user/repos
创建 Issue POST /repos/GTX950L/github-encyclopedia/issues
创建 PR POST /repos/GTX950L/github-encyclopedia/pulls
合并 PR PUT /repos/GTX950L/github-encyclopedia/pulls/123/merge

认证方式

方式1:使用 Token(推荐)✅

# 在请求头中加上 Token
curl -H "Authorization: Bearer ghp_你的token" \
  https://api.github.com/user

方式2:使用 OAuth App

适合需要让用户授权你的应用的场景。

流程:

1. 用户点击"使用 GitHub 登录"
2. 跳转到 GitHub 授权页面
3. 用户点击 Authorize
4. GitHub 回调你的网站,带上 code
5. 你的后端用 code 换取 access_token
6. 使用 access_token 调用 API

常用 API 端点

1️⃣ 用户相关

获取当前用户信息

curl -H "Authorization: Bearer ghp_xxx" \
  https://api.github.com/user

响应示例:

{
  "login": "GTX950L",
  "id": 123456,
  "name": "GTX950L",
  "public_repos": 10,
  "followers": 45,
  "following": 67
}

获取某个用户的信息

curl https://api.github.com/users/GTX950L

2️⃣ 仓库相关

列出当前用户的所有仓库

curl -H "Authorization: Bearer ghp_xxx" \
  https://api.github.com/user/repos

获取某个仓库的信息

curl https://api.github.com/repos/GTX950L/github-encyclopedia

创建新仓库

curl -X POST \
  -H "Authorization: Bearer ghp_xxx" \
  -H "Accept: application/vnd.github.v3+json" \
  https://api.github.com/user/repos \
  -d '{"name":"my-new-repo","description":"我的新仓库","public":true}'

3️⃣ Issue 相关

列出仓库的所有 Issue

curl https://api.github.com/repos/GTX950L/github-encyclopedia/issues

创建一个 Issue

curl -X POST \
  -H "Authorization: Bearer ghp_xxx" \
  https://api.github.com/repos/GTX950L/github-encyclopedia/issues \
  -d '{"title":"Bug: 登录失败","body":"详细描述..."}'

4️⃣ PR 相关

列出仓库的所有 PR

curl https://api.github.com/repos/GTX950L/github-encyclopedia/pulls

创建一个 PR

curl -X POST \
  -H "Authorization: Bearer ghp_xxx" \
  https://api.github.com/repos/GTX950L/github-encyclopedia/pulls \
  -d '{"title":"添加新功能","head":"feature/login","base":"main","body":"描述..."}'

分页和限流

分页(Pagination)

为什么需要?

GitHub API 默认每页只返回 30 条记录。

如何获取所有记录?

# 第1页(默认)
curl https://api.github.com/user/repos?page=1&per_page=100

# 第2页
curl https://api.github.com/user/repos?page=2&per_page=100

自动处理分页(推荐)✅

import requests

token = "ghp_你的token"
headers = {"Authorization": f"Bearer {token}"}

all_repos = []
page = 1

while True:
    response = requests.get(
        "https://api.github.com/user/repos",
        headers=headers,
        params={"page": page, "per_page": 100}
    )
    
    repos = response.json()
    if not repos:  # 没有更多记录了
        break
    
    all_repos.extend(repos)
    page += 1

print(f"总共有 {len(all_repos)} 个仓库")

限流(Rate Limiting)

为什么需要?

防止滥用,GitHub 对 API 调用次数有限制。

限额

认证方式 每小时限额
未认证 60 次
Token 认证 5000 次

查看剩余限额

curl -I https://api.github.com/user

响应头:

X-RateLimit-Limit: 5000       # 每小时限额
X-RateLimit-Remaining: 4999  # 剩余次数
X-RateLimit-Reset: 1623456789   # 重置时间(Unix 时间戳)

超限了怎么办?

import time
import requests

# 如果收到 403 错误,且响应头显示超限:
if response.status_code == 403:
    reset_time = int(response.headers["X-RateLimit-Reset"])
    wait_seconds = reset_time - time.time()
    print(f"超限了!等待 {wait_seconds} 秒...")
    time.sleep(wait_seconds + 1)
    # 然后重试

示例:开发一个 CLI 工具

目标:开发一个 gh-stats 工具

功能:生成你的 GitHub 统计报告。

代码实现

#!/usr/bin/env python3
"""
gh-stats: 生成 GitHub 统计报告
"""

import os
import requests
from datetime import datetime

# 从环境变量读取 Token
token = os.environ.get("GITHUB_TOKEN")
if not token:
    print("❌ 请设置 GITHUB_TOKEN 环境变量")
    exit(1)

headers = {
    "Authorization": f"Bearer {token}",
    "Accept": "application/vnd.github.v3+json"
}

# 获取当前用户信息
response = requests.get("https://api.github.com/user", headers=headers)
user = response.json()
username = user["login"]

print(f"📊 生成 {username} 的统计报告...")
print("=" * 50)

# 获取所有仓库
all_repos = []
page = 1
while True:
    response = requests.get(
        "https://api.github.com/user/repos",
        headers=headers,
        params={"page": page, "per_page": 100}
    )
    repos = response.json()
    if not repos:
        break
    all_repos.extend(repos)
    page += 1

# 统计
total_stars = sum(repo["stargazers_count"] for repo in all_repos)
total_forks = sum(repo["forks_count"] for repo in all_repos)
public_repos = [r for r in all_repos if not r["private"]]
private_repos = [r for r in all_repos if r["private"]]

print(f"📁 公开仓库: {len(public_repos)}")
print(f"🔒 私有仓库: {len(private_repos)}")
print(f"⭐ 总 Star 数: {total_stars}")
print(f"🍴 总 Fork 数: {total_forks}")

# 找出 Star 最多的仓库
if public_repos:
    top_repo = max(public_repos, key=lambda r: r["stargazers_count"])
    print(f"\n🏆 最受欢迎的仓库:")
    print(f"   {top_repo['name']} ({top_repo['stargazers_count']} ⭐)")

print("=" * 50)
print("✅ 报告生成完成!")

使用效果

# 设置 Token
export GITHUB_TOKEN="ghp_你的token"

# 运行工具
python3 gh-stats.py

输出:

📊 生成 GTX950L 的统计报告...
==================================================

📁 公开仓库: 5
🔒 私有仓库: 3
⭐ 总 Star 数: 123
🍴 总 Fork 数: 45

🏆 最受欢迎的仓库:
   github-encyclopedia (100 ⭐)

==================================================
✅ 报告生成完成!

🔷 GraphQL API(新版 API)

什么是 GraphQL?

除了 REST API,GitHub 还提供了 GraphQL API(v4),它可以让你精确获取想要的数据,不多不少。

REST vs GraphQL

对比 REST API GraphQL API
版本 v3 v4
数据量 返回固定数据(可能过多或不足) ✅ 精确指定要什么字段
请求次数 可能需要多次请求 ✅ 一次请求获取所有数据
学习曲线 ⭐ 简单 ⭐⭐ 中等
推荐场景 简单操作、脚本 复杂查询、工具开发

示例对比

场景:获取你的仓库名和 Star 数

REST 方式(先获取用户,再获取每个仓库)

# 第1步:获取所有仓库(返回很多不需要的字段)
curl -H "Authorization: Bearer ghp_xxx" \
  https://api.github.com/user/repos

# 第2步:从返回的 JSON 中提取仓库名和 Star 数
# 返回了一堆你可能不需要的字段

GraphQL 方式(精确指定你要的字段)✅

# 一次请求,精确查询
curl -H "Authorization: Bearer ghp_xxx" \
  -H "Content-Type: application/json" \
  -X POST https://api.github.com/graphql \
  -d '{
    "query": "{
      viewer {
        repositories(first: 10) {
          nodes {
            name
            stargazerCount
          }
        }
      }
    }"
  }'

响应(只有你要的字段):

{
  "data": {
    "viewer": {
      "repositories": {
        "nodes": [
          {"name": "github-encyclopedia", "stargazerCount": 100},
          {"name": "my-tool", "stargazerCount": 42}
        ]
      }
    }
  }
}

在 Python 中使用 GraphQL

import requests

token = "ghp_你的token"
headers = {
    "Authorization": f"Bearer {token}",
    "Content-Type": "application/json"
}

# 查询:获取你的仓库名、Star 数、主要语言
query = """
{
  viewer {
    repositories(first: 50, orderBy: {field: STARGAZERS, direction: DESC}) {
      nodes {
        name
        stargazerCount
        primaryLanguage {
          name
        }
        description
      }
    }
  }
}
"""

response = requests.post(
    "https://api.github.com/graphql",
    headers=headers,
    json={"query": query}
)

data = response.json()
for repo in data["data"]["viewer"]["repositories"]["nodes"]:
    lang = repo["primaryLanguage"]["name"] if repo["primaryLanguage"] else "N/A"
    print(f"⭐ {repo['name']} ({repo['stargazerCount']} stars, {lang})")

什么时候用 REST,什么时候用 GraphQL?

场景 推荐
简单操作(创建 Issue、合并 PR) REST ✅
复杂查询(获取仓库和所有关联数据) GraphQL ✅
写脚本 REST(更简单)
开发工具 GraphQL(更高效)
需要兼容旧代码 REST
新项目新功能 GraphQL

💡 GitHub 官方推荐:尽量使用 GraphQL API,但 REST API 仍然完全支持,不会弃用。


🚀 GraphQL 实战进阶

1️⃣ 变更(Mutation):创建 Issue

GraphQL 不仅可以查询,还可以修改数据(Mutation):

mutation {
  createIssue(input: {
    repositoryId: "R_kgDON8K3tQ"  # 仓库 ID(可以通过查询获取)
    title: "Bug: 页面加载失败"
    body: "在 Chrome 浏览器中访问首页时白屏"
    assigneeIds: ["MDQ6VXNlcjE="]
    labelIds: ["LA_kwDON8K3tQ8AAAABkD"]
  }) {
    issue {
      number
      url
      state
    }
  }
}

Python 调用:

mutation = """
mutation($repo: ID!, $title: String!, $body: String) {
  createIssue(input: {repositoryId: $repo, title: $title, body: $body}) {
    issue { number url }
  }
}
"""

variables = {
    "repo": "R_kgDON8K3tQ",
    "title": "Bug: 页面加载失败",
    "body": "详细描述..."
}

response = requests.post(
    "https://api.github.com/graphql",
    headers=headers,
    json={"query": mutation, "variables": variables}
)

💡 如何找仓库 ID? 查询 { repository(owner: "GTX950L", name: "github-encyclopedia") { id } }

2️⃣ 游标分页(Cursor Pagination)

REST 用 page + per_page,GraphQL 用 游标(cursor) 进行分页:

{
  viewer {
    repositories(first: 10, after: "Y3Vyc29yOnYyOpHOA1Cz") {
      pageInfo {
        hasNextPage
        endCursor
      }
      nodes {
        name
        stargazerCount
      }
    }
  }
}

Python 自动翻页:

def get_all_repos():
    all_repos = []
    cursor = None
    has_next = True

    while has_next:
        query = """
        query($cursor: String) {
          viewer {
            repositories(first: 100, after: $cursor) {
              pageInfo { hasNextPage endCursor }
              nodes { name stargazerCount description }
            }
          }
        }
        """
        response = requests.post(
            "https://api.github.com/graphql",
            headers=headers,
            json={"query": query, "variables": {"cursor": cursor}}
        )
        data = response.json()["data"]["viewer"]["repositories"]
        all_repos.extend(data["nodes"])
        has_next = data["pageInfo"]["hasNextPage"]
        cursor = data["pageInfo"]["endCursor"]

    print(f"共获取 {len(all_repos)} 个仓库")
    return all_repos

3️⃣ 嵌套查询(一次获取多层数据)

GraphQL 的强大之处:一次请求获取关联数据:

{
  repository(owner: "GTX950L", name: "github-encyclopedia") {
    name
    stargazerCount
    # 最近的 5 个 Issue
    issues(first: 5, states: OPEN) {
      nodes {
        title
        url
        # Issue 的标签
        labels(first: 3) {
          nodes { name color }
        }
        # Issue 的创建者
        author { login avatarUrl }
      }
    }
    # 最近的 3 个 PR
    pullRequests(first: 3, states: OPEN) {
      nodes {
        title
        # PR 的审查状态
        reviews(first: 3) {
          nodes { state author { login } }
        }
      }
    }
    # 主要语言
    primaryLanguage { name }
    # 仓库主题
    repositoryTopics(first: 5) {
      nodes { topic { name } }
    }
  }
}

REST 要实现同样效果:至少需要 5-6 次 API 调用。

4️⃣ 使用 GraphQL Explorer 尝试

GitHub 提供了一个浏览器内的 GraphQL 编辑器:

1. 访问 https://docs.github.com/en/graphql/overview/explorer
2. 点击 "Explorer" 按钮
3. 在左侧编写查询,点击 ▶️ 运行
4. 右侧即时显示结果

功能:
├── 自动补全(字段名、参数)
├── 文档面板(查看所有可用字段)
├── 语法高亮
└── 历史记录

💡 学习建议:先在 Explorer 中测试查询,确认无误后再复制到代码中。

5️⃣ REST vs GraphQL 快速抉择

你要做什么?
│
├─ 创建/更新/删除数据
│   └─ 都可以 → REST 更简单
│
├─ 获取单个资源(如某个 Issue)
│   └─ 都可以 → REST 更直接
│
├─ 获取列表 + 关联数据(如仓库+Issue+PR+标签)
│   └─ GraphQL ✅(一次请求搞定)
│
├─ 开发第三方工具/集成
│   └─ GraphQL ✅(数据精确,性能好)
│
└─ 写一次性脚本
    └─ REST ✅(curl 就能调)

常见问题

Q1: API 返回的时区是什么?

A: GitHub API 返回的时间是 UTC 时区。

如何转换?

from datetime import datetime
import pytz

# GitHub API 返回的时间
utc_time_str = "2026-06-12T10:00:00Z"

# 转换为本地时区(如北京时间 UTC+8)
utc_time = datetime.strptime(utc_time_str, "%Y-%m-%dT%H:%M:%SZ")
local_time = utc_time.replace(tzinfo=pytz.utc).astimezone(pytz.timezone("Asia/Shanghai"))

print(local_time)  # 输出:2026-06-12 18:00:00+08:00

Q2: 如何上传文件到仓库?

A: 使用 Create or update file contents 端点。

import base64

# 文件内容需要 Base64 编码
content = "Hello, World!"
encoded_content = base64.b64encode(content.encode()).decode()

response = requests.put(
    "https://api.github.com/repos/GTX950L/github-encyclopedia/contents/README.md",
    headers=headers,
    json={
        "message": "Update README",
        "content": encoded_content,
        "sha": "现有文件的 SHA(如果是更新)"
    }
)

Q3: 如何删除仓库中的文件?

A: 使用 Delete file 端点。

# 先获取文件的 SHA
response = requests.get(
    "https://api.github.com/repos/GTX950L/github-encyclopedia/contents/文件名",
    headers=headers
)
file_sha = response.json()["sha"]

# 删除文件
requests.delete(
    "https://api.github.com/repos/GTX950L/github-encyclopedia/contents/文件名",
    headers=headers,
    json={
        "message": "Delete file",
        "sha": file_sha
    }
)

📝 实战练习

练习1:调用 API 获取你的信息

任务:

  1. 生成 Personal Access Token
  2. 使用 curl 调用 GET /user 端点
  3. 查看返回的 JSON 数据

练习2:开发一个”自动 Star”工具

任务:

  1. 写一个 Python 脚本
  2. 从文件读取仓库列表(每行一个)
  3. 调用 API 给这些仓库点 Star
  4. 运行工具,确认 Star 成功

练习3:开发一个 Issue 统计工具

任务:

  1. 调用 API 获取某个仓库的所有 Issue
  2. 统计:打开的、关闭的、带标签的
  3. 生成统计报告(可以输出为 Markdown 表格)

📚 延伸阅读


📝 本章小结

你现在学会了:

💡 核心要点

GitHub API 是自动化和工具开发的基石。

熟练掌握 API 后,你可以:

  • 开发自己的 GitHub 工具
  • 自动化重复性任务
  • 集成 GitHub 到其他系统

← 返回目录  |  ← 上一章  |  下一章 →