我曾经熬过无数个深夜,专门排查那些本来只是“把文件下载下来然后继续下一步”的脚本。十有八九,问题都不是 cURL 坏了,而是它非常老实地执行了我下的命令,却不是我真正想要的结果。说到底,“curl -O 能用”和“curl -O 能在生产环境里稳定跑”之间,差的可不是一点点。
这篇指南就是为了补上这段差距而写的。cURL 在 macOS、绝大多数 Linux 发行版,以及 Windows 10 及以上版本中都预装了,也就是说,你电脑里大概率已经有它了。但真到了实际使用时,重定向静默失败、莫名其妙的 403、还有从“下载一个文件”变成“批量下载 500 个文件还不能把终端拖死”的各种问题,都会让人卡住。我会带你过一遍最关键的参数、我自己一直在用的命令、最常见的报错,以及 cURL 真不顶用时该怎么办——也就是该换什么工具。
什么是 cURL?为什么你应该了解它?
cURL 是一个免费、开源的命令行工具,用来通过 URL 在服务器之间传输数据。它支持 HTTP、HTTPS、FTP、SFTP 以及很多其他协议,所以你会在 bash 脚本、Dockerfile、CI 流水线等各种地方看到它。你在终端里输入的 curl 命令,底层其实调用的是 libcurl——很多应用和语言封装都会嵌入这个 C 语言传输库。比如 PHP 的 cURL 扩展就是一个例子;而 Python 很流行的 Requests 库则是基于 urllib3 构建的独立 HTTP 客户端,并不是 libcurl。
截至本文写作时,当前稳定版本是 curl 8.21.0,发布于 2026 年 6 月——但别想当然地以为你操作系统自带的就是这个版本。系统仓库里的 curl 往往会比上游项目慢几个月,甚至更久,所以在假设某个参数(比如 --parallel)能用之前,最好先跑一下 curl --version。
为什么要用 cURL 下载文件?常见使用场景
经常有人问我:既然浏览器也能下载文件,为什么还要用命令行工具?答案很简单:浏览器适合手动下载,但一旦你要做自动化,cURL 的价值就出来了。
| 使用场景 | cURL 的优势 |
|---|---|
| 在 CI/CD 流水线中下载二进制文件 | 可脚本化,无需图形界面 |
| 获取 API 响应或数据导出 | 支持自定义请求头、认证和输出管道 |
| 通过 SSH 续传大文件 | 内置断点续传支持(-C -) |
| 自动执行定时下载(cron 任务) | 轻量,方便和 shell 脚本组合 |
| 下载需要认证才能访问的文件 | 认证参数很灵活(Basic、Token、Cookies、.netrc) |
浏览器下载一次,只是你手动点一下而已。cURL 能把同样的操作变成可调度、可串进流水线、失败可重试、还能在上百台服务器上保持一致执行的流程。这才是它真正的吸引力——不是更花哨,而是更可重复。

