简而言之:在 Python 中使用 cURL 有三种合理的方法:调用curl二进制文件subprocess调用cURL二进制文件,通过PycURL绑定到libcurl,或者完全跳过cURL而直接使用Requests库。要真正掌握Python中cURL的使用,就必须了解这三种方法。本指南为您提供了这三种方法的可运行示例、cURL参数到Python的转换表,以及决策矩阵,助您一次选对工具。
简介
如果你编写 Python 并调用 HTTP API,你可能遇到过这样的情况:API 文档或浏览器的“复制为 cURL”按钮给你提供了一行以 curl -X POST ...,而现在你需要将其嵌入 Python 脚本中。弄清楚如何在 Python 中使用 cURL 看似简单,但其实存在多种正确解法。
cURL 本身是一款用于通过网络协议(HTTP、HTTPS、FTP)传输数据的命令行工具。在 Python 中,你可以将 curl 二进制文件作为外部进程调用,通过 PycURL 驱动其底层 C 库(libcurl),或者使用 Requests 库作为 Python 风格的替代方案。每种方案在速度、控制力和可维护性方面都有各自的取舍。
本指南面向已掌握 Python 且希望将任何 cURL 代码片段转换为可运行代码的后端、数据和爬虫工程师。我们将通过可运行的示例讲解这三种方法,将常见的 cURL 参数映射到 Python 对应语法,构建一个小型爬虫管道,并以故障排除作为结尾,助您顺利交付代码,而非与工具周旋。
开发者为何要在 Python 中运行 cURL
大多数团队面临“在 Python 中运行 cURL”这一问题的原因大同小异:有人给他们提供了一个 cURL 命令。API 文档通常以 curl 调用形式呈现,浏览器开发者工具以相同格式导出网络请求,而 Postman 和 Insomnia 也允许您将任何请求复制为 cURL 格式。该代码片段是权威来源,您希望 Python 代码能完全复现其行为。
在 Python 中运行 cURL 让你可以先调试确切的请求,然后过渡到更符合 Python 惯例的写法。通过 subprocess 的复制粘贴运行可验证端点是否有效。在此基础上,你可以放心使用 PycURL 或 Requests 重写调用,确信自己没有改变网络传输格式。这种短反馈循环才是开发者想要了解如何在 Python 中使用 cURL 的真正原因,而不是单独使用 curl。
如何在 Python 中使用 cURL:三种方法一览
当开发者询问如何在 Python 中使用 cURL 时,通常指的是以下三种具体方法之一。在编写任何代码之前,请选择与任务相匹配的方法。这三种选项不可互换,选择错误通常意味着日后需要重写调用。
|
方法 |
最适合 |
是否需要安装 |
优缺点 |
|---|---|---|---|
|
|
原样重现 curl 代码片段、一次性调试、CI 脚本 |
无(curl 已加入 PATH) |
每次请求都要付出启动进程的代价,且需对输出进行字符串解析 |
|
PycURL(libcurl 绑定) |
高吞吐量爬虫、精细的 TLS/超时控制、FTP 及其他协议 |
|
底层 API,某些系统上存在构建问题 |
|
Requests 库 |
几乎涵盖其他所有功能:REST API、JSON、Cookie、会话 |
|
未与 Python 捆绑;抽象掉了某些 curl 特有的选项 |
请将 subprocess 作为你的转换步骤,将 PycURL 视为你的强力工具,并将 Requests 作为你的默认选择。大多数生产环境中的 Python 代码最终都会使用 Requests;另外两个则用于处理特殊情况。
方法 1:subprocess:直接运行 curl 命令
`subprocess` subprocess 模块是 Python 标准库的一部分,因此无需安装任何新组件即可调用 curl。这是对“在 Python 中使用 cURL”最字面意义的解读,当你需要完全复现 API 文档中出现的命令时,它确实非常有用。
有一条不可妥协的安全规则:请将命令作为参数列表传递,而非单一的 shell 字符串。当请求的任何部分源自用户输入时,字符串会引发 shell 注入风险。而参数列表的形式则完全绕过了 shell。Python 的 `subprocess` 文档对此安全模型有详细说明。
将 curl 代码片段放入 subprocess.run
取一条 curl 单行命令,将其拆分为令牌,并将列表传递给 subprocess.run。设置 capture_output=True ,使 stdout 和 stderr 返回给你,并 text=True 并设置以便获取字符串而非字节流。
import subprocess
cmd = [
"curl", "-s",
"-H", "Accept: application/json",
"https://httpbin.org/get?lang=python",
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
print(result.stdout)该 -s 参数会关闭 curl 的进度条,因此 stdout 输出仅包含响应正文。 timeout=15 参数会在 subprocess.TimeoutExpired ,这正是你在脚本中不希望永远阻塞时所需要的。请保留此形式进行翻译:一旦它能正常工作,你就拥有了一个经过验证的基准,可以移植到 PycURL 或 Requests。
捕获输出并检查返回码
默认情况下, subprocess.run 在 curl 退出时返回非零值的情况下不会抛出异常。你必须自行检查返回码,或者显式选择抛出异常。
import json, subprocess
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
if result.returncode != 0:
raise RuntimeError(f"curl failed ({result.returncode}): {result.stderr.strip()}")
try:
payload = json.loads(result.stdout)
except json.JSONDecodeError as exc:
raise RuntimeError(f"non-JSON response: {result.stdout[:200]}") from exc
print(payload["args"])你也可以调用 result.check_returncode(),该函数会在 CalledProcessError 。无论采用哪种方式,请记录 result.stderr 。curl 会将诊断信息写入该日志,通常仅凭该消息即可区分 DNS 故障、TLS 错误或 4xx 响应。
方法 2:PycURL:原生 libcurl 绑定
PycURL 是 libcurl 的 Python 接口,而 libcurl 正是驱动 curl 二进制文件本身的 C 库。它提供了用于超时、SSL 配置、头部、Cookie、重定向以及 HTTP 以外协议的底层选项。当吞吐量或精细控制至关重要时,PycURL 是最佳选择。
安装命令为 pip install pycurl。该 Python 包仅为轻量级封装,因此系统还需安装 libcurl 及 OpenSSL 开发头文件。在 Debian/Ubuntu 系统上,安装命令为 apt install libcurl4-openssl-dev libssl-dev;在 macOS 上, brew install curl openssl。我们将在故障排除部分讨论 OpenSSL 的链接时错误,因为这是新安装失败的最常见原因。
使用 PycURL 进行 GET、POST 和 JSON 操作
PycURL 遵循 libcurl 的模式:创建句柄、设置选项、执行操作,然后关闭。写入操作会发送到一个类似文件的缓冲区,通常是一个 BytesIO.
import json, pycurl
from io import BytesIO
from urllib.parse import urlencode
# GET
buf = BytesIO()
c = pycurl.Curl()
c.setopt(c.URL, "https://httpbin.org/get?lang=python")
c.setopt(c.WRITEDATA, buf)
c.perform()
status = c.getinfo(pycurl.RESPONSE_CODE)
c.close()
print(status, buf.getvalue().decode("utf-8"))对于表单编码的 POST 请求,请将 POSTFIELDS 为 urlencoded 格式的主体。对于 JSON,将字典序列化并设置正确的 Content-Type.
# POST form
form = urlencode({"a": 1, "b": "two"})
buf = BytesIO()
c = pycurl.Curl()
c.setopt(c.URL, "https://httpbin.org/post")
c.setopt(c.POSTFIELDS, form)
c.setopt(c.WRITEDATA, buf)
c.perform(); c.close()
# POST JSON
body = json.dumps({"hello": "world"})
buf = BytesIO()
c = pycurl.Curl()
c.setopt(c.URL, "https://httpbin.org/post")
c.setopt(c.HTTPHEADER, ["Content-Type: application/json"])
c.setopt(c.POSTFIELDS, body)
c.setopt(c.WRITEDATA, buf)
c.perform(); c.close()设置 HTTPHEADER 是控制 Content-Type、Accept、Authorization 以及其他请求头的方式。接下来我们将在此模式基础上进行扩展。
自定义头部、Cookie 和重定向
HTTPHEADER 接受一个 "Name: value" 字符串列表。Cookie 可以作为 Cookie 头部发送,或者你可以让 libcurl 通过 COOKIEFILE 和 COOKIEJAR.
c.setopt(c.HTTPHEADER, [
"Accept: application/json",
"Authorization: Bearer eyJhbGciOi...",
"Cookie: session=abc123; theme=dark",
])
# Or use a cookie jar to persist Set-Cookie across requests
c.setopt(c.COOKIEFILE, "cookies.txt")
c.setopt(c.COOKIEJAR, "cookies.txt")对于重定向,启用 FOLLOWLOCATION (相当于 curl -L) 并使用 MAXREDIRS ,这样行为异常的服务器就无法让你陷入无限循环。
c.setopt(c.FOLLOWLOCATION, True)
c.setopt(c.MAXREDIRS, 5)若仅需响应头(即 curl -I 风格的请求),请将 NOBODY 为 True,并通过 HEADERFUNCTION。该回调会针对每行响应头运行一次,这在抓取 Last-Modified 或速率限制元数据。如需更深入的用法,请参阅我们关于 cURL 中 HTTP 响应头部的详细解析。
使用 PycURL 进行流式文件下载
WRITEDATA 支持任何类文件对象,因此下载只需一行代码:以二进制写入模式打开文件,并将 libcurl 指向该文件。无论有效载荷大小如何,内存占用始终保持恒定。
import os, pycurl
url = "https://example.com/large.iso"
out = "large.iso"
mode = "ab" if os.path.exists(out) else "wb"
offset = os.path.getsize(out) if mode == "ab" else 0
with open(out, mode) as fp:
c = pycurl.Curl()
c.setopt(c.URL, url)
c.setopt(c.WRITEDATA, fp)
c.setopt(c.FOLLOWLOCATION, True)
if offset:
c.setopt(c.RANGE, f"{offset}-") # resume from byte offset
c.perform(); c.close()Range: bytes={offset}- 标头会告知服务器仅发送缺失的尾部数据,这正是 curl -C - 恢复中断下载的方式。服务器必须支持范围请求(大多数 CDN 都支持)。
方法 3:Requests:符合 Python 风格的 curl 替代方案
对于大多数日常工作,Requests 就是答案。它未随 Python 捆绑(需通过 pip install requests),但其 API 与 curl 的语义完美对应:查询参数、头部、Cookie、JSON 正文和超时时间均可作为关键字参数。
import requests
# GET with query params
r = requests.get(
"https://httpbin.org/get",
params={"lang": "python"},
headers={"Accept": "application/json"},
timeout=15,
)
r.raise_for_status()
print(r.json())
# POST JSON
r = requests.post(
"https://httpbin.org/post",
json={"hello": "world"},
headers={"Authorization": "Bearer ..."},
timeout=15,
)raise_for_status() 是你的好帮手:它会将任何 4xx/5xx 状态码转换为 requests.HTTPError,因此网络故障(requests.ConnectionError, requests.Timeout) 与 HTTP 错误的区别在代码中始终清晰可见。
当调用是 Python 程序中的众多请求之一、需要会话状态,或由团队维护代码时,请默认选择 Requests。若已测得实际性能瓶颈或需要仅限 libcurl 的选项,请选择 PycURL。如需更深入的 Python HTTP 客户端对比,请参阅我们对此主题的汇总文章。
将常见的 curl 标志转换为 Python
一旦掌握了这三种 Python cURL 实现方式的使用方法,移植新命令的最快途径就是逐个转换其参数。下表涵盖了 95% 的 API 文档和开发工具导出中常见的参数。
|
curl 参数 |
功能说明 |
PycURL |
Requests 关键字 |
|---|---|---|---|
|
|
设置 HTTP 方法 |
|
|
|
|
添加请求头 |
|
|
|
|
URL编码的请求体 |
|
|
|
|
来自文件的原始正文 |
|
|
|
|
原始 JSON 正文 |
|
|
|
|
多部分表单 |
|
|
|
|
将数据转换为查询字符串 |
|
|
|
|
跟随重定向 |
|
|
|
|
基本认证 |
|
|
|
|
发送 Cookie |
|
|
|
|
将正文写入文件 |
|
|
|
|
总超时 |
|
|
如果您不想手动翻译,可以使用公开的 curl 到 Python 转换器(ScrapingBee 的转换器便是其中一个知名工具),这些工具能接收 curl 命令,并输出包含已填充头部、参数和数据的 Requests 调用。利用它们进行初始化,然后对照本表对输出结果进行合理性检查。
整合应用:基于 curl 的爬取管道
学习如何在 Python 中使用 cURL 的常见原因之一是网页抓取。以下是我们制作小型抓取器原型时采用的模式:使用 PycURL 快速获取 HTML,通过 BeautifulSoup 进行解析,然后将结果保存下来。整个过程不到 40 行代码,涵盖了状态检查、编码处理以及 CSV 导出。
import csv, json, pycurl
from io import BytesIO
from bs4 import BeautifulSoup
URL = "https://books.toscrape.com/catalogue/page-1.html"
def fetch(url: str) -> str:
buf = BytesIO()
c = pycurl.Curl()
c.setopt(c.URL, url)
c.setopt(c.WRITEDATA, buf)
c.setopt(c.FOLLOWLOCATION, True)
c.setopt(c.TIMEOUT, 20)
c.setopt(c.HTTPHEADER, ["User-Agent: scraping-pipeline/1.0"])
c.perform()
code = c.getinfo(pycurl.RESPONSE_CODE)
c.close()
if code != 200:
raise RuntimeError(f"unexpected status {code} for {url}")
return buf.getvalue().decode("utf-8")
def parse(html: str) -> list[dict]:
soup = BeautifulSoup(html, "html.parser")
rows = []
for card in soup.select("article.product_pod"):
rows.append({
"title": card.h3.a["title"],
"price": card.select_one(".price_color").text.strip(),
"stock": card.select_one(".availability").text.strip(),
})
return rows
def main():
rows = parse(fetch(URL))
with open("books.csv", "w", newline="") as fp:
writer = csv.DictWriter(fp, fieldnames=rows[0].keys())
writer.writeheader(); writer.writerows(rows)
print(json.dumps(rows[:2], indent=2))
if __name__ == "__main__":
main()若需原样重现 curl 命令,可将 PycURL 替换为 subprocess ,或者在需要会话状态时改用 Requests。一旦开始访问带有速率限制和反机器人防御机制的真实网站,你就会希望在该抓取步骤前添加一层代理。
Python 配合 cURL 使用的常见错误排查
学习如何在 Python 中使用 cURL 时遇到的多数困扰,都源于少数几种反复出现的故障。以下是简要列表及相应的解决方法。
- PycURL
ImportError: pycurl: libcurl link-time ssl backend (...) is different from compile-time ssl backend (...)。您的 PycURL 轮子是基于与系统中 libcurl 不同的 SSL 后端构建的。在撰写本文时,macOS 上最可靠的解决方法是通过 Homebrew 安装 OpenSSL,并基于它从源代码重新安装 PycURL;在 Windows 上,安装 OpenSSL 1.1.x 二进制文件并设置PYCURL_SSL_LIBRARY,LIB,并INCLUDE之前pip install pycurl --no-binary :all:。请重新查阅您所在平台的 PycURL 安装说明,因为具体环境变量在不同版本中可能有所变化。 UnicodeEncodeErrorPycURL 对 POST 请求正文的处理。PycURL 期望POSTFIELDS。请显式编码非 ASCII 数据:c.setopt(c.POSTFIELDS, body.encode("utf-8")).subprocess.TimeoutExpired。始终传递timeout=给subprocess.run。将该异常视为网络故障,而非程序错误。- TLS 错误与自签名证书。PycURL:
c.setopt(c.SSL_VERIFYPEER, 0); Requests:verify=False。仅在受信任的环境中执行此操作,并在生产环境中优先固定 CA 证书包。 - 区分 HTTP 错误与传输层错误。使用 Requests 时,请
requests.HTTPError与requests.ConnectionError。使用 subprocess 时,非零returncode表示传输层故障;除非传入--fail.
根据具体用例选择合适的方法
一旦掌握了这三种工具,具体选择就要视情况而定。
- 调试 API 或复现错误报告时,请优先使用
subprocess。直接运行 curl 命令能排除“我的 Python 客户端有问题”这一可能性。 - 一次性脚本和 CI 任务。选择 Requests。它代码可读、文档完善,且便于后续工程师维护。
- 长时间运行的爬虫、高请求量或非 HTTP 协议。PycURL。您将获得 libcurl 的连接复用、精细的 TLS 控制以及更低的每次请求开销。
- Cookie 和登录流程。Requests
Session是阻力最小的路径;若已基于 libcurl 开发,PycURL 的 Cookie 管理库则是替代方案。
掌握 Python 中 cURL 的使用,与其说是选择优胜者,不如说是根据请求需求匹配合适的工具。
关键要点
- 将 curl 与 Python 结合的三个实际选项:
subprocess(二进制文件重现)、PycURL(libcurl 绑定)和 Requests。请审慎选择,切勿仅凭习惯。 - 将
subprocess作为转换层:先通过原生命令验证网络传输格式,再有把握地移植到 PycURL 或 Requests。 - 系统地将 curl 参数映射到 Python:
-H转换为 headers,-d转化为数据或 json,-G转化为参数,-F转化为文件,-L转化为跟随重定向,-o变成流式写入。 - 务必设置超时,务必检查状态码,并将 HTTP 错误视为与传输错误截然不同的类别。
- 对于支持断点续传的大规模抓取或下载任务,PycURL 物有所值。对于其他所有情况,建议默认使用 Requests。
常见问题
“在 Python 中使用 cURL”是什么意思:我是运行 curl 二进制文件还是 Python 库?
两者皆是。“运行 curl”通常指从 Python 调用系统 curl 可执行文件,这要求 subprocess,这要求 PATH 环境变量中包含 curl。“使用 Python 库”则意味着导入 PycURL(libcurl 的封装库)或 Requests(纯 Python HTTP 客户端),且完全不接触二进制文件。对服务器而言,网络请求看起来完全相同;仅调用代码有所不同。
在实际工作负载中,PycURL 真的比 Requests 库更快,还是仅在理论上如此?
在合成基准测试中,PycURL 通常更快,因为它将 HTTP 处理工作卸载给了 C 语言编写的 libcurl。但在实际工作负载中,这种差距会缩小:网络延迟、TLS 握手和解析通常是主要瓶颈。在数千个并发连接的情况下,PycURL 依然占优,因为此时每次请求的开销会累积放大。对于大多数脚本而言,两者之间的差异微乎其微,难以察觉。
将 API 文档中冗长的 curl 命令转换为可运行的 Python 代码,最快捷的方法是什么?
将 curl 命令粘贴到 curl-to-Python 转换器中(网上有多个免费工具),选择 Requests 输出格式,然后对照本指南中的参数映射表检查生成的代码。转换器会自动处理头部、参数和数据。发布前,您仍需添加 timeout, raise_for_status(),并在发布前添加适当的异常处理。
如何从 Python 发送多部分文件上传(相当于 curl -F)?
在 Requests 中,使用 files 关键字: requests.post(url, files={"upload": open("data.csv", "rb")})。若需对文件名和内容类型进行额外控制,请传入一个元组: files={"upload": ("data.csv", fp, "text/csv")}。在 PycURL 中,设置 HTTPPOST 选项,并传入一个描述每个表单字段的元组列表,其中包含一个 pycurl.FORM_FILE 磁盘路径的条目。
为什么我的 PycURL 安装会因 OpenSSL 或 libcurl 的链接时错误而失败,该如何解决?
该错误意味着该 wheel 是基于与您系统中的 libcurl 不匹配的 SSL 后端构建的。解决方法是根据您本地的 libcurl 从源代码重新安装 PycURL: pip install --no-binary :all: pycurl 在安装 libcurl 和 OpenSSL 开发头文件后(brew install curl openssl 在 macOS 上;Linux 上的等效开发包)。在 Windows 上,请在重新安装前通过环境变量设置 OpenSSL 路径。
结论
掌握 Python 中 cURL 的使用方法实际上包含三项技能:通过 subprocess,通过 PycURL 驱动 libcurl,以及编写符合惯例的 Requests 代码。每种方式都有其作用。 subprocess 是您将 API 文档转化为经过验证的代码的桥梁。PycURL 是您用于高吞吐量爬虫和下载的性能利器。Requests 则是其他所有场景的默认选择,因为随着项目规模的扩大,它仍能保持代码的可读性。
上文的“标志到 Python”对照表、流式下载方案以及故障排除清单已涵盖了大部分棘手问题。剩下的问题在于目标服务器对非浏览器流量的处理方式:无论你的 Python 代码多么干净,速率限制、验证码、JavaScript 挑战以及 IP 封禁终将困扰你。
这正是 WebScrapingAPI 专注解决的层级。如果您花在应对反机器人防御上的时间比编写爬虫代码还要多,我们的 Scraper API 可以接收 curl 风格的请求并返回原始 HTML,同时在后台处理代理轮换、验证码破解和重试,这样您就可以完全保留原有的子进程、PycURL 或 Requests 代码,只需替换端点即可。 选择适合您任务的 curl 方法,让网络层的问题由他人来解决。




