前几天突然被几个同事同时找,起因是数仓那边丢过来一批Parquet文件,每个几百MB,而下游的数据消费端只认JSONL。本来以为就是读一下再写一下,结果真在Python里转换时发现坑不少:小文件用pandas一条命令能出,文件一上来内存先爆了;类型处理不好,日期时间变成乱码;还有人直接把JSON数组写进去,下游根本读不了。回头把方案重新捋了一遍,觉得这块很值得专门写一篇。它的核心不只是“Parquet转JSONL”这一步,而是围绕格式转换时的数据形态、内存控制、类型映射和上下游兼容性。如果你想找一套能直接照着改的代码和思路,这篇应该能让你少走很多弯路。
这套转换在真实业务里出现频率非常高。数据仓库喜欢用Parquet做存储和交换格式,因为它列式存储、压缩率高、适合分析型查询。但很多业务系统、日志采集管道、全文检索平台,反而更喜欢处理JSONL,因为JSONL每一行就是一个完整对象,没有外层包裹,天然支持按行读取、按行回放、按行消费。两边格式目标不同,中间就需要一座桥。我见过不少人直接拿别人博客里的三行代码去转,结果要么损失精度,要么文件一大多进程被杀,要么嵌套字段被拆得稀碎。这篇文章会把方案选型、类型映射、内存控制和大文件处理完整拆开讲,每一步都能直接复现。
1. 为什么非要把Parquet倒腾成JSONL
1.1 先搞清楚两种格式到底差在哪
Parquet最核心的特点是列式存储。它把同一列的数据连续存放,这样在做聚合、过滤、扫描某些字段时,可以只读取相关列,而且同列数据类型一致,压缩算法能拿到更好的压缩比。举个例子,一张表如果有100个字段,但分析任务只用到其中3个,列式存储完全可以跳过另外97个字段的IO。这也是数仓和分析引擎偏爱Parquet的根本原因。
JSONL又叫JSON Lines,本质是把多个JSON对象用换行符拼接在一起,每一行单独解析。它最大的优点是“按行可用”:不需要一次性加载整个文件,读一行就能处理一行,遇到坏行也只会影响当前行。对于日志采集、消息推送、数据导入这类逐条消费的场景来说,这种格式极其友好。缺点是它没有schema约束,也没有压缩和统计信息,每个JSON对象都有重复的键名,文件体积通常比Parquet大不少,如果字段很多,写出来的文件会非常冗余。
所以两种格式解决的是不同问题:Parquet偏向存储和分析,JSONL偏向传输和逐行消费。把Parquet转成JSONL,不是简单换一层皮,而是要读懂Parquet的类型体系,再变成JSON能够表达的字符串、数字、布尔、对象和数组。真正容易翻车的往往就是类型转换,而不是文件读写本身。
1.2 哪些场景最容易碰到这种转换
我盘点了一下手头接到过的需求,出现频率最高的是下面三类场景。
第一类是数仓数据需要被外部系统消费。数据团队习惯把明细表导出成Parquet放在对象存储上,但下游BI报表、算法平台或者第三方服务可能不认Parquet,只接受JSONL、CSV这类更通用的行式格式。第二类是数据管道中转。有些公司在做ETL时会把中间过程统一转换成JSONL下发到消息队列或者日志平台,方便后续用Flink、Spark Streaming这类引擎逐条消费,减少对源表的重复扫描。第三类是离线数据需要临时分析。非技术同事拿到Parquet文件后打不开,转成JSONL后可以直接用文本编辑器查看,或者导入到某些只支持JSON导入的数据库里。
不管哪种场景,转换的正确性都比速度重要。这里有几个容易踩的坑:Parquet里的时间戳通常是微秒精度,JSON标准里没有原生时间类型,需要约定好序列化成ISO 8601字符串;Parquet里的Binary字段更麻烦,直接json.dumps会报错,必须先做编码转换;还有作为关联键的大整数,比如雪花ID,如果目标解析端用JavaScript来读,超过2^53就会丢失精度,需要主动处理成字符串。这些都不是靠“pandas一行转换”能完美解决的,必须在动手转换前考虑清楚。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 工具选型:为什么推荐PyArrow打底而不是无脑Pandas
2.1 PyArrow是读取Parquet真正的主角
很多人提到用Python处理Parquet,第一反应是pandas.read_parquet,但pandas本身没有能力直接解析Parquet,它底层依赖的还是PyArrow。pandas.read_parquet内部调用的是pyarrow.parquet模块的读取能力,再把结果从Arrow Table转换成DataFrame。这意味着绕了一圈,最后还是回到PyArrow上。
直接使用PyArrow有几个明显优势。第一是内存更可控,PyArrow可以按行组或按固定行数迭代读取RecordBatch,不需要把整个Parquet文件一次性灌入内存;Pandas则倾向于把全部数据加载成DataFrame,文件一大就容易内存暴涨。第二是类型保留更完整,PyArrow能感知到Parquet元数据里的时间戳单位、时区信息、Decimal精度、嵌套的Struct和List类型,而Pandas在转换过程中可能会把一些列变成object类型,细节丢了一半。第三是解析开销更低,PyArrow读取后得到的是底层C++数据结构,只有当你显式调用to_pylist或者to_pandas时,数据才会被转成Python对象。
所以在转换场景里,我更推荐用PyArrow直接读取,再配合标准库json来逐行输出。PyArrow负责“读懂Parquet”,json负责“写出JSONL”,两者职责清晰,不需要引入Pandas这个重型中间层。
2.2 极简的Pandas什么时候够用
也不是说Pandas完全不能用。如果文件不大,比如只有几百MB,行数在百万以内,字段类型基本都是整数、小数、字符串和布尔,那么用Pandas一行读、一行写是非常高效的做法,代码量最少,维护成本也低。
python复制import pandas as pd
df = pd.read_parquet("input.parquet")
df.to_json("output.jsonl", orient="records", lines=True, force_ascii=False)
这里的三个参数是重点。orient="records"表示每个DataFrame行输出成一个JSON对象;lines=True表示不是输出成一个大型JSON数组,而是每行一个JSON对象;force_ascii=False表示保留中文等非ASCII字符,而不是转成\uXXXX序列。这三个参数缺一个,或者参数值不对,写出来的东西就不是标准JSONL。
但Pandas版本
