十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

30GB CSV压缩至3GB:列式存储与Parquet转换实战指南

30GB CSV压缩至3GB:列式存储与Parquet转换实战指南 接手了一个别人扔过来的大活儿一堆CSV格式的数据单文件最大 30GB算上其余拆分的小文件整个数据集快把磁盘撑爆了。业务方的要求很简单数据不能丢、后续要能继续做统计和检索最好查询还别太慢。我当时的第一反应和大多数人一样直接扔给数据库但看着那个“导入需 6 小时起”的预估时间又瞅了瞅磁盘剩余空间我决定先拿格式开刀。折腾了一下午最终成果就是标题里写的30GB 的 CSV 换了个格式变 3GB而且查询效率翻了不止一个量级。这篇文章就围绕这次“格式换装”的完整过程展开重点聊聊为什么CSV这么占地方、换什么格式最划算、具体怎么换、以及换完以后踩过的坑和后续的数据处理心得。内容偏实操涉及的技术点不深但每一步都会解释背后的原因保证你照着做就能复现且能明白自己在做什么。1. 为什么 30GB 的 CSV 文件这么“虚胖”CSV 本质上是纯文本文件它的“虚胖”不是偶然而是格式本身的特性决定的。理解这点你才知道换格式换的到底是什么。1.1 文本存储的“天然浪费”在继续之前我们还是先看一组真实的数据对比。CSV 中每一行是一条记录字段之间用逗号分隔每一行用换行符结尾。问题是计算机存储数字的时候它并不知道“这是数字”。它只看到一串字符比如123456789这就是 9 个字符占 9 个字节。但在内存里一个 32 位的整数只需要 4 个字节就能放下123456789。同样一个数值用文本表示比用二进制表示多出一倍以上的空间。再叠加浮点数就更夸张了。比如3.141592653589793这个小数文本形式占了 17 个字符但如果是用二进制双精度浮点数存储无论这个数小数点后有多少位总共只占 8 个字节。文本形式下位数越多浪费越大。这还只是数字。字符串类型的数据更离谱。CSV 里如果一个字段是固定的枚举值比如“Success”“Failed”“Pending”它在这 30GB 里可能重复出现几千万次每次都要把完整的字符串写一遍。实际上这几个单词的每一个字母都被完整地记录了上千万次文件体积当然下不来。1.2 缺少“元数据”和类型信息CSV 的另一个问题是它不携带任何数据类型信息。所有字段在磁盘上都是字符只有在读取时由程序解析。这也意味着任何工具读取 CSV都必须逐字符扫描、按分隔符切割、再做类型判断。30GB 的文本扫描一遍光解析时间就够喝一壶的。更重要的是CSV 没有任何索引结构。想查某个 ID 对应的记录只能从头到尾全表扫描。没有压缩、没有谓词下推、没有列裁剪一切全凭硬扛。磁盘占用高只是一个表象没有元数据、没有类型感知才是它“笨重”的根源。1.3 与 CSV 对标的“精瘦”格式列式存储与向量化那么换什么格式能把 30GB 压到 3GB答案是列式存储格式业界最常用的是 Parquet其次是 ORC。它的核心思路是不按“行”存储而是按“列”存储数据。举个例子现在有一个订单表有订单号、用户ID、金额、状态四个字段。行式存储CSV在磁盘上的排列顺序是订单1, 用户A, 100, 成功 订单2, 用户B, 200, 失败列式存储则是先把所有订单号放一起再把所有用户ID放一起以此类推。这样做有一个天生的好处同一列的数据类型是一致的而数据分布往往是相似的。比如状态列只有“成功”和“失败”两种值字典编码之后一整个列块可能就被压缩成了极小的字节流。数字列经过压缩编码、增量编码也能达到非常夸张的压缩比。Parquet 这类格式不是靠“把文件打包成 zip”来实现压缩的而是利用数据本身的分布特征结合多种编码算法字典编码、RLE 编码、Delta 编码等从底层重新设计了存储结构。这跟“CSV 先打成 tar.gz”有本质区别。gzip 的 CSV 虽然也能压缩但查询时必须整体解压无法直接读取某一列或某一行压缩效率也远不如 Parquet。所以30GB 变 3GB并不是变魔术只是把“用文本记录所有信息”换成了“用二进制描述数据关系与分布”从根源上消掉了冗余。2. 换格式前必须想清楚的几个问题很多人看到“压缩 90%”就热血上头立马想开干。但作为在半路踩过坑的人我先泼盆冷水动手之前有几个问题必须先想清楚否则换完格式后续的痛苦是你想象不到的。2.1 换格式后谁来消费这些数据这是最重要的问题没有之一。你换完格式后续是用 Python 的 Pandas / Polars 做分析还是用标准 SQL 查询引擎如 DuckDB、ClickHouse、Spark SQL还是准备直接扔给数据库导进去不同的下游工具对格式的支持度不一样。如果你的下游是 Pandas/PolarsParquet 可以说是完美兼容Pandas 原生支持read_parquet。如果你的下游是开源大数据组件Parquet 也是事实标准。但假如你后续需要把这个文件导入 MySQL 或者 PostgreSQL那 Parquet 就不是直接支持的了中间可能需要转换回 CSV 或者其他可导入格式。我在这次实操里最终确定了“Parquet 作为中间存储格式 DuckDB 作为查询引擎”的组合因为 DuckDB 可以直接查询 Parquet 文件无需导入这省去了数据装载的时间。2.2 数据是否会增量更新单文件还是多文件如果这是一次性静态数据直接一个超大 Parquet 文件不是问题。但如果数据后续会每天追加那建议按时间维度拆分成多个 Parquet 文件用 Hive 分区风格管理比如year2024/month05/day12/data.parquet。一开始我没太在意这个图省事把 30GB 一次性转成了一个大 Parquet 文件。后来发现要增量更新时Parquet 文件本身不可变只能整文件重写非常痛苦。最后不得不重新拆成了按天分区的多个小文件。建议大家在源头就规划好分片策略。2.3 怎么校验转换后的数据没丢、没错格式转换最怕的是数据丢行、错列。CSV 转 Parquet 不是简单的“另存为”中间的 schema 推断、类型转换、编码映射都可能出错。尤其当原始 CSV 里存在脏数据时比如某个数字列里混入了字符串“N/A”转换工具可能直接报错也可能默默把整列转成字符串类型。强烈建议转换前先记录源数据的关键统计信息至少包括三个指标总行数、每列的非空值数量、每列的哈希值或关键字段的取值分布。转换完成后对 Parquet 文件跑同样的统计进行比对。这个过程相当于数据测试能有效避免“压缩完数据对不上”的灾难。2.4 磁盘空间和中间文件规划30GB 的 CSV 要转成 Parquet如果你的磁盘只剩 5GB 空闲那必然翻车。Parquet 转换过程中至少要留出源文件大小 目标文件大小 临时缓冲的空间。我当时是先把原 CSV 移到了另一个独立的数据盘确保工作目录有足够空间。这一步看似基础但真的很重要。我见过不止一个人因为忽略了这一点转换到一半磁盘写满最后两头不到岸。3. 完整实操从 30GB CSV 到 3GB Parquet在这部分我直接给出能够落地的完整方案包含环境准备、转换过程、以及中途可能中招的细节。这里使用的是 Python 生态下的主流工具Pandas 或 Polars 负责数据处理PyArrow 作为 Parquet 底层引擎。当然为了处理 30GB 的数据内存的压力必须考虑进去。3.1 环境准备与工具选型先列一下工具清单Python 3.9 以上版本PyArrow提供 Parquet 读写能力Pandas兼容性最好适合 chunked 读取Polars更高效支持 lazy 模式可以边读边转DuckDB用于转换后的查询验证安装方式很简单pip install pandas pyarrow polars duckdb为什么不用单一工具解决因为 30GB 的数据量Pandas 一次性read_csv大概率会内存爆炸。Pandas 虽然好写但对大数据集不友好需要搭配 chunk 分批读取。Polars 虽然能做 lazy 处理但遇到特别脏的数据时它的报错信息不如 Pandas 直观。所以我的策略是先分块探查数据再用 Polars 进行转换转换后用 DuckDB 做验证一套组合拳下来稳如老狗。3.2 第一步探查 CSV 数据的“底细”千万不要拿到 CSV 就开始转换先看看这个文件长什么样。30GB 的文件不能直接用编辑器打开但可以用命令行或者 Python 只读取前几百行做探查。# 查看前 5 行了解字段结构 head -n 5 your_data.csv # 查看总行数不加载数据只数换行符 wc -l your_data.csv同时用 Python 读取一个小的 sample看看列的类型、有无缺失、有无明显的脏数据。import pandas as pd # 只读取前 10000 行推断 schema df_sample pd.read_csv(your_data.csv, nrows10000) print(df_sample.dtypes) print(df_sample.isnull().sum())这一步的核心目的有两个一是确认分隔符是逗号还是分号或者制表符二是确认每一列的数据类型。如果原始 CSV 是从某个数据库导出的大概率有日期时间列Pandas 对日期时间的解析格式需要提前确认好否则转换后会变成字符串类型直接破坏后续的日期过滤性能。3.3 第二步按块读取避免内存爆掉30GB 的 CSV如果整读进内存至少需要约 30GB 的 RAM加上 Pandas 的中间对象开销内存轻松飙到 40-50GB。普通机器根本扛不住。正确的姿势是分批读取chunking每次读取比如 100 万行转换后写入同一个 Parquet 文件。PyArrow 是支持增量写入的不必等所有数据都在内存里。先看一下分批读取的代码框架import pandas as pd import pyarrow as pa import pyarrow.parquet as pq csv_file your_data.csv parquet_file your_data.parquet # 第一批数据用于创建 Parquet 文件并推导 schema first_chunk True # 使用 chunksize 分块读取 for chunk in pd.read_csv(csv_file, chunksize1_000_000): # 在这里可以做一些类型转换或清洗比如 # chunk[event_time] pd.to_datetime(chunk[event_time]) # chunk[user_id] chunk[user_id].astype(int64) table pa.Table.from_pandas(chunk) if first_chunk: # 第一个 chunk 写入文件同时建立 schema pq.write_table(table, parquet_file, compressionsnappy) first_chunk False else: # 后续 chunk 追加写入 with pq.ParquetWriter(parquet_file, schematable.schema, compressionsnappy) as writer: writer.write_table(table)注意上面这段代码里ParquetWriter不能放在循环里反复实例化否则会覆盖之前的文件。正确写法是把ParquetWriter在循环外实例化或者用pq.write_to_dataset多文件模式。我这里的写法为了可读性做了简化实际使用时更推荐下面的结构import pandas as pd import pyarrow as pa import pyarrow.parquet as pq csv_file your_data.csv parquet_file your_data.parquet writer None for chunk in pd.read_csv(csv_file, chunksize1_000_000): # 数据预处理 chunk[timestamp] pd.to_datetime(chunk[timestamp]) table pa.Table.from_pandas(chunk) if writer is None: writer pq.ParquetWriter(parquet_file, table.schema) writer.write_table(table) if writer: writer.close()这样写的好处是只实例化一次写入器所有批次共用同一个 schema而且不会造成文件重复覆盖。3.4 第三步选择合适的压缩算法Parquet 并不自带压缩它是“存储格式 压缩算法”的组合。常用的压缩算法有三种压缩算法压缩比写入速度读取速度适用场景Snappy中等快快追求查询性能Gzip高慢慢追求极致存储压缩Zstd接近Gzip快快兼顾压缩率与性能我在实际操作中用的是Zstd。它的压缩率与 Gzip 相当但速度接近 Snappy属于综合表现最优的选择。最终 30GB 的CSV 压到 3GB用 Zstd 的同时也借助了 Parquet 内部的编码优化单纯靠压缩算法本身是达不到这个体积的。如果你的数据查询频率特别高比如要作为线上服务的数据源那可以退而求其次选 Snappy查询速度更快但文件体积可能是 Zstd 的一倍左右。3.5 第四步使用 Polars 提升效率在整个转换过程中我发现 Pandas 的 chunk 读取虽然稳定但速度确实一般尤其在单核环境下30GB 跑完将近 40 分钟。后来我换成 Polars 的scan_csvsink_parquet流程时间直接缩短到十几分钟。Polars 底层基于 Arrow使用了多线程并行处理文本解析比 Pandas 快很多。import polars as pl # 流式扫描 CSV不加载到内存 lf pl.scan_csv( your_data.csv, infer_schema_length10000, ignore_errorsTrue, low_memoryTrue, ) # 按需进行数据类型转换 lf lf.with_columns( pl.col(timestamp).str.strptime(pl.Datetime, %Y-%m-%d %H:%M:%S), pl.col(user_id).cast(pl.Int64), ) # 直接写出 Parquet 文件 lf.sink_parquet(your_data.parquet, compressionzstd)这个流程最爽的一点是它走的是流式计算内存占用极低而且速度极快。唯一的风险是 Polars 对 CSV 中脏数据的容忍度问题某些行如果字段数不一致Polars 默认会返回 null 或直接丢弃该行这就可能导致数据丢失。所以我在转换前后特别强调了行数校验。这是整个转换流程中最不可以省去的一环千万别怕麻烦。3.6 校验转换后如何确保数据完整用 Polars 转换完成只是第一步接下来必须做数据校验。我提供三个核心验证手段验证一行数对比import polars as pl count_csv 0 # 用 pandas 逐块统计 CSV 行数 for chunk in pd.read_csv(your_data.csv, chunksize1_000_000, usecols[0]): count_csv len(chunk) count_parquet pl.scan_parquet(your_data.parquet).select(pl.len()).collect()[0, 0] print(fCSV 行数: {count_csv}) print(fParquet 行数: {count_parquet}) assert count_csv count_parquet, 行数不一致验证二关键字段 MD5 校验CSV 的合法性没法直接对整个文件做 MD5因为换了格式字节流完全不同但可以用“抽样关键字段内容比对哈希”的方式。import hashlib import polars as pl # 从 CSV 随机抽 100 万行的某个关键列做哈希 # 从 Parquet 抽同样行数同样列做哈希比对真正严谨的做法是算每一列的内容指纹。比如对“user_id”列的所有值做聚合哈希转换前后比对结果。这在 30GB 级别的数据上是强校验。验证三抽样逐行对比对 CSV 和 Parquet 各随机抽取 10000 行按 key 字段关联后对比每一列的值。虽然有抽样误差但配合行数对比已经能发现 99% 的严重问题。4. 转换之后的用法3GB 的 Parquet 比 30GB 的 CSV 好用在哪转换好只是起点关键在于转换后的格式能带来什么实际收益。这里我用真实的数据查询场景来做对比。4.1 全表扫描从 5 分钟变成 10 秒CSV 格式下就算只是想统计某个日期范围内有多少条记录也得把 30GB 文件读一遍按行解析、过滤。实测在普通服务器上光是遍历一遍就超过 5 分钟。换成 Parquet 后DuckDB 查询相同的聚合操作用时不到 10 秒。这背后的原因是 Parquet 的三大特性列裁剪Column Pruning、谓词下推Predicate Pushdown和压缩数据直接运算。列裁剪指的是查询语句只需要哪几列Parquet 就只读取哪几列完全不碰无关数据。30GB 的表如果有 20 列而查询只需要 2 列实际从磁盘读取的数据量可能只有 300MB 左右。谓词下推则让你按某个字段过滤时Parquet 先读取该列的统计信息如最小值、最大值跳过无关的 row group进一步减少 IO。我用 DuckDB 验证了一个具体场景SELECT status, count(*), sum(amount) FROM your_data.parquet WHERE event_date DATE 2024-01-01 AND event_date DATE 2024-02-01 GROUP BY status;在没有建任何索引的情况下这条 SQL 在 3GB 的 Parquet 上跑出了非常理想的效果。同样的逻辑放到 CSV 上光是扫描就要命。4.2 与 Pandas/Polars 的无缝衔接数据处理这一行永远绕不开 Python。Parquet 对 Pandas 和 Polars 都是“一读即用”的状态不需要额外的解析开销。import polars as pl # 直接读取 Parquet df pl.read_parquet(your_data.parquet) # 筛选并聚合 result df.filter(pl.col(event_date) 2024-01-01).group_by(status).agg(pl.len()) print(result)特别是在内存占用方面Polars 读取 Parquet 和读取 CSV 完全是两个概念。同一份数据CSV 读入内存可能撑爆 32GB RAMParquet 因为列式存储和压缩可能只需要 8GB 左右就能完成同样的计算任务。4.3 与数据库导入的配合有些场景下格式换完之后最终数据还是要进数据库比如 MySQL 或 PostgreSQL。Parquet 虽然不能直接被这些数据库导入但可以通过两步走实现无缝过渡使用 DuckDB 查询 Parquet 文件并将结果导出为数据库可识别的格式。直接使用数据库的COPY/LOAD DATA命令导入。例如用 DuckDB 把某个分区的数据导出成 CSV已经过类型修正和清洗COPY ( SELECT * FROM your_data.parquet WHERE event_date DATE 2024-01-01 ) TO output_for_db.csv (HEADER, DELIMITER ,);5. 从 CSV 到 Parquet避坑经验与实操心得最后这部分我把这次转换过程中真正具有普适性的经验整理出来。其中不少是遇到问题后上网查资料都很难找到直接答案的细节。5.1 关于 CSV 拆分不要用固定大小拆要用行数拆当你的 CSV 有 30GB 时直接转换不是不行但如果源文件的 CSV 本身就存在编码问题或格式问题拆分再转反而更稳妥。网上搜“csv拆分工具”能搜出一大堆但很多工具是按固定大小比如每个文件 1GB拆分的。这在实践中不好用因为固定大小拆分容易把一个完整的记录行拦腰截断导致后续转换报错。更合理的拆分方式是按行数拆。比如每个文件 200 万行。这样每个子文件都是完整的记录集合后续不管是转换还是人工排查都能独立处理。我当时写了一个简单的 Python 拆分脚本import pandas as pd csv_file your_data.csv split_size 2_000_000 # 每个文件 200 万行 for i, chunk in enumerate(pd.read_csv(csv_file, chunksizesplit_size)): chunk.to_csv(fsplit_data_{i}.csv, indexFalse)这个方法唯一的缺点是 Pandas 会重新推断每个 chunk 的 dtype可能造成前后分片 schema 不一致。所以拆分完成后转换前最好再用第一步的探查方法确认所有分片的 dtypes 一致。5.2 关于 DBever 和数据库导入工具读取 CSV 时的编码问题很多人在处理 CSV 时会遇到“文件手机打开正常电脑打开乱码”的问题。这本质上不是文件损坏而是编码不一致导致的。电脑端默认读取 CSV 时通常使用系统的本地编码比如 GBK而文件本身是 UTF-8 编码。手机端一般默认按 UTF-8 解析因此显示正常。在格式转换之前务必确认 CSV 编码# Linux 下查看文件编码 file -i your_data.csv如果显示charsetutf-8在 Python 里读取时显式指定pd.read_csv(your_data.csv, encodingutf-8)如果是 GBK 编码pd.read_csv(your_data.csv, encodinggbk)在把 CSV 导入 DBeaver 或其他数据库管理工具时也一定要确认工具的导入编码设置与文件编码一致否则导入后中文内容直接变成乱码后续清洗更费劲。5.3 关于 md5 校验不要对整个大文件做 MD5很多人在网上搜“csv文件怎么进行md5校验”第一反应是对整个文件做 MD5。这在文件小的时候没问题但 30GB 的 CSV 做 MD5 需要读完整份文件极其耗时而且对 Parquet 文件做 MD5 没有意义因为字节流变了但数据内容可能是一样的。正确做法是对数据内容做逻辑指纹。比如选定一个包含唯一值的 ID 列拼接所有 ID 后做 MD5。这个校验只读取一列数据速度远快于全文件扫描import hashlib import polars as pl def column_fingerprint(file_path, column_name, is_parquetTrue): if is_parquet: df pl.scan_parquet(file_path).select(column_name).collect() else: df pl.scan_csv(file_path).select(column_name).collect() # 将列转换为字符串并拼接然后计算 MD5 content ,.join(map(str, df[column_name].to_list())) return hashlib.md5(content.encode()).hexdigest()只要两个格式下同一列内容的 MD5 相同基本可以确认该列数据一致性没有问题。5.4 关于“格式工厂”和通用转换工具的局限性网上很多工具打着“格式工厂”的名头能转视频、转图片也能声称支持文档格式转换。但在 CSV 转 Parquet 这个场景下这类通用工具基本不堪用。原因在于Parquet 是强类型格式转换过程需要识别每一列的类型。通用工具没有智能的类型推断能力往往把所有字段都当字符串处理导致转换后丢失了列式存储的压缩优势。大文件支持差。很多在线转换工具限制了上传文件的大小不可能处理 30GB 的数据集。隐私问题。数据一旦上传到第三方转换工具你的数据就等于拱手送人了。对于业务数据来说这是不可接受的。因此凡是超过几百 MB 的 CSV都建议本地用 Python / Polars / DuckDB 自建流水线转换。这也是这篇文章的核心价值所在。5.5 一个更高效的选择直接绕过 CSV 生成 Parquet在处理的最后我发现一个优化点如果数据流的上游可以控制完全可以选择绕过 CSV直接从原始数据源生成 Parquet。比如日志数据可以通过 Fluentd / Filebeat 直接以 JSON 或 Parquet 格式输出到数据湖而不是先落一份 CSV。这样做的收益是巨大的避免了一次磁盘 IO 的全量读写跳过 CSV 解析的高昂 CPU 成本从一开始就拥有类型信息和压缩优势但现实是很多业务系统仍然只支持导出 CSV所以 CSV 转 Parquet 的存量数据转换需求不会消失。处理这种批量数据时我建议在团队内部固化一套标准转换流程用 Docker 封装好环境以后再有新数据进来直接跑流水线即可。6. 你可能会关心的延伸场景把 30GB 的 CSV 换成了 3GB 的 Parquet这只是万里长征第一步。在实际业务中这个行为往往只是数据治理链路的一个环节。这里再延伸聊几个我接触到的高频场景。6.1 从 CSV 到更标准化的开放格式KMZ、GeoJSON 与地理数据处理在一些地理信息相关的项目里CSV 也是常见的数据分发格式比如一批带有经纬度的 POI 数据。如果只有几万个点CSV 还好说。一旦点位上百万CSV 的加载和渲染就会非常卡顿。数据转换的价值在这里同样成立。你可以把经纬度 CSV 转换为更标准的地理空间格式比如 GeoJSON或者打包成 KMZ。KMZ 是 Keyhole Markup Language 的压缩格式其实就是 KML 文件的 zip 包。在很多 GIS 工具里KMZ 比 CSV 更易用因为它自带样式描述和坐标系信息。import pandas as pd import geopandas as gpd df pd.read_csv(points.csv) gdf gpd.GeoDataFrame( df, geometrygpd.points_from_xy(df[lng], df[lat]), crsEPSG:4326, ) gdf.to_file(points.kmz, driverKMZ)类似的思路还可以用于把 CSV 转换为更适合网络传输的格式。比如前端地图加载如果数据量大直接用 GeoJSON 也会卡更推荐用矢量瓦片格式如 MVT。网上有人搜“cesium 加载 mvt 格式”其实就是想解决大空间数据的渲染性能问题。这些里层逻辑和 CSV 转 Parquet 是一样的——把文本变成高效的二进制结构。6.2 从 CSV 到特定软件的数据输入MATLAB 与示波器数据还有一类场景CSV 是数据传输的中间格式但目标软件对输入格式有特定要求。比如示波器导出的波形数据通常是 CSV 格式要导入 MATLAB 做 FFT 仿真直接把 CSV 扔进去一般也能跑但处理效率低、变量名混乱。推荐的方式是先将 CSV 转为 MATLAB 原生支持的 .mat 格式这相当于给数据加了一层“原生类型”的壳import scipy.io import pandas as pd df pd.read_csv(waveform.csv) scipy.io.savemat(waveform.mat, {data: df.to_numpy(), columns: list(df.columns)})转换后 MATLAB 加载 .mat 文件时数据已经是矩阵格式省去了 parse 的时间尤其是 CSV 文件达到数 GB 时这个优化非常明显。类似的Kettle 做 ETL 时如果两个数据表合并输出成一个 CSV再转去其他系统中间也可以考虑直接用 Parquet 作为落地格式减少临时文件占用空间。6.3 从 CSV 到 Excel别忽略“格式”带来的信息损失聊了这么多大数据场景最后说个接地气的。很多运营同事拿到的数据还是 CSV但他们最终要交出去的结果是 Excel 表格。网上经常有人搜“csv手机打开正常,电脑打开不正常”“复制过去格式不一样”之类的问题说白了还是编码与格式的问题。CSV 本质是不带格式的纯文本它没有列宽、没有单元格样式、没有多 sheet。如果你需要交给别人的是一份带格式的报表正确做法是直接生成 .xlsx而不是让同事在 CSV 上手动调格式。用 Pandas 导出 Excel 并设置列宽import pandas as pd df pd.read_csv(data.csv) with pd.ExcelWriter(report.xlsx, engineopenpyxl) as writer: df.to_excel(writer, sheet_name数据, indexFalse) # 设置列宽 worksheet writer.sheets[数据] for col_idx, col_name in enumerate(df.columns, start1): max_len max(df[col_name].astype(str).str.len().max(), len(col_name)) 2 worksheet.column_dimensions[chr(64 col_idx)].width min(max_len, 50)这样输出的 Excel 打开就是正常表格也不会出现列宽错乱的问题。最后说点实在的这次实战下来我个人最大的体会是数据格式的转换表面上是“换个后缀名”实际上是“重新思考数据的组织方式”。CSV 之所以成为通用格式是因为它简单、可读、几乎所有工具都支持但它的简单是有代价的——空间浪费、无类型信息、无索引、查询性能差。Parquet 补足了这些缺点但也带来了“必须用合适工具去读”的额外学习成本。在动手转换前请问自己三个问题谁要读这份数据多久读一次有哪些字段是高频查询条件这些答案决定了你应该选 Parquet、ORC、还是干脆直接导入数据库。对于我这次处理的数据换成 Parquet 是一个正确的决定。磁盘空间从 30GB 降到 3GB日常查询从几分钟缩到几秒后续接 Polars 和 DuckDB 也是顺手的事。如果你手里正好也有一个“庞然大物”般的 CSV不妨照着这篇文章的思路试一次相信你会回来感谢这个决定。再分享一个小技巧转换完成后别急着删原文件。把原 CSV 压缩归档留一份放到冷存储里。虽然它占空间但它是最终的事实来源万一后续新工具不支持 Parquet你还有退路。等业务稳定运行一个月后再决定是否彻底清理。数据安全这件事永远是多一份冗余多一分安心。
返回列表