第3章 3.5 综合实战:CSV 导入导出工具

📖 这是「PHP 从入门到精通」系列教程的第 15 章。上章我们学了文件读写与目录遍历,现在你已经有能力读取文件内容了。但如果让你处理 1000 行数据,手动读就不现实了——这一章,我们用 CSV 导入导出工具,把文件操作和数据处理串起来,做一个真正能跑的小工具。


🎯 开场 3 分钟:为什么要学这个?

你有没有遇到过这些情况?

  • 运营给你发了一份 500 行的用户数据表,让你筛出「北京地区」「3 月份注册」的用户——你是打算一行行复制粘贴,还是用 Excel 表格一顿操作?
  • 数据导出来是乱码,打开 Excel 发现全是 锟斤拷
  • 想把程序里的数据保存下来,下次程序启动还能用——却不知道该存成什么格式?

CSV 就是解决这些问题的。 它本质上就是一个「用逗号分隔的表格文件」,Excel 能直接打开,Python 读起来也特别简单。

这一章结束,你会有一个能导入 CSV、筛选数据、导出结果的完整小工具,运营给你的表格丢进去,3 秒出结果\n\nSimple tech illustration expla\n\nAI comic creation scene, creat\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 判断,把符合条件的行存到新列表
  • 工具化:把重复操作封装成函数,用命令行参数让它可以重复使用

延伸学习资源

  1. Python 官方 csv 模块文档 - 权威参考
  2. 《Python编程:从入门到实践》第 14 章 - 有几个实战项目
  3. Pandas 入门 - 处理大数据用这个库更方便

互动钩子

📊 你在工作中需要处理 CSV 数据吗?有没有遇到过头疼的乱码或者大文件?评论区聊聊你的场景,老粉优先回复!


下章预告

学完这章,你已经能读写文件、处理数据了。但如果想做一个「用户填表单」的网页,网页上填的数据怎么传给 PHP 处理?下一章,我们来聊 $_GET$_POST,做你的第一个「表单交互」功能。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。