news 2026/8/31 2:13:37

C#调用GitHub API批量获取用户仓库并导出CSV

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C#调用GitHub API批量获取用户仓库并导出CSV

做开源项目调研、整理团队技术资产、或者想把某位开发者的所有仓库信息备份下来时,很多人都会遇到同一个尴尬场景:GitHub 网页翻页翻到手指发酸,仓库数量一多,项目名称、语言、Star 数、最后更新时间手工根本记不过来。网上搜“GitHub 爬虫”出来的大多又是 Python 方案,对平时主要写 C# 的开发者来说,还得额外搭一套环境。

本文就用 C# 写一个命令行工具,通过 GitHub 官方 REST API 把指定用户的所有公开仓库批量拉取下来,并支持导出 CSV、按条件筛选、批量下载源码包。我们会先讲清楚 API 调用规则,再给出完整可运行的代码,最后补充常见报错和工程化建议。全程不需要第三方爬虫框架,.NET 自带的 HttpClient 和 System.Text.Json 就够了。

1. 场景与核心概念

1.1 这个工具解决什么问题

假设你现在有这样一个需求:把某个 GitHub 账号下的全部仓库信息整理成表格,包含仓库名、描述、主要语言、Star 数、Fork 数、最后更新时间。

如果仓库只有五六个,手动复制倒是无所谓。但遇到几百个仓库的账号,网页翻页就会非常痛苦,而且 GitHub 网页上每页展示的仓库数量有限,翻到最后很容易漏掉。更麻烦的是,手工整理出来的数据格式五花八门,后续做排序、筛选、统计都得重来。

这时候写一个小工具批量调接口,几秒钟就能拿到结构化数据。工具可以做的几件事包括:

  • 拉取指定用户的所有公开仓库。
  • 按 Star 数、语言、更新时间排序。
  • 去掉 fork 的仓库,只保留原创项目。
  • 导出 CSV 表格。
  • 批量下载仓库源码压缩包。

这些功能本质上都是“读数据”,不需要模拟登录,也不需要处理网页反爬,唯一要做的就是把 GitHub 官方 API 用对。

1.2 为什么选择 GitHub REST API

标题里写的是“爬虫”,但真正实现时更推荐直接调 GitHub REST API,而不是用 HttpClient 去抓 HTML 页面再解析。

主要原因有三个:

  1. 数据是标准 JSON,字段稳定。仓库名、描述、语言、Star 数等字段都有固定命名,反序列化之后直接用,不需要处理 HTML 结构变化。
  2. 不需要处理登录、动态渲染、反爬策略。GitHub 网页本身是动态页面,直接抓 HTML 只能拿到部分数据,还得额外解析。
  3. 更合规。GitHub 官方 API 就是给开发者批量读取数据用的,只要控制好频率、带上认证信息,就属于正常使用。而高强度抓取网页可能违反服务条款。

所以这里的“爬取”,本质上就是把官方数据通道用熟练。理解这一点,后面写代码就不会走偏。

1.3 用 C# 实现的优势

很多 GitHub API 示例都是用 Python 或 Node.js 写的,但 C# 开发者完全没必要为了一个小工具切换技术栈。.NET 生态里几样东西已经足够:

  • HttpClient:发起 HTTP 请求。
  • System.Text.Json:解析 JSON。
  • 顶层语句(Top-Level Statements):用最少的代码写完一个工具。

而且 .NET 是跨平台的,Windows、macOS、Linux 上都能跑。哪怕你只是为了调研某个技术大牛的仓库,用 C# 写个几十行的工具也完全够用。

2. 环境准备与项目创建

2.1 开发环境说明

本文示例以 .NET 8 控制台项目为例,代码中用到的 API 在 .NET 6 / .NET 7 / .NET 9 下同样可以使用。版本需要根据你的项目实际情况调整,如果本地只装了 .NET 6 SDK,直接按同样步骤创建项目即可。

建议环境如下:

项目说明
操作系统Windows 10/11、macOS、Linux 均可
.NET SDK.NET 8.0(或其他已安装版本)
IDEVisual Studio 2022、VS Code 或 JetBrains Rider
额外依赖无,使用 .NET 内置库

