第3章 3.5 综合实战:CSV 导入导出工具
📖 这是「PHP 从入门到精通」系列教程的第 15 章。上章我们学了文件读写与目录遍历,现在你已经有能力读取文件内容了。但如果让你处理 1000 行数据,手动读就不现实了——这一章,我们用 CSV 导入导出工具,把文件操作和数据处理串起来,做一个真正能跑的小工具。
🎯 开场 3 分钟:为什么要学这个?
你有没有遇到过这些情况?
- 运营给你发了一份 500 行的用户数据表,让你筛出「北京地区」「3 月份注册」的用户——你是打算一行行复制粘贴,还是用 Excel 表格一顿操作?
- 数据导出来是乱码,打开 Excel 发现全是
锟斤拷? - 想把程序里的数据保存下来,下次程序启动还能用——却不知道该存成什么格式?
CSV 就是解决这些问题的。 它本质上就是一个「用逗号分隔的表格文件」,Excel 能直接打开,Python 读起来也特别简单。
这一章结束,你会有一个能导入 CSV、筛选数据、导出结果的完整小工具,运营给你的表格丢进去,3 秒出结果\n\n
\n\n
\n\n。
🧱 基础 25 分钟:核心概念
什么是 CSV?为什么不用 Excel?
先说个生活类比:
- Excel 就像一个精装修的房子,功能强大但文件笨重(.xlsx 其实是压缩包)
- CSV 就像一张白纸,上面用「表格的形式」写字,轻量、快速、任何设备都能打开
CSV 的格式长这样:
name,age,city
张三,25,北京
李四,30,上海
王五,28,广州
第一行是表头,后面每一行是一条数据,用逗号分隔。简单吧?
Python 怎么读 CSV?
Python 读取 CSV 依赖内置的 csv 模块,不需要额外安装。读取的思路很简单:
import csv
# 打开文件(用 with 保证不漏关)
with open('users.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f) # 创建一个"阅读器"
for row in reader: # 逐行读取
print(row)
运行结果:
['name', 'age', 'city']
['张三', '25', '北京']
['李四', '30', '上海']
['王五', '28', '广州']
每行变成了一个列表,row[0] 是姓名,row[1] 是年龄,row[2] 是城市。说白了就是帮你把逗号去掉,把每行变成数组。
读成字典格式(更直观)
上面的写法需要记住「第 0 列是姓名,第 1 列是年龄」,很容易搞混。用 csv.DictReader 可以把每行变成字典:
import csv
with open('users.csv', 'r', encoding='utf-8') as f:
reader = csv.DictReader(f) # 读成字典格式
for row in reader:
print(row['name'], row['age'], row['city'])
输出结果:
张三 25 北京
李四 30 上海
王五 28 广州
这样读的好处是:看到 row['name'] 就知道是姓名,不用数列的位置。
Python 怎么写 CSV?
光读不够,最终要把处理完的数据保存下来。用 csv.writer 写文件:
import csv
# 准备数据(列表里套列表)
data = [
['name', 'age', 'city'], # 表头
['张三', '25', '北京'],
['李四', '30', '上海']
]
# 写入文件
with open('output.csv', 'w', encoding='utf-8', newline='') as f:
writer = csv.writer(f) # 创建一个"写入器"
writer.writerows(data) # 一次写入多行
⚠️ 注意:newline='' 这个参数很重要!Windows 系统不加这个会导致行间距变大。
写字典格式的 CSV
有时候你想指定列名,或者某些列不想要:
import csv
# 用字典格式写入
data = [
{'name': '张三', 'age': 25, 'city': '北京'},
{'name': '李四', 'age': 30, 'city': '上海'}
]
with open('output.csv', 'w', encoding='utf-8', newline='') as f:
fieldnames = ['name', 'age', 'city'] # 指定按这个顺序写列
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader() # 先写表头
writer.writerows(data) # 再写数据
这样输出的文件 Excel 打开就是标准的表格。
CSV 和 JSON 的区别(什么时候用哪个?)
| 场景 | 推荐格式 | 原因 |
|---|---|---|
| 表格数据(报表、名单) | CSV | Excel 直接打开,轻量 |
| 有层级结构(配置、嵌套数据) | JSON | 支持嵌套,结构清晰 |
| 要给人看 | CSV | 更通用 |
说白了:表格类数据用 CSV,配置类数据用 JSON。
🔥 实战 35 分钟:3 个递进的小项目
项目 1:5 分钟 → 读取并显示 CSV 内容
场景:你有一个 students.csv 文件,想看看里面有什么数据。
import csv
# 读取学生成绩表
with open('students.csv', 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
print("=" * 40)
print("📋 学生成绩单".center(30))
print("=" * 40)
for i, row in enumerate(reader):
print(f"第{i+1}行: 姓名={row['name']}, 数学={row['math']}, 语文={row['chinese']}")
# 预期输出:
# ========================================
# 📋 学生成绩单
# ========================================
# 第1行: 姓名=张三, 数学=85, 语文=92
# 第2行: 姓名=李四, 数学=78, 语文=88
# ...
解释:这 10 行代码干了一件事——把 CSV 文件里的每一行读出来,打印成人类能看懂的格式。核心就是 csv.DictReader 这一个 API。
项目 2:15 分钟 → 筛选数据并导出
场景:运营给你一份用户注册表,要你筛选出「北京用户」并且「年龄大于 25 岁」的,导出成新文件。
import csv
# 1. 读取原始数据
source_file = 'users.csv'
target_file = 'filtered_users.csv'
filtered_data = []
with open(source_file, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
# 筛选条件:城市是北京 AND 年龄大于 25
if row['city'] == '北京' and int(row['age']) > 25:
filtered_data.append(row)
# 2. 写入筛选后的数据
with open(target_file, 'w', encoding='utf-8', newline='') as f:
fieldnames = ['name', 'age', 'city', 'register_date']
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(filtered_data)
print(f"✅ 筛选完成!共找到 {len(filtered_data)} 条符合条件的数据")
print(f"📁 结果已保存到: {target_file}")
# 预期输出:
# ✅ 筛选完成!共找到 3 条符合条件的数据
# 📁 结果已保存到: filtered_users.csv
解释:
- 第 1 部分是读,用 csv.DictReader 把每行变成字典
- 第 2 部分是筛选,加了一个 if 判断
- 第 3 部分是写,用 csv.DictWriter 按指定列顺序写入
这其实就是 ETL(Extract-Transform-Load) 的简化版:读数据 → 转换 → 存数据。
项目 3:15 分钟 → 做一个小工具:成绩统计器
场景:做一个命令行工具,输入成绩 CSV 文件路径,自动输出:总分、平均分、最高分、最低分。
import csv
import sys
def calculate_scores(file_path):
"""计算成绩统计"""
scores = []
students = []
with open(file_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
math_score = int(row['math'])
chinese_score = int(row['chinese'])
total = math_score + chinese_score
scores.append(total)
students.append({
'name': row['name'],
'math': math_score,
'chinese': chinese_score,
'total': total
})
# 统计
avg = sum(scores) / len(scores)
max_score = max(scores)
min_score = min(scores)
return students, avg, max_score, min_score
def main():
if len(sys.argv) < 2:
print("使用方法: python score_tool.py <成绩文件路径>")
return
file_path = sys.argv[1]
try:
students, avg, max_score, min_score = calculate_scores(file_path)
print("=" * 45)
print("📊 成绩统计报告".center(35))
print("=" * 45)
for s in students:
print(f"{s['name']}: 数学={s['math']}, 语文={s['chinese']}, 总分={s['total']}")
print("-" * 45)
print(f"📈 平均分: {avg:.1f}")
print(f"🏆 最高分: {max_score}")
print(f"📉 最低分: {min_score}")
except FileNotFoundError:
print(f"❌ 文件不存在: {file_path}")
except KeyError as e:
print(f"❌ CSV 格式错误,缺少列: {e}")
if __name__ == '__main__':
main()
使用方式:
python score_tool.py students.csv
预期输出:
=============================================
📊 成绩统计报告
=============================================
张三: 数学=85, 语文=92, 总分=177
李四: 数学=78, 语文=88, 总分=166
王五: 数学=92, 语文=85, 总分=177
---------------------------------------------
📈 平均分: 173.3
🏆 最高分: 177
📉 最低分: 166
解释:
- 用 sys.argv 接收命令行参数,让程序可以重复使用
- 把「读取-计算-输出」封装成函数,结构更清晰
- 加了异常处理,文件不存在或格式错误时不会崩溃
💪 进阶 20 分钟:常见坑 + 性能小贴士
坑 1:Windows 写入 CSV 行间距变大
❌ 错误写法:
with open('output.csv', 'w', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerows(data)
✅ 正确写法:
with open('output.csv', 'w', encoding='utf-8', newline='') as f:
writer = csv.writer(f)
writer.writerows(data)
原因:Windows 的换行符是 \r\n,CSV 模块会自动帮你处理,但如果你不传 newline='',它会多加一个空行。
坑 2:CSV 有中文乱码
❌ 错误写法:
with open('users.csv', 'r') as f: # 没指定编码
✅ 正确写法:
with open('users.csv', 'r', encoding='utf-8') as f:
说白了:读中文文件必须指定 encoding='utf-8',不然就是乱码,这是规矩。
坑 3:把数字读成了字符串
CSV 读出来全是字符串,哪怕那一列全是数字:
row['age'] # 这是字符串 '25',不是整数 25
int(row['age']) # 需要手动转成整数才能做计算
坑 4:CSV 有空行
如果源 CSV 文件有些行是空的,csv.reader 会读出空列表 []。记得加个判断:
for row in reader:
if not row: # 跳过空行
continue
坑 5:列名有空格
有时候 CSV 表头是 name, age, city,列名后面有空格:
{'name': '张三', ' age': '25', ' city': '北京'} # 注意空格!
用 strip() 清理一下:
row = {k.strip(): v for k, v in row.items()}
性能小贴士:大文件怎么读?
如果 CSV 文件有几万行,不要一次性全部读进内存。用生成器逐行处理:
import csv
def read_large_csv(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
yield row # 一次只吐出一行,不占内存
# 使用时:
for row in read_large_csv('big_data.csv'):
process(row) # 处理完就扔掉,内存占用始终很低
调试技巧:print 大法
写 CSV 处理代码时,最实用的调试方法就是 print:
for row in reader:
print(f"原始数据: {row}") # 看看读出来是什么
if row['city'] == '北京':
print(f"✅ 命中北京: {row}") # 看看有没有匹配上
别小看这个方法,90% 的 bug 都是靠 print 发现的。
✏️ 练习题
练习 1(3 分钟):改个筛选条件
- 输入:在项目 2 中,把筛选条件从「北京 + 25岁以上」改成「上海用户」
- 预期输出:「上海」的所有用户被筛选出来
- 提示:只改 if 判断那一行
练习 2(2 分钟):加一个判断
- 输入:在项目 1 基础上,添加判断:如果数学成绩低于 60 分,显示「❌ 不及格」
- 预期输出:低于 60 分的学生行尾有「❌ 不及格」标记
- 提示:加一个
if int(row['math']) < 60判断
练习 3(5 分钟):处理新数据
- 输入:有一份商品 CSV(name, price, stock),筛选出「库存小于 10」的商品
- 预期输出:库存告急的商品列表
- 提示:复用项目 2 的代码结构,把筛选条件换成
int(row['stock']) < 10
练习 4(5 分钟):串两个项目
- 输入:用项目 2 的筛选逻辑 + 项目 3 的统计逻辑,做一个「筛选后统计」的工具
- 预期输出:筛选出符合条件的行后,统计这些行的平均分/最高分/最低分
- 提示:先筛选到
filtered_data列表,再对这个列表做统计
练习 5(5 分钟):看报错修 bug
- 输入:下面代码运行时报错
KeyError: 'ag':
import csv
with open('users.csv', 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
print(row['name'], row['ag']) # 想打印年龄,但打错了
- 预期输出:修复后正确打印年龄
- 提示:把
row['ag']改成row['age'],检查列名拼写
作业:做一个「数据导入导出工具」
需求描述:
做一个命令行工具,能实现:
1. 读取任意 CSV 文件,按指定列筛选数据
2. 把筛选结果导出成新的 CSV 文件
3. 统计筛选结果的行数
功能点:
1. python tool.py import <源文件> - 查看 CSV 文件的前 5 行
2. python tool.py filter <源文件> <列名> <值> -o <输出文件> - 按条件筛选
3. python tool.py stats <文件> - 统计行数
加分项:
1. 支持多个筛选条件(AND 逻辑)
2. 支持按范围筛选(如:年龄大于 20)
验收标准:
- 能跑起来,不报错
- 输出结果和预期一致
- 代码有注释
提交方式:评论区贴代码或 GitHub 链接
📚 总结 + 资源
这一章学了 3 个核心点:
- CSV 读写:用
csv.reader/DictReader读,用csv.writer/DictWriter写 - 数据筛选:遍历 + if 判断,把符合条件的行存到新列表
- 工具化:把重复操作封装成函数,用命令行参数让它可以重复使用
延伸学习资源:
- Python 官方 csv 模块文档 - 权威参考
- 《Python编程:从入门到实践》第 14 章 - 有几个实战项目
- Pandas 入门 - 处理大数据用这个库更方便
互动钩子:
📊 你在工作中需要处理 CSV 数据吗?有没有遇到过头疼的乱码或者大文件?评论区聊聊你的场景,老粉优先回复!
下章预告:
学完这章,你已经能读写文件、处理数据了。但如果想做一个「用户填表单」的网页,网页上填的数据怎么传给 PHP 处理?下一章,我们来聊
$_GET和$_POST,做你的第一个「表单交互」功能。

评论(0)