4. 写入CSV文件:使用csv.writer写入行、写入表头、处理特殊字符与换行

前面我们一直在讲怎么读CSV,但实际工作中,写CSV才是真正的家常便饭。你想想看,数据清洗完了要导出、处理结果要保存、给下游系统投喂数据……这些都得靠写CSV来完成。

我个人习惯把写CSV分成三个层次:先学会基本写入,再搞定表头,最后处理那些让人头疼的特殊字符。咱们一层层来。

4.1 基础写入:csv.writer 的两种打开方式

Python的csv.writer用起来很直接。但有个细节我刚开始也踩过坑——文件打开模式。

核心原则:写CSV必须用 'w' 模式,并且加上 newline='' 参数。

为什么非要加 newline=''?因为Windows系统下,如果不指定这个参数,写入的每一行后面都会多一个空行。我在项目中第一次导出数据时,客户反馈说“文件里全是空行”,排查了半天才发现是这个问题。

import csv

# 正确写法
with open('output.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.writer(f)
    writer.writerow(['张三', '28', '北京'])
    writer.writerow(['李四', '32', '上海'])

这段代码会生成两行数据。注意看,writerow()接收的是一个列表,每个元素对应一列。

4.2 写入多行:writerows 批量操作

如果你有几十上百行数据要写,一条条writerow()太慢了。这时候用writerows(),一次性搞定。

data = [
    ['产品A', '2024-01-15', '98.5'],
    ['产品B', '2024-01-16', '87.2'],
    ['产品C', '2024-01-17', '92.1'],
]

with open('products.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.writer(f)
    writer.writerows(data)

writerows()接收一个二维列表,内部自动遍历每一行并写入。效率比循环调用writerow()高不少,尤其是数据量大的时候。

4.3 写入表头:让数据有“身份证”

没有表头的CSV文件,说白了就是一堆裸数据。别人拿到文件根本不知道第一列是什么、第二列代表什么。我建议你养成习惯——任何CSV文件,第一行都写表头。

with open('employees.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.writer(f)
    # 先写表头
    writer.writerow(['姓名', '年龄', '部门', '入职日期'])
    # 再写数据行
    writer.writerow(['王五', '30', '技术部', '2023-06-01'])
    writer.writerow(['赵六', '27', '市场部', '2023-08-15'])

表头和数据行用同样的writerow()方法写入,只是位置不同——表头永远在第一行。

小技巧:如果你用pandas处理数据,导出CSV时默认会带表头。但用原生csv模块时,表头需要手动写。我一般把表头定义成一个单独的列表,放在代码开头,方便维护。

4.4 处理特殊字符:引号、逗号、换行符

这才是写CSV的真正难点。你想想看,如果数据里本身就包含逗号,或者包含换行符,直接写进去不就乱套了吗?

举个例子,某个字段值是 "北京,朝阳区",里面有个逗号。如果不做处理,解析方会把它当成两列。

csv.writer 默认会帮我们处理大部分情况——它会在必要时自动给字段加引号。但具体怎么加,可以通过 quoting 参数控制。

quoting 参数 含义 适用场景
csv.QUOTE_ALL 所有字段都加引号 数据格式不确定,求稳
csv.QUOTE_MINIMAL 只在必要时加引号(默认) 常规数据,追求可读性
csv.QUOTE_NONNUMERIC 非数字字段加引号 数字和文本混合的数据
csv.QUOTE_NONE 从不加引号 确定数据不含特殊字符

我曾经遇到过一个坑:配方数据里有个字段叫“备注”,里面写了多行文本,比如:

备注: 该批次需要
在恒温环境下保存
避免阳光直射

这个字段里包含了换行符。如果用默认的QUOTE_MINIMAL,写入时会把换行符原样写进去,导致CSV文件里出现真正的换行,破坏文件结构。

正确的做法是:

import csv

data = [
    ['配方001', '25°C', '该批次需要\n在恒温环境下保存'],
    ['配方002', '18°C', '避免阳光直射\n密封保存'],
]

with open('recipes.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.writer(f, quoting=csv.QUOTE_ALL)
    writer.writerow(['配方编号', '温度', '备注'])
    writer.writerows(data)

用QUOTE_ALL后,所有字段都会被双引号包裹。包含换行符的字段会被正确转义,解析方读取时能还原出多行文本。

注意:如果数据里本身包含双引号,比如 他说"你好",csv.writer 会自动把它转义成 他说""你好""。这是CSV标准规定的转义方式,读取时也能正确还原。

4.5 自定义分隔符与换行符

虽然逗号是CSV的默认分隔符,但有些场景下需要换别的。比如欧洲一些地区用逗号做小数点,CSV文件就会改用分号做分隔符。

with open('europe_data.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.writer(f, delimiter=';')  # 用分号做分隔符
    writer.writerow(['温度', '湿度', '压力'])
    writer.writerow(['23,5', '65,2', '1013,2'])

这里温度 23,5 表示23.5摄氏度。如果用逗号做分隔符,解析方就会把 23 和 5 当成两列,数据全乱套。

换行符方面,默认是 \r\n(Windows风格)。如果你需要输出给Linux系统用,可以指定 lineterminator='\n'。

with open('linux_data.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.writer(f, lineterminator='\n')
    writer.writerow(['A', 'B', 'C'])

经验之谈:除非有明确的跨平台需求,否则我建议保持默认的 \r\n。因为大多数CSV解析器都能兼容两种换行符,没必要自己折腾。

4.6 写入流程总览

下面这张图总结了写CSV文件的完整流程和关键决策点:

CSV 写入流程与关键决策 开始写入 打开文件:open('w', newline='') 创建 writer = csv.writer(f) 是否需要写入表头? 是 writerow(表头) 否 跳过表头 写入数据:writerows(数据) 检查特殊字符:引号/逗号/换行 写入完成 关键参数 quoting / delimiter / lineterminator

4.7 实战:配方数据写入完整示例

最后,咱们把前面学的串起来,写一个完整的配方数据导出函数:

import csv

def export_recipes(recipes, filename='配方数据.csv'):
    """
    导出配方数据到CSV文件
    recipes: 列表,每个元素是字典,包含配方信息
    """
    fieldnames = ['配方编号', '配方名称', '温度(°C)', '湿度(%)', '备注']
    
    with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
        writer = csv.writer(f, quoting=csv.QUOTE_ALL)
        
        # 写入表头
        writer.writerow(fieldnames)
        
        # 写入数据
        for recipe in recipes:
            row = [
                recipe.get('id', ''),
                recipe.get('name', ''),
                recipe.get('temp', ''),
                recipe.get('humidity', ''),
                recipe.get('note', '')
            ]
            writer.writerow(row)
    
    print(f'已导出 {len(recipes)} 条配方数据到 {filename}')

# 使用示例
data = [
    {'id': 'R001', 'name': '标准发酵', 'temp': '28.5', 'humidity': '75', 'note': '恒温发酵'},
    {'id': 'R002', 'name': '低温熟成', 'temp': '4.0', 'humidity': '85', 'note': '需要\n避光保存'},
]

export_recipes(data)

这里我用了 utf-8-sig 编码,而不是普通的 utf-8。为什么?因为Excel打开UTF-8编码的CSV文件时,经常会出现中文乱码。加上BOM(字节顺序标记)后,Excel就能正确识别编码了。这个坑我踩过不止一次,后来干脆所有导出都用 utf-8-sig。

总结一下写CSV的要点:

  • 文件打开必须加 newline='',否则多空行
  • 表头和数据用同样的 writerow() 写入
  • 数据含特殊字符时,用 quoting=csv.QUOTE_ALL 保底
  • 跨平台场景注意分隔符和换行符的选择
  • 给Excel用的文件,编码用 utf-8-sig