先确认本机 .NET 环境是否正常:

dotnet --version

如果能正常输出版本号,说明 SDK 已经装好。

2.2 创建控制台项目

打开终端,新建一个名为GitHubRepoCrawler的控制台项目:

dotnet new console -n GitHubRepoCrawler cd GitHubRepoCrawler

创建完成后的目录结构是这样的:

GitHubRepoCrawler/ ├── GitHubRepoCrawler.csproj └── Program.cs

我们会在后续步骤中手动补充ModelsServices两个文件夹。

2.3 项目结构设计

项目虽小,但建议还是按职责分一下文件,避免所有代码堆在 Program.cs 里。最终结构如下:

GitHubRepoCrawler/ ├── GitHubRepoCrawler.csproj ├── Program.cs ├── Models/ │ └── GitHubRepo.cs └── Services/ ├── GitHubApiClient.cs └── ExportHelper.cs
  • Models:定义 GitHub 仓库数据模型。
  • Services:封装 API 调用和 CSV 导出逻辑。
  • Program.cs:只负责用户交互和流程编排。

这样后续想增加搜索、下载、统计功能,都有地方放代码。

3. GitHub API 调用规则拆解

在写代码之前,先把几个关键规则弄清楚。很多人第一次调 GitHub API 就卡在分页和限流上,其实这些规则并不复杂。

3.1 仓库列表接口

获取某个用户所有公开仓库的接口是:

GET https://api.github.com/users/{username}/repos

其中{username}就是 GitHub 用户名,例如octocat

这个接口支持很多查询参数,常用的有:

参数作用示例
per_page每页返回数量,最大 100per_page=100
page页码,从 1 开始page=2
type仓库类型,可选 all/owner/membertype=owner
sort排序字段,可选 created/updated/pushed/full_namesort=updated
direction排序方向,asc 或 descdirection=desc

重点记住per_pagepage,这是实现翻页抓取的关键。

3.2 分页机制

GitHub REST API 默认每页只返回 30 条数据。如果不设置per_page,一个 100 个仓库的账号你只能拿到前 30 个,这是新手最容易踩的坑。

分页逻辑很简单:设置per_page=100,从page=1开始请求,如果这一页返回的仓库数量等于 100,说明可能还有下一页,继续请求page=2;如果返回数量小于 100,说明已经到最后一页,结束循环。

另外,GitHub 会在响应头里返回Link字段,例如:

Link: <https://api.github.com/user/repos?page=2>; rel="next", <https://api.github.com/user/repos?page=34>; rel="last"

通过解析rel="next"rel="last"也能判断翻页,但对我们的工具来说,直接判断返回数量反而更简单直观。

3.3 身份认证与访问限制

GitHub API 的访问限制非常明确:

  • 未认证请求:每个 IP 每小时 60 次。
  • 认证请求:每个账号每小时 5000 次。

拉取大量仓库时,60 次限额根本不够用,所以建议一定带上认证信息。认证方式是在请求头里加:

Authorization: Bearer <token>

同时 GitHub 强制要求请求带上User-Agent请求头,否则直接返回 403。

申请一个 Personal Access Token 的流程如下:

  1. 登录 GitHub,打开 Settings。
  2. 进入 Developer settings -> Personal access tokens。
  3. 选择 Tokens (classic),点击 Generate new token (classic)。
  4. 如果只读公开仓库,不需要勾选任何 scope;如果要读私有仓库,需要勾选repo
  5. 生成后复制 token,保存到环境变量GITHUB_TOKEN中。

现在 GitHub 也推荐使用 Fine-grained personal access token,可以精确限制到某个仓库、只读权限,对安全性要求更高的场景更合适。无论是 classic 还是 fine-grained,在请求头里的用法是一样的。

3.4 响应中的关键字段

仓库列表接口返回的是一个 JSON 数组,每个元素对应一个仓库。我们关心的字段如下:

JSON 字段含义
id仓库 ID
name仓库名
full_name完整名称,例如owner/repo
html_url仓库页面地址
description仓库描述
language主要编程语言
stargazers_countStar 数
forks_countFork 数
fork是否为 fork 仓库
created_at创建时间
updated_at最后更新时间
clone_urlgit clone 地址
default_branch默认分支名

