很多人第一次遇到“只想下载 GitHub 仓库里某个文件夹”的需求时,第一反应都是把整个仓库 clone 下来,再慢慢翻。结果仓库动不动几百 MB,带着一整套 .git 历史,大一点的 monorepo 甚至能到几个 G,网络稍差点一下午就搭进去了。这篇就把我实际用过的、见过别人用的几种“只下载 GitHub 仓库单个文件夹”的方法全部整理出来,从给新手用的网页工具,到命令行一条指令搞定,再到适合写进自动化脚本的 API 方案,一次性讲透。不管你是刚摸 GitHub 的新手,还是要经常在 CI 里按需拉目录的开发者,都能找到能直接抄作业的那一种。
1. 为什么会有“只下载单个文件夹”的需求
1.1 克隆整个仓库的痛点
Git 的设计初衷是“全量克隆”,也就是你 clone 一个仓库时,默认会把所有分支、所有历史提交、所有文件全部拉到本地。对普通小项目来说这没什么,但现实是 GitHub 上有大量仓库根本不是为了让你全量拉取的。典型的几种情况:一是大型 monorepo,一个仓库里同时放着前端、后端、文档、数据、脚本,你只想要其中某个子包的代码;二是配置型仓库,里面收集了几百个工具的配置模板,你只需要其中某一个;三是文档类仓库,整个仓库几百个 markdown 文件,你只是想把某个专题目录离线保存。
全量 clone 在这种场景下是纯粹的资源浪费。我见过有人为了下载一个不到 2 MB 的配置文件目录,把一个带了大版本历史、总计 1.3 GB 的仓库拖下来,中间还断了几次,只能用git fetch --depth 1反复续。这也是为什么“只下载单个文件夹”这个需求在社区里反复出现——问题太常见了,但偏偏很多新手不知道有更聪明的解法。
1.2 这些场景最适合“按需拉取”
按需拉取子目录本质上是“部分克隆”思路的延伸,它最适用的场景我觉得可以归纳成这么几类。
第一类是临时取用。你只是想看看某个目录里的代码长什么样,或者想拿到某个配置文件放到自己的项目里,根本没有参与这个仓库开发的意思。这时全量 clone 纯属浪费时间和磁盘。
第二类是自动化流程。CI 脚本里要拉取依赖的某个公共模块,或者部署脚本需要从仓库里同步一份指定目录到服务器。这种场景下每多拉取 1 MB 都是成本,用稀疏检出可以显著缩短流水线时间。
第三类是大仓库的单点开发。你参与的项目本身是 monorepo,但你只负责其中一个子包,其他包很少改动。用 sparse-checkout 把工作区限制在自己的目录里,git status、git diff、git log 都会轻量很多,不会再被其他模块的变动刷屏。
1.3 方案选型:先看对比再选
在展开讲每种方法之前,我先放一张方案对比表,方便你按自己的场景对号入座。
| 方案 | 原理 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|---|
| 第三方网页工具(DownGit 等) | 服务端帮你按路径抓取并打包 | 无需安装、操作直观 | 依赖第三方服务稳定性,大目录容易超时 | 临时下载、不想碰命令行 |
| 浏览器插件(GitZip 等) | 在仓库页面注入下载按钮 | 快捷,不用离开页面 | 插件权限风险,依赖非官方维护 | 偶尔想下载,愿意接受插件 |
| SVN 客户端稀疏检出 | 借助 GitHub 的 SVN 协议导出子目录 | 一条命令即可 | GitHub 已逐步收紧 SVN 支持,旧命令不一定可用 | 老旧环境里的应急手段 |
| Git sparse-checkout + 部分克隆 | Git 官方功能,本地只检出指定目录 | 干净、可控、可重复操作 | 对 Git 版本有要求(2.25+) | 绝大多数场景,强烈推荐 |
| GitHub API 脚本 | 通过 REST API 递归拉取目录内容 | 精确控制、适合自动化 | 直接写文件,不走 Git 语义 | 脚本化同步、CI 流程 |
看得出来我现在最推荐的是第四种,也就是 Git 官方自带的 sparse-checkout。后面我会重点讲它,但其余几种我也会逐个拆开讲清楚,因为你总会遇到一些特殊环境,比如对方的服务器上只有 SVN、或者你临时用的电脑没装新版 Git。
2. 最经典的做法:用 SVN 客户端做稀疏检出
2.1 这个方案是怎么来的
很多人不知道,GitHub 在早期是开放过 SVN 协议的,也就是说你可以用svn命令直接访问 GitHub 仓库。SVN 和 Git 最大的不同是,SVN 支持“部分检出”,你不需要把整个仓库的所有目录都拉下来,只要指定路径就能获取对应内容。
所以早期网上流传的“只下载 GitHub 某个文件夹”教程,绝大多数是让你先装一个 SVN 客户端,然后执行类似这样的命令:
svn export https://github.com/owner/repo/trunk/path/to/folder这里/trunk/是 SVN 对默认分支的映射写法。如果仓库默认分支不是 main 而是 master,那就写/trunk/;如果想指定其他分支,可以写成/branches/分支名/路径。这个语法一开始很容易把人绕晕。
2.2 SVN 方案的实际操作
先安装 SVN 客户端,各系统的方式不太一样:
# macOS brew install subversion # Ubuntu / Debian sudo apt install subversion # Windows choco install subversion然后进入你要保存文件的目录,执行:
svn export https://github.com/owner/repo/trunk/docs执行完之后,当前目录会出现一个docs文件夹,里面就是目标目录的文件,而且完全没有.git、.svn这些版本控制目录,非常干净。
2.3 为什么我不把它当首选
这个方案最大的问题是 GitHub 对 SVN 协议的支持已经明显收敛了。GitHub 官方文档早就不再推荐这种方式,一些新仓库、启用了某些特性的仓库,用 SVN 协议访问时会出现各种奇怪问题。我自己有一次帮同事下载一个刚创建不久的仓库目录,SVN 命令直接报错找不到路径,最后换成 Git sparse-checkout 才解决。
所以我的建议是:如果你在非常老旧的服务器环境里,只有 SVN 客户端而没有新版 Git,这个方案可以拿来应急。但只要条件允许,请直接看下一节的 Git 原生方案,它才是真正可靠、受官方支持的做法。
3. 官方推荐:Git 自带的 sparse-checkout 稀疏检出
3.1 原理先搞清楚
sparse-checkout 的中文意思是“稀疏检出”。它解决的核心问题是:Git 在工作区里只“铺开”你指定的目录,其他目录虽然存在于对象数据库里,但不会出现在你的本地文件系统中。
这里有一个关键概念要区分:检出和下载。传统git clone是又下载又检出,所有文件都会落到磁盘。而 sparse-checkout 模式配合部分克隆参数,可以做到需要哪个目录才去下载哪个目录,这才是真正的“只下载单个文件夹”。
Git 2.25 版本开始,sparse-checkout 的功能被大大增强,出现了git sparse-checkout set这种子命令。再配合--filter=blob:none这种部分克隆参数,可以只下载提交历史和目录树,而不下载文件内容,等 checkout 到具体目录时才会按需拉取 blob 对象。这套组合拳打下来,下载体积能有数量级的缩减。
3.2 一条龙命令:从零开始只拉取一个文件夹
我现在最常用的命令是这样的:
git clone --depth 1 --filter=blob:none --sparse https://github.com/owner/repo.git cd repo git sparse-checkout set your/target/folder解释一下每个参数的作用:
--depth 1:浅克隆,只拉取最近一次提交,丢弃历史记录。如果你需要查看历史提交,就不要加这个参数。--filter=blob:none:部分克隆模式,跳过所有文件内容(blob),只保留提交和目录树。--sparse:让仓库在 clone 完成后立即进入稀疏检出模式。
这三者组合在一起的效果是:clone 阶段只下载最少的元数据,然后git sparse-checkout set your/target/folder才会真正去拉取你指定目录里的文件内容。
执行完之后,工作区里只有一个your/target/folder目录,干干净净。
3.3 版本要求与检查
这个方案对 Git 版本有要求。--filter和--sparse参数在 Git 2.25 及以上才被完整支持,所以先用git --version检查一下。如果版本太老,优先考虑升级 Git,而不是退回 SVN 方案。
各系统升级 Git 的方式也简单提一下:
# macOS brew upgrade git # Ubuntu / Debian sudo add-apt-repository ppa:git-core/ppa sudo apt update sudo apt install git # Windows 直接去 Git 官网下载最新版安装包覆盖安装3.4 后续操作:加目录、切分支、恢复全量
git sparse-checkout set是可以反复执行的。比如你刚才只拉取了docs目录,现在又想追加一个scripts目录,有两种写法:
# 一次性列出多个目录(会替换之前的配置) git sparse-checkout set docs scripts # 或者追加模式(保留之前的目录) git sparse-checkout add scripts注意set默认是“覆盖”而不是“追加”。我第一次用的时候就没注意,执行git sparse-checkout set scripts之后发现docs目录直接从工作区消失了,还以为文件被删了,吓一跳。实际上文件还在 Git 对象库里,只是没有被检出到工作区。
切换分支也完全没问题:
git checkout other-branch切换后当前 worktree 会保留 sparse-checkout 的限制,只会显示出你之前设置的目录。如果新分支上这个路径不存在,Git 会提示你路径没匹配到任何文件。
想恢复成普通模式、把整个仓库完全检出,执行:
git sparse-checkout disable这条命令会取消稀疏检出限制,然后把所有文件都拉取到工作区。
3.5 这个方案的隐藏优势
除了省流量、省磁盘,sparse-checkout 还有一个容易被人忽略的好处:它能显著降低git status和git diff的噪音。
我有一次在一个大仓库里改一个模块的代码,全量检出状态下git status要卡好几秒,而且因为别人提交了别的模块的改动,终端里刷出一大堆我看不懂的文件变更。切到 sparse-checkout 模式后,工作区里只有我负责的目录,状态检查变得非常轻量,也不会再被无关目录的变更干扰。对于 monorepo 场景下的日常开发,这个价值甚至比省流量更大。
4. 偷懒与自动化:第三方网站、浏览器插件、API 脚本
4.1 DownGit 这类网页工具怎么用
如果你完全不想碰命令行,DownGit 这类网页工具是最快的选择。
使用过程非常简单:打开 DownGit 页面,粘贴你目标仓库中某个文件夹的完整网页地址(形如https://github.com/owner/repo/tree/main/docs),它会自动解析出仓库名和目录路径,然后你点击 Download,就能得到一个 zip 包。
但这类工具我要提醒几个坑。第一,它本质上是服务端在帮你做一次网络请求,目标仓库太大的时候很容易超时失败。第二,我曾经用这类工具下载过一个目录,zip 解压后发现里面居然包含了整个仓库的所有文件,说明这个工具在服务端直接做了一次完整 clone 再打包,完全违背了“只下载单个文件夹”的初衷。第三,绝对不要把自己的私有仓库地址粘贴到这类第三方网站上,你的代码会在别人服务器上过一遍,存在严重的泄露风险。
所以我的定位是:临时下载公开小仓库的某个目录,图个省事可以用,但不要把它当成可靠的工作流。
4.2 浏览器插件:GitZip 这类工具的便利与风险
浏览器插件是另一条“不用命令行”的路子。装好 GitZip 这类插件后,你打开 GitHub 仓库页面,鼠标悬停在某个文件夹上,就会浮现一个下载按钮,点击就能把这个文件夹打包下载。
它的体验确实很顺滑,但也带来两个问题。一个是权限问题,这类插件通常要申请读取你的 GitHub 页面数据,有些还会请求获取你的账号信息,安装前一定要看仔细。另一个是维护问题,GitHub 的页面结构隔一段时间就改一次,插件跟不上就失效了,这类工具的生命周期往往不长。
我自己现在基本不用浏览器插件了,因为稀疏检出已经足够简单,而且更可靠。
4.3 用 GitHub API 写个小脚本:适合自动化场景
如果你需要把这个需求做成脚本,希望在 CI 或者其他自动化流程里按需同步某个目录,那么直接用 GitHub REST API 是更干净的方式。
思路也很简单:API 可以列出某个路径下的内容,文件自动下载,目录递归进入。下面是我写的一个 Python 示例,只依赖标准库和requests,可以直接跑:
import os import requests API = "https://api.github.com/repos/{owner}/{repo}/contents/{path}" RAW = "https://raw.githubusercontent.com/{owner}/{repo}/HEAD/{path}" HEADERS = { # 如果目标仓库是私有的,填上你的 GitHub Token # "Authorization": "token ghp_xxx" } def download_dir(owner, repo, path, local_dir): url = API.format(owner=owner, repo=repo, path=path.lstrip("/")) resp = requests.get(url, headers=HEADERS) resp.raise_for_status() items = resp.json() for item in items: item_path = item["path"] item_type = item["type"] if item_type == "dir": print(f"[DIR] {item_path}") download_dir(owner, repo, item_path, local_dir) else: print(f"[FILE] {item_path}") save_path = os.path.join(local_dir, item_path) os.makedirs(os.path.dirname(save_path), exist_ok=True) raw_url = RAW.format(owner=owner, repo=repo, path=item_path) file_resp = requests.get(raw_url, headers=HEADERS) file_resp.raise_for_status() with open(save_path, "wb") as f: f.write(file_resp.content) if __name__ == "__main__": download_dir("owner", "repo", "docs/configs", "downloads")这里有一个细节要解释:列表接口返回的是文件元数据,并不直接包含文件内容,所以我是用contentsAPI 列出目录、再用raw.githubusercontent.com逐个拉取文件内容。这样对二进制文件也安全。
这个方案的优势是精确可控、方便集成到现有脚本里。限制也很明显:GitHub API 未认证时每小时只能请求 60 次,如果目录层级很多、文件很多,很容易触发限流。所以建议在 HEADERS 里带上自己的 Token,配额能提升到每小时 5000 次。另外,这个脚本拉取的是文件“内容”,不是 Git 历史,所以拿到的是一张不包含版本信息的快照,适合同步而非开发场景。
5. 实操过程实录:一个完整的示例
5.1 场景设定
接下来我用一个具体场景把整个流程走一遍,方便你对照操作。
假设我有一个大型前端 monorepo 仓库,仓库地址是https://github.com/example/big-frontend,里面大致长这样:
big-frontend/ ├── apps/ │ ├── web/ │ └── admin/ ├── packages/ │ ├── ui/ │ ├── utils/ │ └── api-client/ ├── docs/ │ └── api/ ├── node_modules/ └── package.json我的需求是:只下载packages/utils这个目录,其他一律不要。
5.2 逐步操作
第一步,先确认 Git 版本,保证用得上稀疏检出特性:
git --version # 输出示例:git version 2.39.2第二步,执行部分克隆加稀疏检出:
git clone --depth 1 --filter=blob:none --sparse https://github.com/example/big-frontend.git cd big-frontendclone 阶段因为--filter=blob:none的存在,下载量非常小,基本只拉取了提交快照和目录树信息,几秒钟就完成。
第三步,设置需要检出的子目录:
git sparse-checkout set packages/utils这一步会真正触发packages/utils目录下所有文件的下载,然后将其检出到工作区。执行结束后,用ls看看工作区:
ls # 输出示例:packages package.json README.md注意根目录的package.json和README.md依然会出现,因为这些是仓库根路径上的文件,稀疏检出按目录维度控制,但默认会保留根目录下的常规文件。目录层面则只出现了packages,而apps、docs、node_modules通通没有被检出。
第四步,验证一下取得的目录内容是否完整:
ls packages/utils # 输出示例:index.ts helpers constants tests内容完整,命令到此结束。
5.3 这个过程中我踩过的两个小坑
第一个坑是git sparse-checkout set之前我忘了cd进仓库目录,结果命令直接报错not a git repository。这个属于低级错误,但小白很容易犯,记住切换目录这步不能省。
第二个坑是初次 clone 时我没有加--depth 1,结果虽然只有目录树和提交元数据,但仓库历史特别长,resolve 阶段依然花了不少时间。对于只想拿最新快照的场景,--depth 1一定要加上,收益非常明显。
6. 常见问题与排查技巧实录
6.1 高频问题速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
git sparse-checkout命令不存在 | Git 版本低于 2.25 | 升级 Git,或改用git config core.sparseCheckout true+ 编辑.git/info/sparse-checkout文件 |
执行set后发现之前的目录消失了 | set默认覆盖而非追加 | 想要多个目录时一次性列全,或用git sparse-checkout add |
clone后工作区是空的,没有任何文件 | --sparse模式下默认只检出根目录 | 执行git sparse-checkout set 目标目录生成内容 |
| 目录下载了一半被中断 | 网络波动或目标目录太大 | 重新执行git sparse-checkout set,Git 会复用已下载的对象 |
| 下载的是 LFS 大文件,体积依然很大 | 目标目录含有 Git LFS 对象 | 确认仓库是否启用了 LFS,考虑用 API 方式按需拉取具体文件 |
| 目标目录里有 submodule 子模块 | 稀疏检出不会自动处理子模块 | 需要对子模块单独执行git submodule update --init |
| 只想下载单个文件,不想建仓库 | 稀疏检出按目录维度控制 | 直接用raw.githubusercontent.com下载,或者用 API 脚本 |
| 私有仓库下载 404 | 当前 Git 没有认证信息 | 使用带 Token 的 URL 或先配置好 credential helper |
6.2 几个值得单独展开的问题
关于老版本 Git 的兼容写法。如果你确实没办法升级 Git,可以用老式手写配置代替git sparse-checkout set:
git clone --no-checkout https://github.com/owner/repo.git cd repo git config core.sparseCheckout true echo "your/target/folder/" >> .git/info/sparse-checkout git checkout main这个方式的原理是手动打开 sparseCheckout 开关,然后在.git/info/sparse-checkout文件里写入要检出的目录模式。Git 读到这个配置后,checkout 时就会只铺开匹配的目录。记住目录末尾要加/,否则匹配规则会出问题。
关于文件被误删的焦虑。前面提到过,git sparse-checkout set覆盖配置后,之前检出的目录会从工作区消失。很多人第一次看到git status里出现一堆deleted会慌,以为文件真没了。这里要明确说一下:文件只是不再出现在工作区,它们依然在 Git 对象库里,随时可以用git sparse-checkout set old_dir加回来。理解“检出”和“删除”的区别是掌握这套机制的关键。
关于网络中断的续传问题。部分克隆模式下载到一半断网,重新执行git sparse-checkout set,Git 会基于已下载的对象继续,不会从头再来。这一点比第三方网页工具体验好得多,也是我强推它的原因之一。
关于想下载的目录里包含 submodule。这是一个比较细节的问题。如果一个目标目录本身是 submodule,或者里面嵌套了 submodule,稀疏检出不会自动帮你处理子仓库内容,你需要单独进入对应目录,执行:
git submodule update --init --recursive如果不执行,目录看起来是空的,很容易被你误判为下载失败。
关于只想下载单文件的场景。如果需求降级为“只要某个文件”,其实不需要任何 Git 操作,直接用 raw 链接就行:
curl -L -o config.json https://raw.githubusercontent.com/owner/repo/main/config.json这种方式最简单,也不消耗 GitHub API 配额,适合偶尔一次性的文件下载。
7. 几个补充的实操心得
讲完方法和问题排查,最后再分享几点我实际用下来的体会。
第一个体会是:不要迷信第三方工具,也不要拒绝命令行。我在文章开头说过自己也会用 DownGit 图省事,但用过几次后发现它偶尔会悄悄下载整个仓库,反而更慢。后来我强迫自己把那行 clone 命令背下来,现在已经是肌肉记忆了,效率比打开网页再找工具高得多。你现在如果只记住一句话,就记住这个组合:
git clone --depth 1 --filter=blob:none --sparse <仓库地址> && cd <仓库名> && git sparse-checkout set <目标目录>第二个体会是:sparse-checkout 不是只能用于“下载”,它更适合作为 monorepo 日常开发的标配。我现在的项目工作流基本是:sparse-checkout 只检出自己维护的包,配合部分克隆,无论git status还是git fetch都轻快很多。你可以把这种模式理解成给 Git 做了一层“瘦身滤镜”,只让你关心的问题出现在眼前。
第三个体会是:GitHub API 脚本的方案虽然写起来麻烦一点,但在自动化场景里是无可替代的。如果你要做定时同步、或者把某个目录内容同步到服务器,用稀疏检出的方式每次都要处理 Git 仓库状态,而 API 脚本直接拉文件写磁盘,逻辑要简单得多。
根据我的经验,把这几种方法同时记在脑子里,遇到“只下载单个文件夹”的需求时就不用再对着整个仓库发愁了。哪种顺手用哪种,但请务必优先考虑 Git 官方的 sparse-checkout 方案,它才是所有方法里最经得起时间考验的那一个。