GitHub 是软件领域最丰富的公开数据集之一。公开仓库页面包含项目名称、描述、星标数、Fork 数、主要编程语言和话题标签,而公开个人主页则汇总了开发者的公开姓名、个人简介、仓库数量和粉丝数。这些数据驱动着大量有实际价值的工作:追踪开源项目的流行度、调研哪些语言和框架正在兴起,以及构建团队所依赖的库的看板。

本指南将演示如何通过 Crawlbase Crawling API 用 Python 抓取 GitHub 公开仓库和个人主页,解析所需字段,并导出为 JSON 和 CSV。这里的所有内容都限定在公开页面范围内,任何人无需登录即可访问。本指南不涉及私有仓库、组织成员列表、电子邮件地址或任何需要身份验证的内容。在将此方法用于实际抓取之前,请阅读文末的合法性部分,并请注意:GitHub 提供了官方 REST API,对于大多数此类工作而言,那才是更合适的工具。

你将构建的内容

一个小型 Python 脚本,接受公开 GitHub 仓库或个人主页 URL,通过 Crawling API 获取页面,用 BeautifulSoup 解析,并将结构化记录写入 JSON 和 CSV。它提取以下字段:

  • 仓库名称 仓库标题区显示的项目名称。
  • 描述 侧边栏中的一行摘要。
  • 星标数 公开星标数量。
  • Fork 数 公开 Fork 数量。
  • 关注数 追踪该仓库的用户数量。
  • 编程语言和话题 主要编程语言以及仓库的话题标签。
  • 个人主页字段 对于用户 URL:公开姓名、个人简介、公开仓库数量和粉丝数量。

请注意有意省略的内容:没有电子邮件地址、没有私有仓库、没有私有组织的成员列表,也不会试图建立任何个人的详细档案。个人主页数据涉及真实的人,因此脚本将其视为个人数据,只保留粗粒度的公开字段。

为什么直接请求在 GitHub 上可能失败

GitHub 的大部分仓库和个人主页内容以服务器端渲染 HTML 形式提供,因此直接请求通常能返回可用的标记。麻烦出现在规模化访问时。GitHub 对未认证流量的限速非常严格,来自单个数据中心 IP 的紧密循环请求很快就会被节流或受到验证挑战。匿名浏览也只能看到比登录会话更少的内容,而且登录/未登录状态下的标记结构有所不同,这会破坏脆弱的选择器。

因此,一个可靠的 GitHub 爬虫需要让请求看起来像普通访客,并分散到多个 IP 地址,使任何单个 IP 都不触发限制。你可以用轮换代理池和自定义重试逻辑自行构建,但维护这套技术栈本身就是大部分工作量所在。Crawling API 将这一切合并为一次调用:你发送 URL,它通过可信的轮换 IP 获取页面,并返回供你解析的完整 HTML。GitHub 页面足够静态,这里使用普通 token 是正确的选择,无需 JavaScript 渲染。

使用哪种 token

Crawlbase 提供两种 token 类型。普通 token 获取静态 HTML;JavaScript(JS)token 则先在真实浏览器中渲染页面。GitHub 仓库和个人主页是服务器端渲染的,因此普通 token 已经足够,成本也更低。只有当你需要的特定页面依赖客户端渲染时,才需要使用 JS token。

前提条件

先准备好以下几项。每项都不会花太长时间。

Python 基础。 你应该能够运行脚本并用 pip 安装包。如果 HTML 解析对你来说还比较陌生,我们的BeautifulSoup 使用指南涵盖了提取部分,而用 Python 抓取网站则介绍了端到端的完整流程。

Python 3.8 或更高版本。python --version 确认版本。如果尚未安装,请从 python.org 下载安装。

Crawlbase 账号和 token。 注册后打开控制台,从账号文档页面复制你的普通 token。Crawlbase 最多提供 20,000 次免费请求作为起始额度,且只对成功的请求计费。请像对待密码一样保护你的 token,不要将其提交到版本控制系统。

搭建项目

创建独立的虚拟环境,然后安装爬虫所需的三个库。

bash
python --version

python -m venv github_env
source github_env/bin/activate

pip install crawlbase beautifulsoup4 pandas

在 Windows 上,使用 github_env\Scripts\activate 替代 source 那行来激活环境。三个依赖各司其职:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 解析返回的 HTML 以便通过选择器提取字段,pandas 在最后将记录转为 CSV。

第 1 步:获取公开仓库页面

