Python 数据处理入门(三)读取数据:CSV 与 Excel 的正确打开方式

发布时间:2026/10/15 19:16:13
Python 数据处理入门(三)读取数据:CSV 与 Excel 的正确打开方式 先确认示例数据在手边import os import mkdata # 第 1 篇里的 mkdata.py ​ if not os.path.exists(data/sales.csv): mkdata.main() # 没生成过就先跑一次 print(示例数据就绪, os.path.exists(data/sales.csv))这段会检查第 1 篇生成的data/目录在不在不在就自动补一次已经有数据时只会打印一行示例数据就绪 True不影响后面的结果。写在前面本系列已更新到第 3 篇。上一篇我们把 Series 和 DataFrame 摸清了这一篇解决数据从哪来。真实场景里数据基本只有两个来源系统导出的 CSV和同事发来的 Excel。而新手最常卡住的地方不是分析是文件根本读不进来——路径写错、编码不对、表头位置不对、Excel 有多个 sheet。这篇用第 1 篇生成的几个文件把这些问题过一遍data/sales.csv销售明细、data/sales.xlsx两个 sheet、data/employees.csv员工信息、data/students.csv学生成绩。一、读 CSV 第一步编码和 BOM先看数据本身再谈代码。我们用二进制方式读一下文件的前三个字节。import os import runpy import pandas as pd ​ # 兜底data/ 目录由第 1 篇的 mkdata.py 生成没有就先生成一次 if not os.path.exists(data/sales.csv): runpy.run_path(mkdata.py, run_name__main__) ​ # 看一眼前 3 个字节带 BOM 的 UTF-8 文件以 EF BB BF 开头 with open(data/sales.csv, rb) as f: print(文件开头三个字节, f.read(3)) ​ df pd.read_csv(data/sales.csv, encodingutf-8-sig) print(列名, df.columns.tolist()) print(df.head(3).to_string(indexFalse)) print(形状, df.shape)文件开头三个字节 b\xef\xbb\xbf 列名 [订单日期, 城市, 产品, 数量, 单价, 销售员, 金额] 订单日期 城市 产品 数量 单价 销售员 金额 2024-01-05 杭州 A-笔记本 3 49.9 小张 149.7 2024-01-06 上海 B-鼠标 5 29.9 小李 149.5 2024-01-06 上海 B-鼠标 5 29.9 小李 149.5 形状 (16, 7)EF BB BF就是 BOMExcel 另存为 CSV 时常常会加上它作用是告诉程序我是 UTF-8。这个字符肉眼看不见但它可能被算进第一个列名里——表现就是列名打印出来看着是订单日期可df[订单日期]偏偏报KeyError。写encodingutf-8-sig就等于告诉 Pandas开头有 BOM 就跳过零成本保险本系列统一这么写。另外说明一下开头那几行os和runpy是兜底用的——如果data/目录还没生成它会自动帮你跑一次mkdata.py已经有数据时它什么都不打印不会影响后面的结果。每段代码都自带依赖才是最省心的复制粘贴体验。二、路径相对路径、绝对路径和那个 r在读文件这件事上路径写错的概率比编码还高。先记住一句相对路径是相对于你运行 Python 时所在的目录不是脚本所在的目录。import os import pandas as pd ​ # 三种写法都能找到同一个文件Windows 上正斜杠也认 print(data/sales.csv →, os.path.exists(data/sales.csv)) print(./data/sales.csv →, os.path.exists(./data/sales.csv)) print(rdata\sales.csv →, os.path.exists(rdata\sales.csv)) print(拼路径推荐 os.path.join, os.path.join(data, sales.csv)) print(----) # 用带 r 的路径真的读进来一次 df pd.read_csv(rdata\sales.csv, encodingutf-8-sig) print(读进来的形状, df.shape) print(----) print(不写 r 的字符串会被转义这是新手最常踩的坑) print(data\new_sales.csv) print(加了 r 才是原样, rdata\new_sales.csv)data/sales.csv → True ./data/sales.csv → True data\sales.csv → True 拼路径推荐 os.path.join data\sales.csv ---- 读进来的形状 (16, 7) ---- 不写 r 的字符串会被转义这是新手最常踩的坑 data ew_sales.csv 加了 r 才是原样 data\new_sales.csvdata\new_sales.csv里的\n被 Python 当成了换行符路径直接碎成两行——Windows 路径里的\t、\n、\f都是转义字符。所以从资源管理器复制来的绝对路径前面一定要加r你的项目目录\data\sales.csv也就是写成pd.read_csv(r你的项目目录\data\sales.csv, encodingutf-8-sig)你的项目目录换成你电脑上的真实路径。绝对路径虽然清楚但换台电脑就失效所以更推荐把脚本和data/放同一层用相对路径data/sales.csv。在 PyCharm 里点运行、工作目录默认是项目根目录这样写最稳。三、read_csv 的常用参数read_csv有几十个参数真正天天用到的就这几个import pandas as pd ​ # ① nrows先读前几行探路几十万行的文件也能秒回 print(① nrows3, pd.read_csv(data/sales.csv, encodingutf-8-sig, nrows3).shape) ​ # ② usecols只读要用的列列多的表能省一半内存 part pd.read_csv(data/sales.csv, encodingutf-8-sig, usecols[城市, 产品, 数量]) print(② usecols, part.columns.tolist()) print(part.head(3).to_string(indexFalse)) ​ # ③ dtype指定列的类型。学号、手机号、邮编一律按字符串读否则前导 0 直接丢 stu pd.read_csv(data/students.csv, encodingutf-8-sig, dtype{学号: str}) print(③ dtype 学号列, stu[学号].dtype) print(③ dtype 语文列, stu[语文].dtype) ​ # ④ headerNone第一行不当表头有些导出文件开头就是数据 noheader pd.read_csv(data/sales.csv, encodingutf-8-sig, headerNone) print(④ headerNone, noheader.shape, noheader.columns.tolist()) ​ # ⑤ skiprows1跳过开头几行导出文件前面常有多余的标题行 skip pd.read_csv(data/sales.csv, encodingutf-8-sig, skiprows1) print(⑤ skiprows1 之后列名变成, skip.columns.tolist())① nrows3 (3, 7) ② usecols [城市, 产品, 数量] 城市 产品 数量 杭州 A-笔记本 3 上海 B-鼠标 5 上海 B-鼠标 5 ③ dtype 学号列 object ③ dtype 语文列 float64 ④ headerNone (17, 7) [0, 1, 2, 3, 4, 5, 6] ⑤ skiprows1 之后列名变成 [2024-01-05, 杭州, A-笔记本, 3, 49.9, 小张, 149.7]第 ⑤ 个最值得琢磨skiprows1跳过的是表头那一行于是第一行数据被迫当了列名列名变成了[2024-01-05, 杭州, ...]。所以真实文件前面有多余行时正确姿势是skiprows2, header0跳过 2 行废话第 3 行当表头或者干脆headerNone, names[订单日期, 城市, ...]自己命名。再换一个文件看看类型还会怎么坑人import pandas as pd ​ # 只关心这三列用 usecols 拿进来就够了 emp pd.read_csv(data/employees.csv, encodingutf-8-sig, usecols[姓名, 部门, 月薪]) print(月薪的类型, emp[月薪].dtype) print(emp.to_string(indexFalse)) print(----) print(转成可空整数, emp[月薪].astype(Int64).tolist())月薪的类型 float64 姓名 部门 月薪 张伟 技术部 12000.0 王芳 市场部 9500.0 李娜 技术部 15000.0 刘洋 人事部 8000.0 陈静 市场部 NaN 杨波 技术部 18000.0 赵敏 人事部 8500.0 周涛 市场部 10500.0 ---- 转成可空整数 [12000, 9500, 15000, 8000, NA, 18000, 8500, 10500]原始数据里月薪明明是 12000、9500 这样的整数读进来却全成了12000.0类型也变成了float64——因为一列里只要有一个缺失值整列就会被提升成浮点数。想保留整数 空值的样子可以转成 Pandas 的可空整数类型Int64I 要大写空值显示为NA而不是小数。至于空值该删还是该填是第 6 篇的主题。四、分隔符不只是逗号sepCSV 的全称是逗号分隔但现实里分号、制表符、竖线都有人用。先用我们自己的数据造一个分号文件再读回来。import pandas as pd ​ df pd.read_csv(data/sales.csv, encodingutf-8-sig) # 模拟别的系统导出的分号分隔文件 df.to_csv(data/sales_semicolon.csv, indexFalse, sep;, encodingutf-8-sig) ​ semi pd.read_csv(data/sales_semicolon.csv, encodingutf-8-sig, sep;) print(用 sep; 读回来, semi.shape) print(semi.head(3).to_string(indexFalse)) print(----) # 故意不写 sep看看会发生什么 wrong pd.read_csv(data/sales_semicolon.csv, encodingutf-8-sig) print(不写 sep 时的列名, wrong.columns.tolist())用 sep; 读回来 (16, 7) 订单日期 城市 产品 数量 单价 销售员 金额 2024-01-05 杭州 A-笔记本 3 49.9 小张 149.7 2024-01-06 上海 B-鼠标 5 29.9 小李 149.5 2024-01-06 上海 B-鼠标 5 29.9 小李 149.5 ---- 不写 sep 时的列名 [订单日期;城市;产品;数量;单价;销售员;金额]不写sep时整行被当成了一列列名变成一长串订单日期;城市;产品;...——数据其实进来了但每一列都取不出来这是文件读到了却没读对的典型症状。看到列名长得离谱先检查分隔符。制表符文件同理写sep\t。五、读 Excelsheet_name 的三种写法Excel 比 CSV 多一层结构一个文件里可能有多个 sheet。sheet_name决定了你拿到的是表还是字典。import pandas as pd ​ # ① 读单个 sheet给名字也可以给序号 0 one pd.read_excel(data/sales.xlsx, sheet_name明细, engineopenpyxl) print(① 单个 sheet, one.shape) ​ # ② 读多个 sheet给一个列表返回字典 two pd.read_excel(data/sales.xlsx, sheet_name[明细, 按城市汇总], engineopenpyxl) print(② 多个 sheet, list(two.keys()), type(two).__name__) ​ # ③ 全部读进来sheet_nameNone sheets pd.read_excel(data/sales.xlsx, sheet_nameNone, engineopenpyxl) for name, sub in sheets.items(): print(f③ {name}{sub.shape}) print(sheets[按城市汇总].to_string(indexFalse)) ​ # ④ usecolsExcel 一样支持只取部分列 part pd.read_excel(data/sales.xlsx, sheet_name明细, usecols[城市, 金额], engineopenpyxl) print(④ usecols, part.columns.tolist())① 单个 sheet (16, 7) ② 多个 sheet [明细, 按城市汇总] dict ③ 明细(16, 7) ③ 按城市汇总(5, 4) 城市 订单数 销量 销售额 上海 4 24 1545.0 北京 3 5 227.9 广州 3 16 535.4 杭州 5 19 2805.1 NaN 1 3 149.7 ④ usecols [城市, 金额]规律很好记sheet_name给字符串或整数 → 返回 DataFrame给列表或None→ 返回字典键是 sheet 名值是对应的 DataFrame用sheets[按城市汇总]取就行。另外.xlsx依赖openpyxlengineopenpyxl写不写都能跑写上是让代码更明确。六、导出to_csv、to_excel 和多 sheet 写入分析完总要交东西indexFalse是导出时最容易忘的一个参数——不写它结果里会多出一列 0、1、2 的行号。import os import pandas as pd ​ sales pd.read_csv(data/sales.csv, encodingutf-8-sig) os.makedirs(output, exist_okTrue) ​ # 导出 CSVindexFalse 去掉行号utf-8-sig 让 Excel 双击打开不乱码 sales.to_csv(output/sales_clean.csv, indexFalse, encodingutf-8-sig) ​ # 一个 Excel 里写多个 sheet with pd.ExcelWriter(output/report.xlsx, engineopenpyxl) as writer: sales.to_excel(writer, sheet_name明细, indexFalse) sales.head(5).to_excel(writer, sheet_name前5行, indexFalse) ​ # 读回来验证一下 back pd.read_excel(output/report.xlsx, sheet_nameNone, engineopenpyxl) print(写出来的 sheet, {name: sub.shape for name, sub in back.items()}) print(output 目录, sorted(os.listdir(output))) print(back[前5行].head(3).to_string(indexFalse))写出来的 sheet {明细: (16, 7), 前5行: (5, 7)} output 目录 [report.xlsx, sales_clean.csv] 订单日期 城市 产品 数量 单价 销售员 金额 2024-01-05 杭州 A-笔记本 3 49.9 小张 149.7 2024-01-06 上海 B-鼠标 5 29.9 小李 149.5 2024-01-06 上海 B-鼠标 5 29.9 小李 149.5多 sheet 必须用with pd.ExcelWriter(...) as writer这种写法写完自动保存如果直接连着调两次to_excel(same.xlsx)第二个 sheet 会把第一个覆盖掉。七、大文件chunksize 分块读取几百 MB 的日志或导出文件一次性读进来内存会直接爆掉。chunksize让 Pandas 每次只交给你一小块。import pandas as pd ​ total 0 # chunksize5每块 5 行处理完就丢内存里永远只有一小块 for i, chunk in enumerate(pd.read_csv(data/sales.csv, encodingutf-8-sig, chunksize5), start1): print(f第 {i} 块{chunk.shape}本块数量合计 {chunk[数量].sum()}) total chunk[数量].sum() print(全表数量合计, total)第 1 块(5, 7)本块数量合计 19 第 2 块(5, 7)本块数量合计 27 第 3 块(5, 7)本块数量合计 16 第 4 块(1, 7)本块数量合计 5 全表数量合计 67chunksize返回的不再是 DataFrame而是一个可以 for 循环的读取器适合逐块过滤后累加逐块写进数据库这类流式处理。要注意跨块的操作比如求中位数、去重、分组排名不能简单地在每块里各算一遍再合并那种需求还是得用usecols减列、dtype减内存或者换 Parquet 格式。常见问题Q1报UnicodeDecodeError: utf-8 codec cant decode byte ...怎么办说明文件根本不是 UTF-8。国内银行、老系统导出的 CSV 多半是 GBK/GB18030把encoding换成gbk试一次不行再试gb18030GBK 的超集兼容性更好。不要用errorsignore糊过去那会静默吃掉读不懂的字符数据错了你都不知道。Q2读 Excel 报ImportError提示里带着 openpyxl。缺 Excel 读写引擎一条命令就好pip install openpyxl。注意要装进你实际在用的那个 Python 环境python -m pip install openpyxl最保险。Q3导出时报PermissionError: [Errno 13] Permission denied: output/report.xlsx。文件正被 Excel 打开着Windows 会锁住它。关掉 Excel 再跑一次即可。养成导出文件先关、后覆盖的习惯能省很多莫名其妙的报错。小结这一篇把数据进门这一步拆开了read_csv用encodingutf-8-sig处理 BOM路径优先用相对路径、Windows 反斜杠记得加rnrows/usecols/dtype/header/skiprows/sep应对各种不规整的文件read_excel用sheet_name的三种写法拿单个、多个或全部 sheet导出时别忘indexFalse多 sheet 要用ExcelWriter文件太大就chunksize分块读。读进来只是开始接下来才是真正决定分析质量的一步先别急着算把表看清楚。下一篇我们聊数据探查拿到一张表先看这 6 个指标——形状、类型、统计量、缺失、重复、分布写成一套拿到任何新表都能跑一遍的检查清单。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询