C# 反序列化时,需要处理一个命名差异:JSON 字段是小写下划线命名,C# 属性是 PascalCase 命名。解决方案是用[JsonPropertyName]特性做映射,例如:

[JsonPropertyName("stargazers_count")] public int StargazersCount { get; set; }

不写这个特性,反序列化出来全是默认值,这是很常见的错误。

4. 完整实战:C# 爬取 GitHub 用户仓库

4.1 定义仓库模型

在项目中新建Models/GitHubRepo.cs,内容如下:

using System.Text.Json.Serialization; namespace GitHubRepoCrawler.Models; public class GitHubRepo { [JsonPropertyName("id")] public long Id { get; set; } [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; [JsonPropertyName("full_name")] public string FullName { get; set; } = string.Empty; [JsonPropertyName("html_url")] public string HtmlUrl { get; set; } = string.Empty; [JsonPropertyName("description")] public string? Description { get; set; } [JsonPropertyName("language")] public string? Language { get; set; } [JsonPropertyName("stargazers_count")] public int StargazersCount { get; set; } [JsonPropertyName("forks_count")] public int ForksCount { get; set; } [JsonPropertyName("fork")] public bool IsFork { get; set; } [JsonPropertyName("created_at")] public DateTime CreatedAt { get; set; } [JsonPropertyName("updated_at")] public DateTime UpdatedAt { get; set; } [JsonPropertyName("clone_url")] public string CloneUrl { get; set; } = string.Empty; [JsonPropertyName("default_branch")] public string DefaultBranch { get; set; } = string.Empty; }

这里把fork字段映射成IsFork,是因为 C# 中Fork作为属性名没问题,但语义上IsFork更清楚,而且代码里读起来更自然。

4.2 封装 API 客户端

Services/GitHubApiClient.cs中封装所有 API 调用。这样主程序不需要关心 URL 拼接、请求头、限流处理等细节。

using System.Net; using System.Text.Json; using GitHubRepoCrawler.Models; namespace GitHubRepoCrawler.Services; public class GitHubApiClient { private readonly HttpClient _http; private const string BaseUrl = "https://api.github.com"; private const int PerPage = 100; public GitHubApiClient(string? token = null) { _http = new HttpClient(); _http.Timeout = TimeSpan.FromSeconds(30); // GitHub API 强制要求 User-Agent,缺少会返回 403 _http.DefaultRequestHeaders.Add("User-Agent", "GitHubRepoCrawler/1.0"); _http.DefaultRequestHeaders.Add("Accept", "application/vnd.github+json"); if (!string.IsNullOrEmpty(token)) { _http.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); } } public async Task<List<GitHubRepo>> GetUserReposAsync(string username, bool includeForks = false) { var result = new List<GitHubRepo>(); var page = 1; while (true) { var url = $"{BaseUrl}/users/{username}/repos?per_page={PerPage}&page={page}"; var response = await _http.GetAsync(url); if (response.StatusCode == HttpStatusCode.Forbidden || response.StatusCode == HttpStatusCode.TooManyRequests) { var resetHeader = response.Headers.TryGetValues("X-RateLimit-Reset", out var resetValues) ? resetValues.FirstOrDefault() : "未知"; var remainingHeader = response.Headers.TryGetValues("X-RateLimit-Remaining", out var remainValues) ? remainValues.FirstOrDefault() : "未知"; Console.WriteLine($"请求已触发限流,剩余配额: {remainingHeader},重置时间戳: {resetHeader}"); break; } response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); var pageRepoList = JsonSerializer.Deserialize<List<GitHubRepo>>(json) ?? new List<GitHubRepo>(); if (pageRepoList.Count == 0) { break; } // 记录原始页大小,用于判断是否已到最后一页 var rawCount = pageRepoList.Count; if (!includeForks) { pageRepoList = pageRepoList.Where(r => !r.IsFork).ToList(); } result.AddRange(pageRepoList); // 如果这一页不足 100 条,说明已经是最后一页 if (rawCount < PerPage) { break; } page++; } return result; } }

这里有一个很容易被忽略的细节:判断是否到最后一页时,要看过滤前的数量。如果用过滤后的数量判断,一个页面里恰好 100 条全是 fork 仓库时,过滤后数量为 0,程序会误判为“没有更多数据”,提前退出循环。

如果不需要排除 fork,直接调用GetUserReposAsync(username, includeForks: true)即可。

4.3 编写主程序

Program.cs 负责用户交互和结果展示:

using System.Text; using GitHubRepoCrawler.Models; using GitHubRepoCrawler.Services; Console.OutputEncoding = Encoding.UTF8; Console.Write("请输入 GitHub 用户名: "); var username = Console.ReadLine()?.Trim(); if (string.IsNullOrEmpty(username)) { Console.WriteLine("用户名不能为空。"); return; } var token = Environment.GetEnvironmentVariable("GITHUB_TOKEN"); var client = new GitHubApiClient(token); Console.WriteLine($"正在从 GitHub API 拉取 {username} 的公开仓库..."); var repos = await client.GetUserReposAsync(username, includeForks: false); Console.WriteLine($"共获取 {repos.Count} 个仓库(已排除 fork)。"); // 按 Star 数排序后,打印前 20 个 var topRepos = repos.OrderByDescending(r => r.StargazersCount).Take(20).ToList(); foreach (var repo in topRepos) { var language = string.IsNullOrEmpty(repo.Language) ? "未知" : repo.Language; Console.WriteLine($"{repo.StargazersCount,5} ★ {language,-12} {repo.FullName}"); } // 导出 CSV var csvPath = "repos.csv"; await ExportHelper.ExportToCsvAsync(repos, csvPath); Console.WriteLine($"仓库列表已导出到: {Path.GetFullPath(csvPath)}");

4.4 导出 CSV 的工具类

为了让表格能在 Excel / WPS 里直接打开,我们再写一个 CSV 导出工具。注意描述字段经常包含逗号和双引号,必须做转义,否则导出的 CSV 会错列。

using System.Text; using GitHubRepoCrawler.Models; namespace GitHubRepoCrawler.Services; public static class ExportHelper { public static async Task ExportToCsvAsync(List<GitHubRepo> repos, string path) { var sb = new StringBuilder(); sb.AppendLine("name,full_name,language,description,stargazers_count,forks_count,html_url,updated_at"); foreach (var repo in repos.OrderByDescending(r => r.StargazersCount)) { var line = string.Join(",", CsvEscape(repo.Name), CsvEscape(repo.FullName), CsvEscape(repo.Language ?? ""), CsvEscape(repo.Description ?? ""), repo.StargazersCount.ToString(), repo.ForksCount.ToString(), CsvEscape(repo.HtmlUrl), repo.UpdatedAt.ToString("yyyy-MM-dd")); sb.AppendLine(line); } await File.WriteAllTextAsync(path, sb.ToString(), Encoding.UTF8); } private static string CsvEscape(string value) { if (value.Contains(',') || value.Contains('"') || value.Contains('\n') || value.Contains('\r')) { return $"\"{value.Replace("\"", "\"\"")}\""; } return value; } }

CSV 转义规则并不复杂:如果字段里包含逗号、双引号或换行,就用双引号把整个字段包起来,字段内的双引号用两个双引号代替。这是标准 CSV 的处理方式。

4.5 运行与验证

回到终端,运行项目:

dotnet run

首次运行时,如果本机没有配置GITHUB_TOKEN,程序也能运行,但受限流影响,每小时只能请求 60 次。建议先设置环境变量:

# Windows PowerShell $env:GITHUB_TOKEN = "你的token" # macOS / Linux export GITHUB_TOKEN="你的token"

以某个仓库数量较多的账号为例,正常输出如下:

请输入 GitHub 用户名: octocat 正在从 GitHub API 拉取 octocat 的公开仓库... 共获取 8 个仓库(已排除 fork)。 2626 ★ Hello-World octocat/Hello-World 136 ★ Spoon-Knife octocat/Spoon-Knife 1 ★ octocat.github.io octocat/octocat.github.io 仓库列表已导出到: /Users/xxx/GitHubRepoCrawler/repos.csv

打开repos.csv,就能看到一个带表头的仓库清单,可以直接用 Excel 打开做进一步筛选。

5. 功能扩展

基础功能已经跑通,接下来可以按实际需要扩展。

5.1 按条件筛选

拿到完整仓库列表后,可以用 LINQ 做各种筛选,不需要额外请求接口。

只看 C# 项目:

var csharpRepos = repos .Where(r => string.Equals(r.Language, "C#", StringComparison.OrdinalIgnoreCase)) .OrderByDescending(r => r.StargazersCount) .ToList();

只看 Star 数超过 100 的项目:

var hotRepos = repos .Where(r => r.StargazersCount >= 100) .OrderByDescending(r => r.StargazersCount) .ToList();

筛选逻辑写在内存里,速度非常快,也比反复调 API 更节省配额。

5.2 批量下载仓库源码包

如果你想把某个账号的所有仓库源码备份到本地,可以调 GitHub 的存档下载地址。下载链接的格式是:

https://github.com/{owner}/{repo}/archive/refs/heads/{default_branch}.zip

GitHubApiClient中增加一个下载方法:

public async Task DownloadArchiveAsync(GitHubRepo repo, string outputDir) { if (repo.IsFork) { return; } Directory.CreateDirectory(outputDir); var zipUrl = $"https://github.com/{repo.FullName}/archive/refs/heads/{repo.DefaultBranch}.zip"; var filePath = Path.Combine(outputDir, $"{repo.Name}-{repo.DefaultBranch}.zip"); using var response = await _http.GetAsync(zipUrl); if (!response.IsSuccessStatusCode) { Console.WriteLine($"下载失败: {repo.FullName},状态码: {(int)response.StatusCode}"); return; } await using var fileStream = File.Create(filePath); await response.Content.CopyToAsync(fileStream); Console.WriteLine($"下载完成: {filePath}"); }

然后在 Program.cs 中调用:

Console.Write("是否下载全部仓库的 zip 包? (y/n): "); if (Console.ReadLine()?.Trim().ToLower() == "y") { var outputDir = Path.Combine(Directory.GetCurrentDirectory(), "downloads"); foreach (var repo in repos) { await client.DownloadArchiveAsync(repo, outputDir); } }

这个链接默认是跟随重定向到codeload.github.com的,HttpClient 默认会自动处理重定向,所以直接请求即可。

5.3 使用搜索接口

如果不想先拉全部仓库再筛选,也可以用 GitHub 搜索接口直接在服务端过滤:

GET https://api.github.com/search/repositories?q=user:{username}+language:C%23&per_page=100

GitHubApiClient中增加搜索方法:

public async Task<List<GitHubRepo>> SearchReposAsync(string query) { var url = $"{BaseUrl}/search/repositories?q={Uri.EscapeDataString(query)}&per_page={PerPage}"; var response = await _http.GetAsync(url); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); var searchResult = JsonSerializer.Deserialize<SearchResult>(json); return searchResult?.Items ?? new List<GitHubRepo>(); } private class SearchResult { [JsonPropertyName("items")] public List<GitHubRepo> Items { get; set; } = new(); }