首先获取完整的页面。导入 CrawlingAPI,用你的 token 初始化,然后请求一个公开仓库 URL。在解析前检查状态码,让失败情况显式暴露,而非悄无声息。

python
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

def crawl(page_url):
    response = api.get(page_url)
    if response["status_code"] == 200:
        return response["body"].decode("latin1")
    print(f"Request failed: {response['status_code']}")
    return None

if __name__ == "__main__":
    page_url = "https://github.com/TheAlgorithms/Java"
    html = crawl(page_url)
    print(html[:500] if html else "No HTML returned")

响应正文以 latin1 解码,以避免仓库渲染 HTML 中偶尔出现的非 UTF-8 字节导致崩溃。示例指向一个知名公开仓库,便于你在编写任何选择器之前先确认获取是否正常。运行后你应该在前 500 个字符中看到真实的 GitHub 标记,这证明请求通过了可信 IP 抵达页面。

Crawlbase GitHub Scraper

上面的 api.get 调用所做的不仅仅是一次 HTTP 请求。GitHub 对未认证流量的限速很严格,单个数据中心 IP 很快就会被限制,因此 Crawling API 通过轮换住宅 IP 获取每个页面,并为你处理重试和 CAPTCHA。你无需自行运行代理池及其配套的退避逻辑。先在免费套餐上用一个公开仓库测试。

第 2 步:解析仓库字段

拿到渲染后的 HTML,将其加载到 BeautifulSoup 并提取仓库字段。GitHub 的仓库标题通过 itemprop 属性暴露名称,描述位于侧边栏,星标数、Fork 数和关注数则紧挨着各自的 Octicon SVG 图标,这使得图标成为定位旁边数字的可靠锚点。话题以标签链接形式存在,主要编程语言显示在语言列表中。

python
from bs4 import BeautifulSoup

def text_of(soup, selector):
    el = soup.select_one(selector)
    return el.text.strip() if el else None

def scrape_repository(html):
    soup = BeautifulSoup(html, "html.parser")

    topics = [t.text.strip() for t in
              soup.select('a[data-octo-click="topic_click"]')]

    return {
        "name": text_of(soup,
            'strong[itemprop="name"] a'),
        "description": text_of(soup,
            "div.Layout-sidebar div.BorderGrid-row p.f4.my-3"),
        "stars": text_of(soup,
            "svg.octicon-star ~ strong"),
        "forks": text_of(soup,
            "svg.octicon-repo-forked ~ strong"),
        "watchers": text_of(soup,
            "svg.octicon-eye ~ strong"),
        "language": text_of(soup,
            'span[itemprop="programmingLanguage"]'),
        "topics": topics,
    }

text_of 辅助函数在选择器未命中时返回 None,因此某个字段缺失不会导致整个解析崩溃。星标数、Fork 数和关注数的选择器以 Octicon 图标类为锚点,用兄弟选择器(~ strong)获取旁边渲染的数字,这比依赖深层嵌套的类链更加稳健。话题通过所有 topic_click 链接收集到一个列表中。

选择器会漂移

GitHub 会定期修改其标记,因此今天能用的选择器之后可能返回 None。当某个字段为空时,请在浏览器开发者工具中查看实时页面并更新选择器。锚定在 itemprop 和 Octicon 图标类等稳定钩子上,而非自动生成的工具类,可以将维护工作量降到最低。

第 3 步:解析公开个人主页

公开个人主页包含一组不同的字段。你可以从中获取用户的公开显示名称、用户名(handle)、个人简介、公开仓库数量和粉丝数量。GitHub 用稳定的 vcard 类标记显示名称和用户名,仓库数量和粉丝数量则紧挨着各自的 Octicon 图标,与仓库页面的模式相同。

python
def scrape_profile(html):
    soup = BeautifulSoup(html, "html.parser")

    return {
        "name": text_of(soup,
            "span.p-name.vcard-fullname"),
        "username": text_of(soup,
            "span.p-nickname.vcard-username"),
        "bio": text_of(soup,
            "div.p-note.user-profile-bio div"),
        "repositories": text_of(soup,
            "svg.octicon-repo ~ span"),
        "followers": text_of(soup,
            "svg.octicon-people ~ span.color-fg-default"),
    }