下载文件最常用的 cURL 参数
我在 90% 的场景里,反复会用到的也就十来个参数。下面这份速查表,是我希望几年前有人直接递给我的版本,我按实际用途分好了类。
输出与保存文件相关参数
-O(--remote-name)会把 URL 最后一段当作文件名保存下来。很方便,但如果本地已经有同名文件,它可能会悄悄覆盖掉。-o <filename>(--output)允许你自己指定保存的文件名:curl -o report.pdf https://example.com/downloads/file.pdf。-J(--remote-header-name)会优先使用服务器Content-Disposition头里的文件名,而不是 URL 里的名字。这个对 API 下载很方便,但服务器给的文件名别默认就信,最好下载到专门目录里,不要直接放进主目录;curl 官方安全建议也是这么说的。
每次下载都该考虑的行为参数
-L(--location)告诉 curl 跟随 HTTP 重定向。不加它的话,3xx 响应往往会被当成一小段 HTML 重定向页面存下来,而不是你真正要的文件——这是我见过最多的“为什么下载坏了”问题。-C -(--continue-at -)用于从中断处继续下载。-s/-S会静默运行,但仍然显示错误信息——很适合不想在日志里塞满进度条的脚本。--limit-rate 1M用来限制带宽(共享网络,或者你不想把流量占满时很有用)。--connect-timeout 10和--max-time 300可以防止卡死的连接把脚本一直挂住。--retry 3和--retry-delay 5会在短暂故障时自动重试——根据 curl 手册页 的说明,只有在“重复完全相同的请求”确实安全时,才建议配合--retry-all-errors使用。
进度与调试相关参数
-#会显示一个简单的进度条,而不是默认的统计表。-v会输出详细信息,包括完整的请求/响应头——出问题的时候,这是我最常用的排查方式。-I(--head)只获取响应头,适合在正式下载大文件前先做一次探测。-w可以在传输结束后输出自定义内容,比如curl -o /dev/null -s -w "%{http_code}\n" <url>,只看状态码。
开始之前
- 难度: 初学者到中级(批量下载和认证部分会稍微进阶一些)
- 预计时间: 约 15–20 分钟可以过完核心命令
- 你需要准备: 一个终端(macOS Terminal、Linux shell 或 Windows PowerShell/WSL)、已安装的 curl(用
curl --version检查),以及一个测试 URL——我会用公开的 GitHub Release 资源作为示例,因为它稳定而且可自由访问
如何使用 cURL 下载文件:一步一步来
第 1 步:下载单个文件
最基础的用法:curl -O <url> 会按原始文件名保存,而 curl -o myfile.zip <url> 则允许你在下载时顺手改名。
curl -LO https://github.com/curl/curl/releases/download/curl-8_21_0/curl-8.21.0.tar.gz
我现在默认都会加上 -L,而且不管什么情况都加——我已经被重定向悄悄变成一个 400 字节 HTML 文件坑过太多次了。执行后,你应该会看到终端里的进度条往前走,最后文件出现在当前目录。
命令成功后,进度会到 100%,并且当前目录里会出现 curl-8.21.0.tar.gz。在继续使用之前,先确认文件是不是真的下好了:
ls -lh curl-8.21.0.tar.gz
第 2 步:下载并重命名文件
如果你想要一个指定的本地文件名,而不是 URL 末尾那个名字,就用 -o:
curl -L -o curl-latest.tar.gz -S https://github.com/curl/curl/releases/download/curl-8_21_0/curl-8.21.0.tar.gz
这里的 -S 是为了在你脚本里别处已经用了 -s 时,仍然能把错误信息显示出来。这个组合——-L -o <name> -S——基本就是我默认的单文件下载命令。
第 3 步:续传中断的下载
如果大文件下载到一半断了(网不好、VPN 抖了一下,或者别的原因),别重来。直接运行:
curl -C - -LO https://example.com/large-file.iso
但要注意:这只有在服务器支持字节范围请求时才有效。Accept-Ranges: bytes 是一个有用的积极信号,但它不存在并不代表一定不支持范围请求。更靠谱的判断方式,是看服务器对真实范围请求的响应:可续传的响应通常会返回 206 Partial Content,并带有有效的 Content-Range。执行续传命令后,可以用 -v 或 -D - 检查状态;如果服务器忽略了范围请求或者拒绝了偏移量,就老老实实重新开始,不要想当然地以为那个半成品文件还能直接用。

