上周同事甩了个文件给我,没有上下文,只说“快帮我看看这个文件里有没有我们需要的那批用户数据”。我下意识双击,编辑器弹出来的是满屏乱码,拉到文件尾部才看到四个字节 PAR1,那一刻我知道事情没那么简单了。这是很多做数据的人都会撞上的场景:手头拿到一个 parquet 文件,没有 Spark 集群、没有 IDE、甚至没有数仓权限,却要在几分钟之内搞清楚文件里到底是什么。
这篇文章就围绕“快速在线查看 parquet 文件”展开,给出我在实际工作中验证过的一套组合方案:从浏览器端零后端的 DuckDB-Wasm 查看器,到 parquet-tools、DuckDB CLI 这些离线轻量手段,再穿插一些选型时容易踩的坑。不管你是数据工程师、数据分析师,还是偶尔需要接触数仓文件的业务开发,应该都能在下面找到契合自己那幕场景的做法。
parquet 这两个词在近两年的大数据生态里出现频率实在太高了,凡是和数据湖、Spark、Flink 沾边的项目,最终落盘的基本都是这种格式。但高频不等于高熟悉度,很多人对 parquet 的认知停留在“它是一种文件格式”,真要打开看的时候就手足无措。所以我先花点篇幅把“为什么 parquet 会打不开”这件事讲透,后面给工具方案时你会更容易理解每个工具的价值。
1. 为什么一个数据文件会“打不开”
1.1 列式存储:parquet 和 CSV/Excel 的本质差别
Apache Parquet 是一种列式存储格式,这是回答“为什么打不开”的起点。我们日常用的 CSV、Excel,本质上是以“行”为逻辑单位组织数据:第一行是表头,下面每一行是一条记录。行式存储在写入和整行读取时很自然,但在分析场景里会遇到麻烦。比如一张 1 亿行的表,你只是想统计某一列的平均值,行式存储也必须把每一行都读进来,把不相关的其他几十列数据也顺带扫一遍,IO 开销非常大。
parquet 反其道而行,它把同一个文件在物理存储上按照列来切分。一列的连续值放在一个区域,另一列放在另一个区域,查询时只需要读取你关心的那些列块即可。再加上每一列可以单独配置压缩算法,snappy、zstd、gzip、lz4 都行,同一份数据往往比 CSV 小很多。这就是它的核心价值:面向分析查询的高性能列式存储。
也正是因为这种设计,parquet 文件注定不是给人“双击直接看”的。你用一个文本编辑器打开它,看到的是压缩后的字节流,不是可读的字符;Excel 更不用说,它的内核模型和列式存储完全不兼容,硬要导入只会报“文件格式与扩展名不匹配”。所以当你拿到一个 parquet 文件时,第一步要建立的认知就是:这不是一个能被通用文本软件直接消费的东西,它需要专门的解析器。
1.2 二进制魔数与页脚元数据:它在文件里藏了什么
稍微深入一点看文件结构,你会更理解为什么需要专门工具。parquet 文件以 4 个字节的魔数“PAR1”开头,文件结尾还有另一组“PAR1”。中间的部分由 row group(行组)组成,每个 row group 内部包含多个 column chunk(列块),每个 column chunk 又切分成若干 page(页)。page 是压缩和编码的基本单元,字典编码、RLE 编码、增量编码这些都作用在这一层。
更关键的信息藏在文件末尾的 footer metadata 里。它记录了 schema、每个 column chunk 在文件中的偏移量、每列的 min/max 统计值、null 数量、压缩编码方式等元数据。解析工具读文件时,会先拿到 footer 元数据,再根据偏移量按需读取具体的列块。这也是 parquet 能实现谓词下推的原因:查询引擎可以借助列块统计信息直接跳过不满足条件的 row group。
这个结构决定了你要查看它,至少要有一个“理解 footer 元数据 + 按列读取页 + 解压解码”的解析器。记事本没有,Excel 没有,浏览器默认也没有。所以当你需要快速在线查看 parquet 文件时,真正要做的事情就是找一个能执行这些解析动作的工具或库,而不是改文件后缀名或者换个文本编辑器。
1.3 什么场景最容易碰到 parquet 文件
我日常收到 parquet 文件最常见的来源大概有四类。第一类是 Spark、Flink 作业写出的结果文件,尤其在用 Hudi、Iceberg 或 Delta Lake 这类数据湖表格式时,底层落盘的基本都是 parquet。第二类是数据仓库或查询引擎的导出产物,Athena、BigQuery、ClickHouse 都支持把查询结果导出成 parquet,方便做进一步数据交换。第三类是数仓之间的冷数据迁移,很多公司的离线链路直接拿 parquet 作为中间交换格式。第四类是各种数据采集服务,埋点日志落库、广告投放回传、用户行为分析系统的原始明细,也大量使用 parquet。
出现“快速在线查看”这个需求的场景往往很典型:你不在数仓环境里、只是临时拿到一个文件,要确认字段名、行数、某个枚举值的分布,或者要判断这份数据能不能直接用于某个报表。再往前一步,如果你想把这个 parquet 文件的某个字段提出来转成 JSON/CSV 给业务同学,那还涉及查询与导出的问题。后面几个章节给的方案,覆盖的就是这类需求。
2. 在浏览器里搭一个零后端的 parquet 查看器:DuckDB-Wasm 方案
2.1 为什么选定 DuckDB-Wasm,而不是网上那些“在线转换器”
“在线查看 parquet 文件”,很多人第一反应是搜一个网页上传文件让它预览。我试过几个这类站点,体验很不稳定:小文件或许能出来几十行预览,一旦文件上到几十 MB,页面直接转圈,有的干脆上传完就 502。更让我介意的是,数据到底去了哪台服务器、中间有没有被存到别的地方,我完全不知道。遇到生产数据,这是绝对不能接受的。
所以我更推荐 DuckDB-Wasm。DuckDB 本身是一个嵌入式分析型 SQL 引擎,设计思路类似 SQLite,但强项在于分析查询。它被编译成 WebAssembly 之后,可以完整跑在浏览器里。配合少量前端代码,就能实现一个“上传 parquet 文件到浏览器本地内存、用 SQL 查询、数据不出本机”的查看器。相比第三方在线站点,它的优势很明确。
数据零上传是最大的卖点。文件在浏览器本地被解析,不经过任何中间服务器,这比那些要求你把文件传到云端再解析的站点安全太多。能力上限也高得多,不只可以翻前几行,还能做聚合、过滤、多文件联合查询。加上它完全可自部署,一个 HTML 加几个静态资源就能跑在任意静态托管平台上,团队内部随时能用。
有人可能会问:既然 DuckDB 是分析引擎,那我只想看几行是不是杀鸡用牛刀?实际用下来不是。因为 DuckDB 针对 parquet 做了列裁剪和谓词下推优化,即使文件有几个 GB,只要你不强制查全部列,它读取的物理数据量依然可控,体验比那些把整个文件塞进内存再转成表格的网站好太多。
2.2 一个可以直接用的 HTML 页面
我这里提供一个最小可用的页面,保存成 index.html 就能跑。它做的事情是:让用户选择一个本地 parquet 文件,把文件内容注册成虚拟文件 data.parquet,然后用 SQL 查询默认输出前 100 行,并把结果渲染到页面上。代码基于 @duckdb/duckdb-wasm。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Parquet 在线查看器</title> </head> <body> <h1>Parquet 快速查看</h1> <input type="file" id="pqFile" accept=".parquet" /> <br /><br /> <textarea id="sql" style="width:90%;height:60px;">SELECT * FROM "data.parquet" LIMIT 100</textarea> <br /><br /> <pre id="output" style="background:#f6f8fa; padding:12px; overflow:auto;"></pre> <script type="module"> import * as duckdb from 'https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.29.0/dist/duckdb-esm.js'; import duckdb_wasm from 'https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.29.0/dist/duckdb-mvp.wasm?url'; import duckdb_wasm_eh from 'https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.29.0/dist/duckdb-eh.wasm?url'; const worker = await duckdb.createWorker(); const logger = new duckdb.ConsoleLogger(); const db = new duckdb.AsyncDuckDB(logger, worker); await db.instantiate(duckdb_wasm, duckdb_wasm_eh); document.getElementById('pqFile').addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file) return; const buf = new Uint8Array(await file.arrayBuffer()); await db.registerFileBuffer('data.parquet', buf); const conn = await db.connect(); const showSql = document.getElementById('sql').value || 'SELECT * FROM "data.parquet" LIMIT 100'; try { const result = await conn.query(showSql); document.getElementById('output').innerText = result.toString(); } catch (err) { document.getElementById('output').innerText = 'SQL 执行失败:' + err.message; } finally { await conn.close(); } }); </script> </body> </html>注意一点:前端依赖的 CDN 版本号可能会更新,如果你在测试时发现 wasm 文件 404,去 npm 页面看一眼最新版本号替换掉即可。核心逻辑不会变,就是注册文件、建连接、跑 SQL、渲染结果这几步。
如果是给团队用,我会在页面上再加几个快捷按钮:一个执行DESCRIBE SELECT * FROM "data.parquet"看字段结构,一个执行常见的分组聚合,再放一个清空结果按钮。这样即使不会写 SQL 的同学也能先看结构再翻数据。
2.3 如何跑起来与日常使用技巧
本地运行最简单的方式是保存上述文件后,在目录下执行python3 -m http.server 8000,然后浏览器打开http://localhost:8000。不要直接用 file:// 协议打开页面,因为 ES module 和 wasm 加载在部分浏览器下会碰到跨域限制。
部署给团队远程使用时,可以直接推到 GitHub Pages,或者放到任意支持静态网站的云对象存储上,OSS 静态网站托管、S3 加 CloudFront 都行。这样任何一个拿到链接的人都能在浏览器里查看 parquet,服务器上不需要装任何东西。
使用中有几个小技巧非常实用。第一,永远别在默认 SQL 里写不带 LIMIT 的SELECT *,尤其当文件列很多、行很多时,浏览器结果渲染会成为瓶颈。建议先执行 DESCRIBE 看字段,再按需 SELECT 所需的列并限制行数。第二,如果怀疑某个字段是嵌套结构,struct、list、map 这类,用 DuckDB 的展开函数比瞎翻整行好懂,比如SELECT unnest(arr_col) FROM "data.parquet" LIMIT 10。第三,一次注册多个文件之后,这些文件可以互相 join,这在对比两份数据的差异时非常好用。
3. 第三方在线服务怎么选:现成入口、判断标准与风险
3.1 官方 Shell 和数据平台的预览入口
如果你不想自己写页面,还有一个官方入口值得记住:DuckDB 官方提供的 Web Shell,本质就是把 DuckDB-Wasm 变成了一个网页版 SQL 控制台,你可以直接执行 SELECT 查询并看到结果,适合临时验证某个想法。
用它来查看 parquet 文件通常有两种路径。一种是把 parquet 文件放到一个支持 CORS 的公开对象存储地址,然后在 Shell 里执行SELECT * FROM 'https://your-bucket.oss.aliyuncs.com/path/to/data.parquet' LIMIT 100;,浏览器会直接读取远程文件。另一种是先跑通官方文档里的示例数据集,再换成自己的文件。
这里有个现实约束是 CORS。大多数云厂商的对象存储默认不允许任意网页跨域读取,你要么给 bucket 配置 CORS 规则,要么在本地搭一个同源的反向代理。如果只是想团队小范围内用,我建议直接用上一节那个自建 HTML 页面,反而更直接,毕竟官方 Shell 的功能是通用的,不会为你的文件做权限管理。
除了 DuckDB 官方 Shell,一些数据平台也有内置的文件预览能力。比如数据目录类工具在注册了表以后,可以通过底层链接直接展示 schema 和 sample data;MinIO 控制台对 CSV、JSON 可以直接预览,对 parquet 的支持则取决于版本与部署配置。这些属于企业内已有的平台能力,如果你公司恰好搭了这类系统,直接在平台上打开要看的表是最省事的。但只是临时收到一个孤立文件时,这类平台往往帮不上忙,因为文件还没有注册成平台上的表。
3.2 判断一个在线站可不可用的四条标准
万一你还是要用搜索引擎找到的某个在线预览站点,我给你一套判断标准,避免数据打水漂。
第一条,代码是否开源、能否自部署。一个“上传文件到服务器解析完再吐回给你”的站点,如果连开源仓库都没有,我基本不会在生产数据上用它。反过来,基于 DuckDB-Wasm 的纯前端实现如果源码公开,至少数据不出浏览器这一点是可验证的。
第二条,是否明确说明数据只在你本地处理。站点页面上如果有类似“Your file never leaves your browser”的声明,可信度会高一些;如果只字不提存储与隐私策略,默认按最坏情况处理。
第三条,文件大小限制是否透明。很多在线解析工具是跑在函数计算或者临时容器里的,上传几十 MB 的压缩 parquet 很可能直接触发超时或内存上限。如果一个站点没有写清楚限制,而你又要传一个大文件,建议先剪一个小分区文件再试。
第四条,导出与分享机制是否符合你预期。有的站点看起来能预览,但导出 CSV 是限量的、分享链接是公开的,甚至会在你上传的文件基础上生成一个公开 URL 给任何人访问。对涉及敏感字段的数据,这类功能反而是风险来源。
3.3 上传生产数据前你会踩的坑
我想强调一个很多人在“快速查看”心态下容易忽略的问题:文件里常常不止你想看的那一列。我见过有人把包含手机号、身份证、设备 ID 的用户明细 parquet 直接拖进一个不知名在线转换站,就为了看一眼 CPU 统计列。结果文件确实解析出来了,但那两列敏感数据也已经在别人服务器上过了一圈,之后想撤回没有任何办法。
所以我的个人习惯是:在上传到任何第三方站点之前,先本地用 pyarrow 或 DuckDB 做一次脱敏裁剪,只保留必要的列,把需要验证的字段导出成一个小的样本 parquet 或 CSV,再拿这个样本去做在线体验。如果只是验证文件结构,一个 100 行的样本完全够用。这件事花不了两分钟,却能避免大麻烦。
4. 在线不可用时,本地三把刀也能快速查看
4.1 parquet-tools:Parquet 官方瑞士军刀
如果你的环境里没有浏览器 JavaScript 的条件,或者文件涉及敏感数据不能网传,本地工具是最稳妥的选择。首推 Apache Parquet 官方出品的 parquet-tools,它是 Java 写的命令行工具,当年还没有 DuckDB 的时候,我全靠它救急。
parquet-tools 的典型用法分成几个子命令。schema 命令只读 footer 元数据,输出字段名和嵌套类型,这个操作非常快,哪怕文件几个 GB 也几乎瞬时完成;head 命令读取前 N 行并以 CSV 或 JSON 形式打印,适合先肉眼看几条数据;cat 命令会把全部行都输出,适合把结果重定向到文件做进一步处理;meta 命令能看到每个 row group 的压缩方式、行数、列统计信息。我实际排查文件损坏或压缩问题时,meta 是高频命令。
# 查看 schema java -jar parquet-tools.jar schema user_actions.parquet # 查看前 10 行 java -jar parquet-tools.jar head -n 10 user_actions.parquet # 查看 footer 统计信息 java -jar parquet-tools.jar meta user_actions.parquet注意两个前提:机器上要有 Java 8 及以上运行环境,jar 包从 Maven Central 或 GitHub Releases 获取。老版本 jar 不支持 zstd 解压的情况确实存在,我会在下一节展开。
4.2 DuckDB CLI:一条 SQL 搞定远程与本地文件
如果说 parquet-tools 是看文件的,DuckDB CLI 就是查文件的,而且它的能力完全可以替代一套轻量数仓查询。安装方式很多,官方安装脚本、Homebrew、直接下载二进制都行。装好后进入交互式 shell,或者用-c参数直接跑单条 SQL。
本地文件是最常见的用法:
SELECT * FROM 'data.parquet' LIMIT 100; DESCRIBE SELECT * FROM 'data.parquet'; SELECT event_date, count(*) FROM 'data.parquet' GROUP BY 1 ORDER BY 1;远程对象存储也可以直接读。这个能力在数据文件还在 S3 或 OSS 上时非常好用,不用先下载到本地:
INSTALL httpfs; LOAD httpfs; SET s3_region='ap-northeast-1'; SET s3_access_key_id='xxx'; SET s3_secret_access_key='xxx'; SELECT * FROM 's3://bucket/path/part-*.parquet' LIMIT 100;这个用法对我来说是线上数据快速排查的神器。不用把几十 GB 分区数据下载到本地,DuckDB 的 httpfs 扩展会流式读取远程 parquet 文件,配合列裁剪和谓词下推,一个查询经常只拉回目标分区中的部分列块,速度很快。OSS 的配置项略有差异,但思路一致,搜一下文档里 httpfs 相关章节即可。
多文件也方便。如果 Spark 作业输出了一大堆 part-xxxxx-*.parquet,你可以直接在 FROM 子句里写'path/to/part-*.parquet',DuckDB 会当成一张大表处理。这个能力在 parquet-tools 里没有,在 pyarrow 里要写不少代码,一条 SQL 下来真是降维打击。
4.3 Python 一行式与场景对照表
如果你本身就在 Python 环境里,用 pyarrow 或 duckdb 包是最自然的。pyarrow 可以做到:
pip install pyarrow python -c "import pyarrow.parquet as pq; print(pq.read_schema('data.parquet')); print(pq.read_table('data.parquet').to_pandas().head())"如果已经装了 duckdb 包,更简洁:
python -c "import duckdb; print(duckdb.sql(\"select * from 'data.parquet' limit 10\").fetchdf())"四种工具对照起来,我平时按下面的思路选:
| 工具 | 安装成本 | 上手难度 | 适合场景 |
|---|---|---|---|
| parquet-tools | 中(需 Java + jar) | 低 | 只看 schema、前几行、文件元数据 |
| DuckDB CLI | 低(单二进制) | 低 | SQL 查询、多文件、远程对象存储 |
| pyarrow + pandas | 中(Python 环境) | 中 | 想直接在 Python 里继续处理数据 |
| DuckDB-Wasm 页面 | 低(静态页面) | 低 | 给不懂命令行的同事在线查看 |
工具没有绝对优劣,重要的是场景匹配。在线优先的场合我用 wasm;需要查远程对象存储我用 DuckDB CLI;只是想知道这个文件是什么结构,parquet-tools 的 schema 命令最快。
5. 选型决策与实际排错经验
5.1 按场景选方案
我做过一张内部小抄,按不同目标直接选方案:
- 只想确认文件里有没有某个字段、大概几行:用 parquet-tools 的 schema 或 meta,或者 DuckDB 的
DESCRIBE SELECT * FROM 'data.parquet'。 - 要给业务同学演示一段数据:本地 DuckDB CLI 查
LIMIT 10,或者转成 CSV 给对方;如果对方不会命令行,把 DuckDB-Wasm 页面部署到内网让他在浏览器里操作。 - 文件在 S3/OSS 上,不想下载:DuckDB CLI 加 httpfs 扩展,一条 SQL 直接查远程文件。
- 只是临时看一次,不想装任何环境:DuckDB 官方 Web Shell 或自建 wasm 页面。
- 文件列很多、要看嵌套结构:用 DuckDB 查询并展开字段,比如
SELECT unnest(struct_col) FROM ...。
这套方案组合下来,几乎覆盖了我遇到过的所有“快速查看 parquet 文件”的场景。
5.2 四个高频问题与排查链路
问题一:parquet 文件打开报 magic 错误或 “Could not open file”。先用file data.parquet看真实类型。我实际中遇到过同事把 CSV 改名成 .parquet 发给我,文本打开能看到表头;还遇到过下载中断导致文件尾部 PAR1 缺失的情况,DuckDB 报错信息恰好指向 footer 解析处,用 parquet-tools 的 meta 命令也能验证。
问题二:打开的是_metadata或_common_metadata,里面只有 schema 和列统计,没有数据。这是 Hudi、Iceberg 或 Spark 写入时自动生成的元数据文件,不是具体的数据文件。要看数据应该找part-*.parquet格式命名的文件,或者直接用目录级通配符查询。
问题三:分区目录。Spark 自动分区写出的路径常是dt=2024-01-01/part-0001.snappy.parquet。如果你只读单个文件,结果没问题;但你要聚合全部分区,应该写SELECT ... FROM 'path/to/root/**/*.parquet'或'path/to/root/*/*.parquet',DuckDB 支持这类 glob 通配。
问题四:解压失败。常见原因是用旧版 Java parquet-tools 看 zstd 压缩文件,会报Unsupported compression codec: ZSTD。解决办法是升级到新版 jar,或者直接换 DuckDB 读,DuckDB 原生支持主流压缩算法。如果在浏览器里用 wasm,也要确保 wasm 版本足够新,否则同样可能遇到不认识的压缩格式。
还有一个值得单独提的:如果 parquet 文件是加密的,普通工具根本读不出 schema。需要提供密钥,并且用支持对应加密方案的库,比如 pyarrow 带 encryption 配置的那种。这种文件通常不是随机发给你的,优先弄清密钥协商方式,比硬找工具更重要。
5.3 我的默认工作流
踩过不少坑之后,我现在的默认工作流很固定。第一步,拿到 parquet 文件先复制一份样本到本地,原始文件不动,样本可以是第一个 partition 文件,也可以是已经裁剪过的迷你版本。第二步,本地先跑parquet-tools schema确认字段和嵌套结构,再跑 head 看三条数据,确认没有拿到张冠李戴的文件。第三步,如果只是定性判断,到此结束;如果需要更深层查询,进入 DuckDB CLI 写 SQL 做过滤聚合。第四步,如果同事也要看且不熟悉命令行,我就把那个 DuckDB-Wasm 页面部署到团队内网静态站点,让他们自己上传文件查。
个人体会是,在线查看 parquet 这个需求里,最危险的其实不是工具慢,而是把“看数据”和“查数据”混为一谈。你只是想确认文件内容,就不要随手把整个生产文件喂给第三方网站。先用本地工具裁剪出一个安全样本,再考虑在线协作。只要手边有 parquet-tools 和 DuckDB 这两样,不管文件来自数仓导出、数据湖文件还是同事的临时产物,基本都能在一分钟内打开看清楚。