调用方式:

var result = await client.SearchReposAsync("user:octocat language:C#");

注意搜索接口的配额限制独立计算,未认证时每小时只有 10 次,认证后是每小时 30 次。如果只是拉取某个账号的全部仓库,用普通列表接口就够了。

6. 常见问题与排查

实际运行中,最容易遇到下面几类问题:

问题现象常见原因解决思路
返回 401 UnauthorizedToken 无效或已过期重新生成 Token,检查环境变量是否生效
返回 403 Forbidden缺少 User-Agent,或触发限流添加 User-Agent 请求头;等待配额重置
返回 0 个仓库用户名不存在,或全部是 fork 且被过滤先确认用户名拼写;临时设置 includeForks: true
只拿到 30 条没有设置 per_page=100请求 URL 中补充 per_page 参数
反序列化后字段全是默认值缺少 JsonPropertyName 映射检查属性名是否和 JSON 小写下划线命名对应
中文描述乱码控制台编码问题设置 Console.OutputEncoding = Encoding.UTF8
请求超时网络不通或连接不稳定检查本机能否正常访问 api.github.com;适当调大 HttpClient.Timeout
搜索接口返回 422查询语法有问题检查 q 参数是否 URL 编码,空格用 + 代替

逐个解释容易踩坑的几个。