第 4 步:显示进度条下载,或者静默下载
如果你是在交互式终端里看着更舒服,可以用:curl -# -LO <url>。如果是脚本或 cron 任务,只想保留错误,不想输出杂音,就用:curl -sS -LO <url>。除了手动调试,我几乎到处都在用静默版本。
第 5 步:限制下载速度
在共享办公网络上,或者不想在视频会议时把带宽全占了,我会这样限速:
curl --limit-rate 1M -LO https://example.com/big-dataset.zip
单位分别是 K、M、G,表示每秒千字节、兆字节和吉字节。
第 6 步:在保存文件的同时保存响应头
有时候我需要知道服务器到底返回了什么——比如内容类型、缓存头之类——又不想让终端太乱:
curl -L -D headers.txt -o file.zip https://example.com/file.zip
这会把响应头写入 headers.txt,而实际文件则保存为 file.zip。在排查内容类型不匹配,或者验证 CDN 是否真的按预期缓存时,这个特别好用。
使用技巧与常见坑
- 技巧: 默认就加上
-L。我真想不出不加它有什么好处,反而已经因为忘记加它浪费了好几个小时。 - 技巧: 写脚本时,把
--fail一起带上,这样非 2xx 响应就会真的让脚本以错误退出,而不是把错误页面悄悄存成“文件”。 - 坑: 不要把
-C -和--remove-on-error搭配使用——curl 文档明确说明这两个选项不兼容,因为续传需要那个未完成的部分文件还留着。 - 坑:
-O可能会无提示覆盖现有文件。如果你在共享目录里批量下载,最好用--output-dir把文件都收纳到指定目录。
如何使用 cURL 下载多个文件和批量下载
单文件示例最简单。真正的工作流——比如拉取每晚的数据导出、在不同构建服务器之间同步二进制文件——往往都需要并发,而很多教程讲到这里就没了。下面这三种方式值得了解,复杂度也逐级上升。
方式 1:在一个 cURL 命令里写多个 URL
最简单的办法就是直接列出多个地址:
curl -LO https://example.com/a.zip -LO https://example.com/b.zip -LO https://example.com/c.zip
这个可以用,但它是串行的——curl 会先完整下载一个文件,再开始下一个。三个文件还行,三百个就很痛苦了。
方式 2:使用 --parallel 并行下载(curl 7.66+)
从 curl 7.66 开始,你可以加上 --parallel(或者 -Z)并发获取多个 URL:
curl --parallel --parallel-max 5 --remote-name-all \
https://example.com/a.zip https://example.com/b.zip https://example.com/c.zip
顺便知道一下:默认的并行上限其实是 50,这比大多数服务器,甚至你自己的网络,真正愿意承受的并发连接数都要高得多。我通常会显式指定一个保守值——一般 4 到 8——而不是直接用默认值。
方式 3:用 xargs 和 Bash 循环,对 URL 列表进行并发下载
如果有一大串 URL 放在文本文件里,我通常会用 xargs:
cat urls.txt | xargs -n1 -P 8 curl -O -L
或者,如果我想更精细地控制每个任务的行为,就用后台运行的 bash 循环:
while read -r url; do
curl -O -L "$url" &
done < urls.txt
wait
最后的 wait 很重要——没有它,脚本会在后台下载还没结束时就提前退出。
什么时候该改用 wget 或 aria2
我直接说实话:cURL 并不是任何场景下都最合适的工具。如果你要镜像整个网站目录树,wget -r 可以直接递归抓取,这不是 cURL 本身为这种用途设计的。如果你需要多源、分段下载,把单个超大文件的吞吐量拉满,aria2c 确实会更快。
| 工具 | 最适合的场景 |
|---|---|
| cURL | 精准控制、脚本化、单文件或小批量下载、API 交互 |
| wget | 递归/镜像式网站下载、简单的静态文件批量获取 |
| aria2 | 多源/分段下载、尽可能提高大文件吞吐量 |
cURL 的强项一直是精准和可组合性——管道、脚本、协议灵活性——而不是暴力抓取。
如何使用 cURL 下载受保护文件:认证方式详解
很多 cURL 教程讲到 -u user:pass 就结束了。那是早期互联网时代的写法。到了 2026 年,我实际下载的文件大多来自 REST API、基于会话的后台页面和 CI 系统——而这些场景各自需要不同的凭证方式。
Basic Auth 基础认证
curl -u username:password -O https://legacy-server.example.com/file.zip
适用于老式 FTP 服务器或简单的 HTTP 接口。但要知道,密码会出现在 shell 历史记录和进程列表里,除非你非常小心——这不是我会拿来处理敏感内容的方式。
Bearer / OAuth Token 认证
这个其实在很多指南里被严重低估了,但我现在最常用的反而是它:
curl -H "Authorization: Bearer $GITHUB_TOKEN" \
-LO https://api.github.com/repos/curl/curl/releases/assets/12345
这就是一个拉取私有 GitHub Release 资源的真实模式——把 token 和资源 ID 换成你自己的就行。现在大多数 REST API 和受 OAuth2 保护的资源基本都是这么交互的。
基于 Cookie 的会话认证
对于登录后会创建会话的 Web 应用,把登录后的 cookie 保存下来,再在下载时复用:
curl -c cookies.txt -d "user=me&pass=secret" https://example.com/login
curl -b cookies.txt -O https://example.com/protected/file.zip
适用于脚本和 CI 环境的 .netrc 文件
这是我最喜欢的无人值守场景方案。新建一个 ~/.netrc 文件(Windows 上是 _netrc):
machine example.com
login myusername
password mypassword
然后把权限收紧到 chmod 600 ~/.netrc,再这样调用:
curl --netrc -LO https://example.com/protected-file.zip
它的好处是凭证不会出现在 shell 历史或脚本源码里——这在 CI/CD 里尤其重要,因为脚本经常会被完整记录日志。
| 认证方式 | 参数/选项 | 最适合的场景 |
|---|---|---|
| Basic auth | -u user:pass | 旧式 FTP、简单 HTTP |
| Bearer token | -H "Authorization: Bearer <token>" | REST API、OAuth2 |
| Cookie 认证 | -b cookies.txt(保存时再加 -c) | 基于会话的 Web 应用 |
.netrc 文件 | --netrc 或 --netrc-file | CI/CD、脚本化环境 |