这些是个人主页向任何未登录访客展示的粗粒度公开字段。脚本在这里有意停止。它不读取用户的电子邮件、组织成员身份或其仓库内容,也不将个人主页信息拼接成关于某个人的记录。公开姓名、个人简介、仓库数量和粉丝数量是关于开发者公开足迹的总量信号;其背后的个人不应被你所刻画描绘。

第 4 步:组装完整脚本并导出

现在将获取与解析串联成一个可运行的脚本,读取一个仓库和一个个人主页,然后用 pandas 写出 JSON 和 CSV。

python
import json
import time
import pandas as pd
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

def crawl(page_url):
    response = api.get(page_url)
    if response["status_code"] == 200:
        return response["body"].decode("latin1")
    print(f"Request failed: {response['status_code']}")
    return None

def main():
    repo_url = "https://github.com/TheAlgorithms/Java"
    profile_url = "https://github.com/torvalds"

    records = []

    repo_html = crawl(repo_url)
    if repo_html:
        repo = scrape_repository(repo_html)
        repo["url"] = repo_url
        records.append(repo)
    time.sleep(3)

    profile_html = crawl(profile_url)
    if profile_html:
        profile = scrape_profile(profile_html)
        profile["url"] = profile_url
        records.append(profile)

    with open("github_data.json", "w") as f:
        json.dump(records, f, indent=2, ensure_ascii=False)

    pd.DataFrame(records).to_csv("github_data.csv", index=False)
    print(f"Wrote {len(records)} records to JSON and CSV")

if __name__ == "__main__":
    main()

两次请求之间的 time.sleep(3) 不是点缀。在像 GitHub 这样有速率限制的目标上,控制节奏是运行能否保持健康的最重要因素。脚本将一条仓库记录和一条个人主页记录收集到一个列表中,将结构化结果写入 github_data.json,并让 pandas 将相同记录平铺到 github_data.csv 中供电子表格使用。topics 列表在 JSON 中序列化良好,并以字符串形式出现在 CSV 列中。

输出示例

运行完整脚本,你将获得干净的公开字段记录,可直接加载到 notebook、数据库或电子表格中。

json
[
  {
    "name": "Java",
    "description": "All Algorithms implemented in Java",
    "stars": "59.1k",
    "forks": "19.5k",
    "watchers": "1.3k",
    "language": "Java",
    "topics": ["algorithms", "java", "data-structures"],
    "url": "https://github.com/TheAlgorithms/Java"
  },
  {
    "name": "Linus Torvalds",
    "username": "torvalds",
    "bio": null,
    "repositories": "8",
    "followers": "219k",
    "url": "https://github.com/torvalds"
  }
]

数字的具体显示格式(59.1k219k)直接来自 GitHub 渲染的缩写计数。如果需要精确整数,该值通常存在于元素的 title 属性中;当你需要对数字进行计算时,读取该属性而非可见文本。

扩展到多个仓库

单页脚本可以很自然地推广。若要调查一组项目,维护一个仓库 URL 列表,对其循环调用同一个 scrape_repository,累积记录后统一导出。

python
repo_urls = [
    "https://github.com/TheAlgorithms/Java",
    "https://github.com/pallets/flask",
    "https://github.com/psf/requests",
]

records = []
for url in repo_urls:
    html = crawl(url)
    if html:
        record = scrape_repository(html)
        record["url"] = url
        records.append(record)
    time.sleep(3)

保持请求间的延迟,关注状态码,并在获取所需内容后停止,而非无限制地爬取。关于在速率限制下保持健康运行的完整方法论,请参阅如何在不被封锁的情况下抓取网站。如果你希望通过自己的流量路由而不使用托管 API,Smart AI Proxy 提供相同的住宅轮换功能,作为直接替换的代理端点;我们的顶级开源抓取库盘点则介绍了自行搭建技术栈时的解析器和爬虫选择。

抓取 GitHub 是否合法?

请在编写生产代码之前阅读本节。出于个人或教育目的抓取 GitHub 公开页面通常是可辩护的,因为这些数据是为任何人公开发布的,无需登录即可读取。但这不是无条件的。GitHub 的可接受使用政策约束自动化访问,其 robots.txt 告知爬虫哪些路径禁止访问。请阅读两者并将其视为边界。永远不要访问私有仓库、需要登录才能看到的内容,或任何需要凭据才能访问的内容,也不要以降低站点服务质量的速度冲击其服务器。

