# 数据输入基础:CSV、JSON、XML 文件解析

轨迹数据处理与地图匹配分析平台 - 第3节

3.1 三种格式,三种故事

数据工程师的日常,说白了就是和各种格式的文件打交道。刚开始带团队时,我发现新人最容易卡壳的地方——不是算法,不是框架,而是连数据都读不进来。

CSV、JSON、XML,这三兄弟几乎是所有数据管线的入口。你想想看,无论是从业务系统拉数据,还是对接第三方API,最后到你手里的,大概率是这三种格式之一。

我个人习惯,拿到一个新项目,先看数据长什么样。看清楚了,再动代码。

3.2 CSV:最朴实的表格搬运工

3.2.1 CSV长什么样?

CSV(Comma-Separated Values)就是逗号分隔的值。说白了,就是把表格拍平了,一行一条记录,字段之间用逗号隔开。

name,age,city,score
张三,28,北京,92.5
李四,35,上海,88.0
王五,24,深圳,95.2

嗯,这里要注意——CSV没有严格的类型系统。所有字段都是字符串,解析的时候得你自己处理类型转换。

3.2.2 用Python读CSV

Python标准库自带csv模块,我个人基本只用它,简单可靠。

import csv

with open("students.csv", "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["name"], row["age"], row["score"])

如果用pandas,一行搞定:

import pandas as pd
df = pd.read_csv("students.csv")
print(df.head())
性能对比
小文件(<100MB): pandas读起来最方便
大文件(>1GB): 建议用csv模块循环读取,避免内存爆炸

3.2.3 避坑指南

我曾经接手过一个项目,CSV文件里有字段本身包含逗号,比如地址字段"北京市,朝阳区"。结果一读,字段全错位了。

解决方案:用双引号包裹含逗号的字段,或者换个分隔符,比如制表符TSV。

name,address,phone
张三,"北京市,朝阳区",13800138000
李四,"上海市,浦东新区",13900139000

还有编码问题——UTF-8 with BOM、GBK、Latin-1,我踩过的坑至少有三四种。建议统一用UTF-8,读取时指定 encoding="utf-8-sig" 来跳过BOM头。

3.3 JSON:API时代的主角

3.3.1 JSON的核心结构

JSON(JavaScript Object Notation)用键值对组织数据。相比CSV的扁平行,JSON天然支持嵌套结构——数组里套对象,对象里再套数组,非常灵活。

{
  "students": [
    {
      "name": "张三",
      "age": 28,
      "city": "北京",
      "score": 92.5,
      "courses": ["数学", "英语", "物理"]
    },
    {
      "name": "李四",
      "age": 35,
      "city": "上海",
      "score": 88.0,
      "courses": ["化学", "生物"]
    }
  ],
  "total_count": 2,
  "source": "教务系统"
}

3.3.2 Python解析JSON

标准库json模块,上手很快。

import json

with open("students.json", "r", encoding="utf-8") as f:
    data = json.load(f)

for student in data["students"]:
    print(student["name"], student["courses"])

读写也是对称的:

# 从字符串解析
json_str = '{"name": "张三", "age": 28}'
obj = json.loads(json_str)

# 转成字符串
json_str_out = json.dumps(obj, ensure_ascii=False, indent=2)
print(json_str_out)

我个人习惯,dumps时一定加 ensure_ascii=False,否则中文会变成\u5f20\u4e09这种转义字符,可读性很差。indent=2让输出有缩进,调试时一目了然。

小技巧
处理超大JSON文件时,考虑使用ijson库流式解析,不用一次性加载到内存。

3.3.3 JSON的陷阱

JSON的键必须用双引号,字符串值也必须用双引号。单引号是不合法的。我见过有人从数据库导出JSON时用了单引号,结果解析直接报错。

还有,JSON没有注释语法。想加注释?抱歉,标准不允许。不过有些工具支持JSON5或HJSON格式,它们扩展了注释功能。

3.4 XML:老大哥的坚持

3.4.1 XML的结构特点

XML(Extensible Markup Language)用标签定义结构,和HTML很像,但标签名可以自定义。XML有三大核心:元素、属性、命名空间。

<?xml version="1.0" encoding="UTF-8"?>
<class name="计算机科学与技术班" year="2024">
  <students>
    <student id="001">
      <name>张三</name>
      <age>28</age>
      <city>北京</city>
      <scores>
        <score subject="数学">95.0</score>
        <score subject="英语">88.5</score>
      </scores>
    </student>
    <student id="002">
      <name>李四</name>
      <age>35</age>
      <city>上海</city>
    </student>
  </students>
</class>

3.4.2 Python解析XML

Python有多个解析XML的库,我按场景分三类:

库 场景 特点
xml.etree.ElementTree 中小文件,日常解析 标准库,API简洁,够用
lxml 大文件,XPath查询 第三方的,速度快,功能强
xml.sax 超大文件,流式处理 事件驱动,内存友好

ElementTree的常用写法:

import xml.etree.ElementTree as ET

tree = ET.parse("students.xml")
root = tree.getroot()

# 遍历所有学生
for student in root.findall(".//student"):
    name = student.find("name").text
    age = student.find("age").text
    student_id = student.get("id")
    print(f"ID={student_id}, Name={name}, Age={age}")

如果用lxml,XPath查询会更灵活:

from lxml import etree

tree = etree.parse("students.xml")
names = tree.xpath("//student/name/text()")
print(names)  # ['张三', '李四']
注意
XML的命名空间(xmlns)会让XPath变复杂。比如子元素在ns:Student下,查询时要加上命名空间映射。我刚开始用lxml时在这上面浪费了不少时间。

3.5 三个格式,一张表格说清楚

3.5.1 何时用哪个?

维度 CSV JSON XML
数据结构 扁平表格 键值对+嵌套 树形标签
可读性 极简,一行一条 良好,结构直观 标签冗余,偏重
文件大小 最小 中等 最大(标签重复)
类型支持 全字符串 数字/布尔/字符串/数组 全字符串(通过属性+类型描述)
解析速度 极快 快 慢(标签解析成本高)
主要场景 量级大、结构规律的数据 API接口、配置、NoSQL 遗留系统、文档标记、法律数据
校验能力 几乎无 JSON Schema DTD/XSD,强校验

我个人的选择原则很简单:

  • 数据规律、量大、结构简单 → CSV
  • 结构嵌套、API通信 → JSON
  • 需要强校验、有命名空间、遗留系统 → XML

3.6 三种格式的解析流程

下面这张图,是我画给学生用的解析流程总览。三种格式走到最后,都是标准的Python对象——列表和字典。记着,数据输入不是终点,把数据变成可操作的内存结构才是。

数据输入解析流程总览 CSV 文件 DictReader → 字典列表 JSON 文件 json.load → 字典/列表 XML 文件 parse → Element树 Python 内存对象 清洗 → 转换 → 入库 共性问题 编码声明 (UTF-8) 异常处理 (try-except) 特别提醒 大文件用流式解析 不要全量加载内存

3.7 实战避坑清单

做数据输入这行当久了,各种奇葩问题都见过。整理一份清单,你留着用:

3.7.1 编码问题

  • UTF-8 with BOM:Excel导出CSV时经常带BOM头,Python读时会多出不可见字符\ufeff。用 encoding="utf-8-sig" 解决。
  • GBK/GB2312:国内旧系统常见,读的时候用 chardet 库先检测编码。
  • 乱码预防:我建议统一规范——所有输入文件强制 UTF-8,非UTF-8的提前转码。

3.7.2 字段解析陷阱

  • CSV字段内换行:有些字段内容包含换行符,把文件行数搞乱了。用 csv.reader 处理,它能识别引号包裹的换行。
  • JSON Key重复:标准JSON不允许重复键,但有些实现不会报错——后一个覆盖前一个。写代码时要有心理准备。
  • XML空白节点:text属性可能返回 None 或空字符串,用 text or "" 兜底。

3.7.3 大文件处理原则

  • CSV:用 csv.reader 逐行迭代,别一次性 readlines()。
  • JSON:用 ijson 流式解析,或分块加载。
  • XML:用 xml.sax 事件驱动,或者 lxml.etree.iterparse 增量解析。
我的经验法则:
- 文件小于100MB → 全量读入,怎么方便怎么来
- 文件100MB-1GB → 循环读取,控制内存
- 文件大于1GB → 流式/分布式处理,考虑Spark或Dask

3.8 小结一下

数据输入,是整个数据管线的第一关。CSV、JSON、XML,三种格式各有长短。

CSV简单直接,适合规矩的表格数据;JSON灵活轻量,是API时代的宠儿;XML虽然啰嗦,但有强大的校验和命名空间能力,在金融、法律等行业依然坚挺。

解析不难,难的是把数据扛进来之后,怎么保证它不乱、不丢、不重复。编码、换行、转义、null值处理——这些细节才是见真功夫的地方。