排查常见的 cURL 下载失败问题
这一部分是我刚入门时最希望看到的,因为几乎没人会认真讲。"为什么我的 curl 下载不工作" 是一个非常常见、也很让人崩溃的搜索词——但一旦知道原因,通常一行命令就能修好。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
curl: (60) SSL certificate problem | 自签名或过期证书 | --cacert <file> 或 -k(仅开发环境) |
403 Forbidden / 空文件 | 服务器屏蔽默认 curl 用户代理 | -A "Mozilla/5.0..." 或 -H "User-Agent: ..." |
使用 -C - 时下载从 0 开始 | 服务器不支持 Range | 用 curl -I <url> 检查 Accept-Ranges: bytes |
| 保存了 0 字节文件 | 没有跟随重定向 | 加上 -L 参数 |
curl: (28) Operation timed out | 服务器太慢或网络不稳定 | --connect-timeout 10 --max-time 300 + --retry 3 |
| 保存成了 HTML 页面而不是文件 | 页面需要 JavaScript 渲染 | curl 无法执行 JS——看下面的部分 |
SSL 证书错误:是什么意思,怎么修
错误 60 表示 curl 无法验证服务器的 SSL 证书——通常是因为证书是自签名的、已经过期,或者由 curl 不信任的 CA 签发。如果你控制这台服务器,可以用 --cacert /path/to/ca.pem 指定正确的 CA bundle。-k(--insecure)会直接跳过校验,这在本地开发环境里可以,但只要涉及生产环境或真实用户数据,我都不建议这么干。
403 Forbidden 和空下载
很多服务器会拦截那些默认 User-Agent 显示为 curl/8.21.0 的请求,觉得它们像机器人或爬虫。通常的解决办法,就是伪装成浏览器:
curl -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" -LO https://example.com/file.zip
如果我想在正式下载前先看看服务器到底返回了什么,通常会先跑:curl -o /dev/null -s -w "%{http_code}\n" <url>。
超时、重试与不稳定连接
如果我够勇敢做纹身,这就是我想纹在胳膊上的命令。
我在生产脚本里真正常用的下载命令,会把可靠性相关的参数一起叠上:
curl -L -C - --retry 5 --retry-delay 3 --connect-timeout 10 --max-time 600 --fail -O <url>
这包括:跟随重定向、断点续传、重试 5 次并每次间隔 3 秒、10 秒连接超时、最长 10 分钟,以及遇到错误 HTTP 状态时直接失败——基本上就是我踩过坑之后,知道必须加上的全部东西。
在真实自动化里使用 cURL:CI/CD、管道与脚本安全
把 cURL 输出直接管道给其他工具
cURL 并不一定要把文件先落盘——直接管道给另一个命令,是它最被低估的能力之一:
curl -sL https://example.com/archive.tar.gz | tar xz
curl -s https://api.example.com/data | jq '.results'
下载后直接解压,或者下载后直接解析,一行就能完成。我在临时抓数据时经常这么用。
在 GitHub Actions 和 CI/CD 中使用 cURL
下面是一个最小化的 GitHub Actions 步骤示例:下载二进制文件,带重试逻辑,而且一出错就明确失败:
- name: Download binary
run: |
curl -L --fail --retry 3 --retry-delay 5 \
-o app-binary "https://example.com/releases/app-binary"
把 token 存成 CI secret,再通过环境变量引用——千万不要直接硬编码进脚本。并且要使用 --fail(如果你需要看错误正文排查问题,也可以用 --fail-with-body),这样下载坏了才会真的让构建失败,而不是“假装成功”地把垃圾文件带过去。
关于 curl | sh 的安全问题
这个话题几乎在我看过的每个开发者论坛里都会出现,而且确实值得担心:把 curl 直接管道给 sh,意味着你在执行远程代码,而且完全依赖于服务器没有被入侵、传输没有被篡改。真正的风险就在这里——不是杞人忧天,而是实打实的供应链安全问题。
更稳妥的做法是先下载,再检查脚本,如果提供了校验和或 GPG 签名,也先验证通过,然后再执行:
curl -sL https://example.com/install.sh -o install.sh
cat install.sh # 先真的看一眼
sha256sum install.sh # 如果有公开校验和,就拿来比对
bash install.sh
像 rustup 和 Homebrew 这类知名安装器仍然会使用 curl | sh 模式,在这些特定场景下通常也被接受,因为维护者和分发渠道都很成熟。不过,如果多花十秒看一下脚本能让我避免踩坑,我宁愿多花这十秒。
什么时候 cURL 不够用:JS 渲染页面、反爬网站与结构化数据
这里有一种失败模式,特别容易让人抓狂,而且通常还不是你的错:你对一个看起来很正常的页面执行 curl -O,结果拿到的不是预期内容,而是一个空壳 HTML、Cloudflare 挑战页,或者一堆看起来像乱码的东西。cURL 其实已经尽职尽责了——它按设计只负责抓取原始 HTTP 响应——只是它不会执行 JavaScript、不会解 CAPTCHA,也不会绕过反爬指纹系统。这不是 cURL 的 bug,而是它本来就不负责做这些事。
为什么 cURL 在现代网页上会失效
现代单页应用往往只返回一个几乎空白的 HTML 骨架,真正内容是在页面加载后由 JavaScript 在客户端渲染出来的——而这一步 cURL 根本不会执行。除此之外,Cloudflare 和 Akamai 这类系统会主动向“看起来不像真实浏览器”的请求返回挑战页,而来自同一 IP 的重复 curl 请求,很快就可能被限流或者被识别为机器人流量。
下一步:面向开发者的 AI 抓取 API
如果让我估算,cURL 大概适合 80% 的文件和数据下载场景——静态资源、API 响应、任何以普通 HTTP 资源形式提供的内容。剩下那 20%,也就是 JavaScript 很重或者有机器人防护的页面,才是开发者最容易花几个小时和请求头、User-Agent 死磕,最后还是不得不换一层方案的地方。
这正是我团队打造 Thunderbit 想解决的问题之一,也包括我们很多人熟悉的 Chrome 扩展。在开发者侧,Thunderbit 的 Open API 提供 POST /distill,可以从 URL 返回干净、适合 LLM 使用的 Markdown,页面渲染由服务端处理;还提供 POST /extract,在你需要结构化字段数据而不是可读文本时,它会返回和你定义 schema 匹配的 JSON。我们还有一个 MCP 服务器,Claude 或 Cursor 里的代理可以在任务中直接调用 thunderbit_distill 和 thunderbit_extract;另外也有 CLI(npx @thunderbit/thunderbit-cli distill <url>),在终端里的使用体验很像 cURL。比如你可以把 JSON 输出再交给 jq:thunderbit distill <url> --format json | jq -r '.data.markdown';如果用 --format markdown 输出,则可以直接送给文本工具或写入文件。
横向对比一下,差别会很明显。对一个由 JavaScript 渲染的产品页发起 curl 请求,可能只会返回一个几乎空白的 <div id="root"></div>。而同样的 thunderbit distill 命令,会把已经渲染好的页面内容直接转成干净的 Markdown。Distill 每个 URL 消耗 1 credit,Extract 每个 URL 消耗 20 credits。当前各接口的具体限制也不同:Batch Distill 单次任务最多支持 100 个 URL,而 Batch Extract 使用同一份 schema 时最多支持 50 个 URL。在规划生产队列容量前,记得先查看最新 API 文档。
如果你对“网页抓取”这个概念还不太熟,可以先看看我们自己的入门说明:什么是网页抓取以及如何在 2026 年完成它,以及 无需编程的网页抓取指南,它会从非开发者视角解释同一个问题,方便团队里不碰终端的人也能理解。如果你想更全面地比较这个领域的工具,我们还整理了 最佳 AI 网页爬虫 的参考清单。
快速参考:cURL 下载速查表
| 任务 | 命令 |
|---|---|
| 基础下载 | curl -LO <url> |
| 自定义文件名 | curl -L -o myfile.zip <url> |
| 续传下载 | curl -C - -LO <url> |
| 静默运行但显示错误 | curl -sSL -O <url> |
| 并行下载 | curl --parallel --parallel-max 5 -O <url1> -O <url2> |
| Bearer token 认证 | curl -H "Authorization: Bearer <token>" -LO <url> |
| 日常脚本化下载 | curl -LO --retry 5 --retry-delay 3 --max-time 600 --fail <url> |
| 管道输出到提取工具 | curl -sL <url> | tar xz |
结论与核心要点
用 curl 下载文件,起步非常简单——curl -O 基本就能搞定大半——但真正的功夫在于那些底层细节:什么时候要加 -L,什么时候该续传而不是重来,哪种认证方式才适合你的工作流,以及当 403 或空白 HTML 壳出现时该怎么办。我自己也是在这些模式上踩过一个又一个坑,通常都是在弄明白它为什么重要之后,才后知后觉地学会了。
对我来说,cURL 依然是最顺手、最可靠的文件下载和可脚本化 HTTP 处理工具——它速度快、到处都有,而且和 shell 管道配合得天衣无缝。但当你碰到 JavaScript 渲染页面或反爬屏障时,那就不是“再加几个参数”能解决的 cURL 问题了;这说明你需要另一层工具,而这正是像 Thunderbit 这样的 API 可以接手的地方,而且不会逼你离开终端。
建议你把这份速查表收藏起来,下次碰到不稳定下载时先试试重试和续传命令;如果你遇到那种 cURL 只能返回乱码的墙,也知道下一步该怎么走——如果你想看看升级到 Thunderbit 的实际成本,可以去看它的定价页;如果你更喜欢看视频而不是读文字,我们的 YouTube 频道 里也有详细演示。
关于使用 cURL 下载文件的常见问题
如何用 cURL 下载文件并保存成指定名称?
使用 -o 后面跟上你想要的文件名:curl -L -o yourname.ext <url>。记得加上 -L,避免重定向把下载搞乱。
如何恢复失败的 cURL 下载?
运行 curl -C - -LO <url>。前提是服务器支持范围请求——先用 curl -I <url> 检查响应里是否有 Accept-Ranges: bytes。
cURL 能下载需要登录的文件吗?
可以,主要有四种方式:Basic Auth(-u user:pass)、Bearer Token(-H "Authorization: Bearer <token>")、基于 Cookie 的会话(-b cookies.txt)、或者在脚本环境中使用 .netrc 文件。上面的认证章节里有完整说明,也会告诉你每种方式适合什么场景。
cURL 和 wget 下载文件有什么区别?
cURL 支持更多协议,通常更适合脚本化、管道处理,以及精确控制的单文件或小批量下载。wget 则更擅长递归抓取和镜像整个站点目录,因此更适合批量静态网站下载。
为什么 cURL 下载下来的是 HTML 页面,而不是实际文件?
常见原因有两个:要么你忘了加 -L,导致服务器把你重定向到了别处;要么页面的真实内容需要 JavaScript 才会渲染,而 curl 根本不会执行 JS。第二种情况就不是加几个 curl 参数能解决的了,你需要的是能渲染页面的工具。