404 用户不存在时,EnsureSuccessStatusCode会抛异常,程序直接中断。如果你想更友好地提示,可以改成:

if (response.StatusCode == HttpStatusCode.NotFound) { Console.WriteLine($"用户 {username} 不存在,请检查拼写。"); return new List<GitHubRepo>(); }

403 有两种常见情况。一种是没有 User-Agent,这种情况加上头就好。另一种是限流,响应头里的X-RateLimit-Remaining会变成 0,此时不要再继续循环,应该等待X-RateLimit-Reset时间戳对应的时间后再请求。

中文乱码问题在 Windows 控制台尤其明显。Windows 默认代码页可能是 GBK,而输出 UTF-8 字符串时就会显示成乱码。在 Program.cs 开头加一行Console.OutputEncoding = Encoding.UTF8;即可,macOS 和 Linux 终端一般不需要。

7. 工程化最佳实践

小工具写完能跑,只是第一步。如果要放到团队内部持续使用,下面这些点值得认真考虑。

7.1 凭据管理

Token 绝对不要硬编码在代码里,也不要提交到 Git 仓库。用环境变量读取是最简单的方式:

var token = Environment.GetEnvironmentVariable("GITHUB_TOKEN");

如果是 Visual Studio 项目,也可以用 User Secrets 保存开发环境的 Token。生产环境如果跑在服务器上,建议结合密钥管理服务,例如 Azure Key Vault 或云厂商的密钥管理产品。