个人主页数据需要格外谨慎,因为它描述的是真实的人。公开姓名、个人简介和粉丝数量都属于个人数据,在许多司法管辖区,GDPR 和 CCPA 等隐私法律在你收集和存储关于可识别个人的信息时即适用,即便该信息是公开的。这意味着你需要有合法的收集依据,只保留必要的数据,并响应删除请求。尽量以聚合方式处理(跨多个仓库的计数和趋势),而非建立具名开发者的详细档案,绝不重新发布个人的详情或将其足迹拼接成关于这个人的画像。

对于大多数工作,更好的工具是官方 GitHub REST API。它慷慨、免费供正常使用,并为仓库、用户、星标、Fork、编程语言和话题提供干净的结构化 JSON,无需解析任何 HTML。这是有官方背书的路径,能够在标记变化时继续正常工作,并附有可供规划的已记录速率限制。只有当某个特定公开页面暴露了 API 未提供的内容时,才需要使用爬虫,且要将该工作保持在小规模、有节奏、仅限公开非敏感字段的范围内。如果你的项目需要任何规模的 GitHub 数据,请从 REST API 开始,而非爬虫。

回顾

核心要点

  • GitHub 是服务器端渲染的,但有速率限制。 直接请求能返回标记,但来自单个 IP 的未认证流量很快就会被节流,因此需要通过轮换 IP 路由请求。
  • 普通 token 已经足够。 仓库和个人主页不需要 JavaScript 渲染,因此更便宜的普通 token 可以获取所需的一切。
  • 锚定在稳定的钩子上。 通过 itemprop 属性和 Octicon 图标类解析仓库字段,通过 vcard 类解析个人主页字段,而非自动生成的工具类。
  • 将个人主页数据视为个人数据。 只提取粗粒度的公开字段,以聚合方式处理而非刻画具体个人,并在存储时遵守 GDPR 和 CCPA。
  • 优先使用 GitHub REST API。 它免费、慷慨且结构化;只对 API 未涵盖的公开页面进行小规模、有节奏的爬取。

常见问题

GitHub 需要普通 token 还是 JS token?

普通 token。GitHub 在服务器端渲染仓库和个人主页,因此静态 HTML 中已经包含名称、描述、星标数和 Fork 数、编程语言、话题以及公开的个人主页字段。JS token 先在浏览器中渲染页面,成本更高,只有在 GitHub 某个依赖客户端渲染的特定视图时才需要使用。

抓取 GitHub 的哪些数据是安全的?

任何未登录访客均可查看的公开数据:公开仓库的名称、描述、星标数、Fork 数、关注数、主要编程语言和话题,以及公开个人主页的姓名、简介、公开仓库数量和粉丝数量。私有仓库、组织成员列表、电子邮件地址以及任何需要身份验证的内容均属禁区,无论是依据 GitHub 条款还是(对于个人数据而言)依据隐私法律。

我应该使用 GitHub REST API 而非爬虫吗?

对于大多数工作,是的。官方 GitHub REST API 对正常使用免费,速率限制慷慨,并为仓库、用户、星标、Fork、编程语言和话题返回干净的 JSON,无需解析任何 HTML。这是有官方背书的路径,能够在标记变化时继续正常工作。只有当某个特定公开页面暴露了 API 未提供的内容时才使用爬虫,并保持小规模和有节奏。

如何避免在抓取 GitHub 时被限速?

控制每个 IP 的请求速率,在请求之间添加真实的延迟(如上面的 time.sleep(3)),并通过轮换住宅 IP 路由,使任何单个地址都不触发限制。Crawling API 为你管理轮换和重试。密切关注状态码,一旦开始出现验证挑战或错误,立即退后重试,而非继续加压。

为什么星标数和粉丝数是"59.1k"这样的字符串?

因为这是 GitHub 在页面上渲染的缩写文本,而脚本读取的是可见文本。当你需要精确整数时,请查看元素的 title 属性,该属性通常保存精确数字,在进行任何计算之前读取该属性而非显示文本。

我可以抓取私有仓库或用户电子邮件地址吗?

不可以,本指南也有意不展示如何做到。私有仓库需要身份验证,而电子邮件地址属于个人数据,GitHub 不向匿名访客暴露。访问两者中的任何一个都意味着绕过访问控制或在没有合法依据的情况下收集个人数据,这两者都违反了 GitHub 条款和隐私法律。如需访问你控制的账号或组织,请通过官方 GitHub REST API 进行身份认证。

开始构建

大规模爬取任何站点,无需与基础设施对抗。

Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

自助开通 · 无需销售通话 · 提供企业级爬取量