简介:curl2py 是一份面向 Python 开发者与运维人员的轻量工具脚本,用于将日常调试中常见的 curl 命令快速转换为可直接运行的 Python 脚本,省去手工改写请求头、参数与数据体的重复劳动,适合需要频繁对接 HTTP 接口、做接口调试或爬虫原型验证的初中级使用者。压缩包共 3 个文件,以 1 个 py 主脚本为核心,辅以 1 个 md 说明文档和 1 个 license 授权文件,整体仅 14KB,结构精简、开箱即用。脚本支持通过 -r 或 -raw 参数以原始格式输出,默认则采用 json pretty print 美化结果,便于阅读与二次修改。目前已有 1169 人学习下载,说明其在接口调试场景中具备一定实用价值。读者可借此获得一份可直接运行的转换脚本、清晰的使用说明与授权信息,快速把命令行请求迁移到 Python 代码中,减少手动重写成本,并在此基础上按需扩展功能。
1. 从终端到脚本:为什么我劝你早点把 curl 命令转成 Python
你有没有过这种经历:在浏览器开发者工具里复制了一条 curl 命令,想把它塞进 Python 脚本里跑,结果对着-H、--data-raw、-b这些参数一个个手敲requests代码,敲到一半发现引号转义错了,或者 cookie 忘了带,请求直接 403。更别提那种带--compressed、--insecure、多层嵌套 JSON 的 curl,手动翻译简直是体力活加玄学调试。
curl2py就是冲着这个场景来的:它把一条完整的 curl 命令解析成结构化的请求参数,再生成一段可以直接运行的 Python 代码,通常基于requests库。你不需要再对着终端输出猜哪个 header 对应哪个字段,也不用担心-d和--data-binary的区别被忽略。适合谁用?做接口调试的后端、写爬虫的数据采集、搞自动化测试的 QA,以及任何经常从浏览器“Copy as cURL”然后往 Python 里搬的人。它解决的不是什么高深算法,就是每天重复十几次的机械劳动。
2. curl2py 的解析逻辑:从命令行参数到 Python 字典
2.1 一条 curl 命令到底携带了哪些信息
先拆解一条典型 curl 命令的结构。比如:
curl 'https://api.example.com/v1/items?page=2' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer token123' \ -H 'Content-Type: application/json' \ --data-raw '{"name":"test","tags":["a","b"]}' \ --compressed \ --insecure这条命令里,curl2py需要提取的维度包括:请求方法(有--data-raw通常推断为 POST)、URL 和查询参数、请求头字典、请求体原始字符串、是否压缩、是否跳过证书验证。这些信息在 curl 里是平铺的参数,在 Python 里要映射成requests.request()的 keyword arguments。
常见做法是先用shlex.split()把命令拆成 token 列表,再逐 token 扫描。shlex的好处是能正确处理单引号、双引号和转义字符,比直接split()靠谱得多。拆完之后,遇到-H或--header就取下一个 token 作为 header 行,按第一个冒号切分成 key 和 value;遇到-d、--data、--data-raw、--data-binary就取下一个 token 作为 body;遇到-X或--request就显式指定方法。
这里有个容易翻车的点:curl 允许-H 'Key:Value'和-H 'Key: Value'两种写法,冒号后的空格可有可无。解析时要用partition(':')而不是split(':'),否则 value 里带冒号的 header(比如Authorization: Bearer abc:def)会被切碎。另外--data-raw和--data在 curl 里行为略有差异,前者不处理@文件引用,后者会。生成 Python 代码时统一按原始字符串处理更安全。
2.2 用 Python 实现一个最小可用的解析器
下面这段代码是一个简化版的核心解析逻辑,能覆盖大部分日常 curl 命令:
import shlex def parse_curl(curl_cmd): # 去掉开头的 curl 和可能的换行符 tokens = shlex.split(curl_cmd.replace('\\\n', ' ')) if tokens[0] == 'curl': tokens = tokens[1:] method = None url = None headers = {} data = None verify = True i = 0 while i < len(tokens): tok = tokens[i] if tok in ('-X', '--request'): method = tokens[i + 1] i += 2 elif tok in ('-H', '--header'): key, _, value = tokens[i + 1].partition(':') headers[key.strip()] = value.strip() i += 2 elif tok in ('-d', '--data', '--data-raw', '--data-binary'): data = tokens[i + 1] i += 2 elif tok in ('--compressed',): headers.setdefault('Accept-Encoding', 'gzip, deflate') i += 1 elif tok in ('-k', '--insecure'): verify = False i += 1 elif tok.startswith('http'): url = tok i += 1 else: i += 1 if method is None: method = 'POST' if data else 'GET' return { 'method': method, 'url': url, 'headers': headers, 'data': data, 'verify': verify, }逻辑说明:shlex.split负责把带引号的参数正确切分;循环里用i手动控制索引,因为每个选项后面跟的参数数量不同。partition(':')保证 header 值里的冒号不被误切。--compressed在 curl 里是让 curl 自己解压,在 Python 里对应的是设置Accept-Encoding头,让服务端返回压缩内容,requests会自动解压。-k对应verify=False。
参数怎么改:如果你要支持-b或--cookie,加一个分支把 cookie 字符串塞进 headers 的Cookie字段。如果要支持--form上传文件,那就要生成files参数而不是data,复杂度会上升,建议单独处理。
2.3 生成可运行的 Python 代码模板
解析出字典之后,生成代码就是字符串拼接的事。但拼接也有讲究,直接repr()整个字典虽然能跑,但可读性差,而且data如果是 JSON 字符串,最好用json=参数而不是data=,这样requests会自动设置Content-Type。
import json def generate_python(parsed): lines = ['import requests', ''] lines.append(f"url = {parsed['url']!r}") lines.append(f"headers = {parsed['headers']!r}") if parsed['data']: try: json.loads(parsed['data']) lines.append(f"payload = {parsed['data']!r}") lines.append("json_data = json.loads(payload)") use_json = True except json.JSONDecodeError: lines.append(f"data = {parsed['data']!r}") use_json = False else: use_json = False lines.append('') lines.append(f"resp = requests.request(") lines.append(f" {parsed['method']!r}, url,") lines.append(f" headers=headers,") if parsed['data']: if use_json: lines.append(f" json=json_data,") else: lines.append(f" data=data,") if not parsed['verify']: lines.append(f" verify=False,") lines.append(')') lines.append('print(resp.status_code)') lines.append('print(resp.text)') return '\n'.join(lines)这段生成逻辑里,!r是repr()的格式化写法,能保证字符串里的引号被正确转义。判断 body 是否为合法 JSON 是个实用技巧:如果是 JSON,用json=参数让requests自动序列化并设置Content-Type: application/json;如果不是,用data=原样发送。verify=False只在原始 curl 带-k时才加,避免默认关闭证书验证带来安全风险。
把这两段拼起来,输入一条 curl 命令,输出就是一段能直接python run.py的脚本。实际使用时,curl2py这类工具还会处理--url显式指定、多行命令合并、-G把 data 转 query 等边缘情况,但核心骨架就是上面这些。
3. 避坑与排查:curl2py 转换时最容易翻车的五个地方
3.1 现象:生成的代码跑起来 403,但 curl 命令在终端正常
原因通常出在 header 丢失或大小写被改写。curl 对 header 名大小写不敏感,但某些服务端框架会严格校验。解析时如果用headers[key.lower()] = value统一小写,就可能触发服务端的校验逻辑。另外--compressed如果没有转成Accept-Encoding,服务端可能返回未压缩内容,某些接口会因此拒绝。
解决:保留原始 header 名的大小写,不要做lower()归一化。--compressed显式补上Accept-Encoding: gzip, deflate。如果 curl 里带了-A或--user-agent,确保它被正确解析到 headers 里,而不是被忽略。
3.2 现象:POST 请求体发送后服务端返回 400,提示 JSON 解析失败
原因多半是data和json参数用混了。curl 的--data-raw发送的是原始字符串,如果这个字符串是 JSON,用requests的data=参数发送时,Content-Type默认是application/x-www-form-urlencoded,服务端按表单解析就会失败。
解决:生成代码时先尝试json.loads(body),成功则用json=参数,失败再用data=。同时检查原始 curl 里是否有-H 'Content-Type: application/json',如果有,即使 body 不是合法 JSON,也要保留这个 header。
3.3 现象:带 cookie 的请求转换后丢失登录态
原因:curl 的-b参数和-H 'Cookie: ...'是两种写法,很多解析器只处理了后者。-b后面跟的字符串可能是name=value; name2=value2格式,也可能是一个 cookie 文件路径。
解决:遇到-b或--cookie时,判断参数是否以@开头或是否为存在的文件路径。如果是字符串,直接塞进 headers 的Cookie字段;如果是文件,读取文件内容再塞入。生成 Python 代码时,也可以提示用户用requests.Session()来管理 cookie。
3.4 现象:URL 里的查询参数在生成的代码里变成了乱码或丢失
原因:curl 命令里的 URL 可能包含&、?等 shell 特殊字符,如果没有用引号包住,shlex.split之前就已经被 shell 解释了。另外--data-urlencode参数会把 data 转成 query string,解析器如果只认-d就会漏掉。
解决:确保输入的 curl 命令是从浏览器“Copy as cURL”完整复制的,URL 部分带引号。解析时遇到--data-urlencode要单独处理,把它拼到 URL 的 query 部分或者作为data发送,取决于是否同时有-G参数。
3.5 现象:生成的代码里verify=False导致安全告警
原因:原始 curl 带了-k或--insecure,生成代码时直接照搬了verify=False。这在测试环境没问题,但如果脚本被带到生产环境,就会跳过证书验证。
解决:生成代码时在verify=False旁边加一行注释# 仅在测试环境使用,或者用环境变量控制。更稳妥的做法是生成代码时默认verify=True,只在注释里说明原始 curl 带了-k,让使用者自己决定是否关闭。
4. 进阶用法:把 curl2py 嵌进你的调试工作流
4.1 批量转换与命令行封装
单条转换用在线工具或脚本都行,但如果你一天要处理几十条 curl,最好把它做成命令行工具。下面是一个简单的 CLI 封装:
import sys from curl2py import parse_curl, generate_python if __name__ == '__main__': curl_cmd = sys.stdin.read().strip() parsed = parse_curl(curl_cmd) print(generate_python(parsed))用法:pbpaste | python curl2py_cli.py > request.py,在 macOS 上直接读剪贴板。Windows 下可以用powershell Get-Clipboard替代pbpaste。这样从浏览器复制 curl 到生成 Python 文件,全程不超过三秒。
参数说明:sys.stdin.read()会一直读到 EOF,所以管道输入没问题。如果你要处理多行 curl,确保换行符被replace('\\\n', ' ')正确处理。生成的文件建议用black格式化一下,可读性更好。
4.2 与抓包工具和 API 文档的配合
常见做法是把 Charles、Fiddler 或浏览器开发者工具里导出的 curl 直接丢给 curl2py。但要注意,这些工具导出的 curl 有时会带--compressed和-H 'Accept-Encoding: gzip, deflate, br',其中br是 Brotli 压缩,requests默认不支持,需要额外安装brotli库。如果生成代码后报ContentDecodingError,把br从Accept-Encoding里去掉,或者pip install brotli。
另一个场景是 API 文档里给的 curl 示例。很多文档的 curl 示例是简化版,缺少Content-Type或Authorization,直接转换后跑不通。我的习惯是先用 curl 在终端跑一遍文档示例,确认能通,再转 Python。这样能把文档错误和转换错误分开排查。
4.3 验证生成的代码是否等价于原始 curl
转换完不要直接信,用-v对比一下。终端跑curl -v看请求头和请求体,Python 脚本里加resp.request.headers和resp.request.body打印,逐项对比。重点看四个地方:请求方法、URL 路径和 query、Content-Type、Authorization。这四个对了,基本就等价了。
我自己的习惯是,每次用 curl2py 生成代码后,先跑一遍print(resp.request.headers),确认 header 没有多也没有少。有一次就是漏了Referer头,服务端做了防盗链校验,排查了半小时才想起来对比原始 curl。从那以后我每次转换完都强制走一遍 header diff,再也不敢跳过。
希望帮到你。
本文还有配套的精品资源,点击获取