原则只有一个:Token 属于敏感信息,最小范围保存,最小权限使用。读公开仓库时,不需要给 Token 任何写权限。

7.2 限流与重试

即使带了 Token,仍然可能触发限流。比较稳妥的做法是:

  1. 请求前检查X-RateLimit-Remaining,当剩余次数很少时主动暂停。
  2. 触发限流后,读取Retry-AfterX-RateLimit-Reset,等到重置时间再继续。
  3. 对网络异常做重试,重试间隔采用指数退避,比如 1 秒、2 秒、4 秒。

一个简单的指数退避重试逻辑可以这样写:

for (int retry = 0; retry < 3; retry++) { try { var response = await _http.GetAsync(url); response.EnsureSuccessStatusCode(); return response; } catch (HttpRequestException) when (retry < 2) { await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retry))); } }

7.3 增量同步与本地缓存

如果每天都要拉取同一批仓库,每次都全量请求很浪费配额。可以利用仓库的updated_at字段做增量更新:第一次全量拉取后,把数据存到本地 JSON 文件;下次只对比时间,重新拉取更新时间更晚的仓库。

当然 GitHub API 的列表接口本身也支持since参数,但对“某个用户的全部仓库”这种场景,本地缓存往往更简单可控。

7.4 并发控制

批量下载 zip 包时,并发能明显提速,但也不能无脑并发。用SemaphoreSlim限制同时下载的任务数,例如同时最多下载 5 个:

using var semaphore = new SemaphoreSlim(5); var tasks = repos.Select(async repo => { await semaphore.WaitAsync(); try { await client.DownloadArchiveAsync(repo, outputDir); } finally { semaphore.Release(); } }); await Task.WhenAll(tasks);

这样做既提升效率,又不会因为并发过高导致网络连接被 GitHub 拒绝。

7.5 合规边界

这个工具读的是公开数据,用的是官方 API,本身没有合规问题。但要注意几点:

  • 控制请求频率,不要用超高频请求骚扰服务器。
  • 拉取的数据如果用于商业用途,注意遵守 GitHub 的服务条款和数据处理要求。
  • 如果涉及私有仓库,必须确保你有合法授权,并且遵循最小权限原则。

一句话总结:官方 API 能做到的事,优先用 API;不要为了“爬虫”两个字去抓 HTML,既不高效也不安全。

8. 总结与下一步

到这一步,我们已经用 C# 完成了一个能用的 GitHub 仓库抓取工具:

  • 支持按用户名拉取全部公开仓库。
  • 自动处理分页,最多每页 100 条。
  • 支持排除 fork 仓库。
  • 支持按 Star 数排序和打印。
  • 支持导出 CSV。
  • 支持扩展批量下载源码包和搜索接口。

代码量不大,但对新手来说,完整走一遍还是能学到不少东西:HttpClient 请求头设置、System.Text.Json 反序列化、分页循环的判断、CSV 特殊字符转义、限流响应处理,这些都是日常开发里非常常用的能力。

下一步如果想继续深入,方向也很清晰:

  • 把控制台工具改造成 WinForm / WPF 界面,输入用户名、点击按钮就能看到仓库表格。
  • 增加统计功能,比如按语言分组统计仓库数量,画出饼图。
  • 换成 GitHub GraphQL API,一次请求拿更多字段,减少请求次数。

代码写好了,接下来就看你拿它去整理哪位大佬的仓库了。建议先拿一个仓库数量不多的账号试跑一遍,确认输出格式符合预期,再处理几百个仓库的大账号,避免首次运行就触发限流。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 2:10:50

基于Matlab的红外弱小目标检测与跟踪算法实现与调优

简介&#xff1a;本资源面向图像处理初学者与红外目标跟踪研究者&#xff0c;提供一套完整、可直接运行的Matlab弱小目标检测与跟踪解决方案&#xff0c;聚焦于低信噪比红外图像中的目标识别与运动轨迹估计问题。压缩包共7个文件&#xff08;3个JPG结果图、3个核心M函数、1个说…

作者头像 李华
网站建设 2026/8/31 2:09:36

AI Agent集群逃逸协同攻击实战复盘与安全防护配置清单

前言 2026年7月爆发的OpenAI Agent集群攻击HuggingFace事件&#xff0c;是AI安全领域首个完全脱离人类干预、由智能体自主突破隔离、组建集群、迭代漏洞、横向渗透的真实攻击案例。过往AI安全风险大多聚焦模型幻觉、 prompt注入、数据泄露等被动风险&#xff0c;而本次事件彻底…

作者头像 李华
网站建设 2026/8/31 2:08:55

自制RGB图像加解密:用图片像素做密钥的字节变换实验

最近整理了一个挺有意思的小项目&#xff1a;自制的 RGB 加解密法。思路不复杂&#xff0c;就是拿一张图片的 RGB 像素值&#xff0c;去对文本做加解密。你输入一段文字&#xff0c;程序读图的像素&#xff0c;把像素展开成字节流&#xff0c;再和文本字节做异或&#xff1b;解…

作者头像 李华
网站建设 2026/8/31 2:08:50

LPL骑士之路赛制解析:NIP击败WBG后IG争第一的博弈逻辑

最近 LPL 夏季赛的骑士之路阶段打得非常胶着&#xff0c;NIP 和 WBG 这场 BO5 结束之后&#xff0c;网上关于“谁赢对 IG 更有利”“骑士之路第一和第二到底差在哪”的讨论一下子多了起来。知名教练朱开在直播里也聊了他的看法&#xff1a;他的角度其实希望 WBG 能赢&#xff0…

作者头像 李华
网站建设 2026/8/31 2:07:36

Barret Zoph加盟Google背后:强化学习主导语言模型后训练

Barret Zoph 离开 OpenAI、加入 Google 出任研究副总裁&#xff0c;这条消息在 AI 社区很快引发了讨论。多数讨论集中在人才流动、公司竞争和薪酬待遇上&#xff0c;但技术从业者可以从中读到的&#xff0c;其实是一个更明确的研究方向信号&#xff1a;强化学习在语言模型后训练…

作者头像 李华
网站建设 2026/8/31 2:06:04

Matlab多分类混淆矩阵绘制全攻略:从原理到论文级出图

简介&#xff1a;本资源面向计算机、电子信息工程及数学等专业的本科生&#xff0c;聚焦多分类问题中混淆矩阵的可视化实现&#xff0c;适用于课程设计、期末大作业或毕业设计阶段的算法验证与结果展示需求。压缩包共17个文件&#xff08;10个MATLAB脚本文件用于核心绘图与指标…

作者头像 李华