JSON 与其他文本格式读写
本教程共 54 篇 · 第 13 篇 · 更新于 2026-08-11 · 约 8 分钟阅读
本节目标:学会读写 JSON(重点搞懂 orient 方向)、展平嵌套 JSON,并了解 HTML 表格、XML 和剪贴板这几种”非表格”数据的读取方法。
JSON:API 世界的通用语
JSON(JavaScript Object Notation)是一种轻量文本格式,网页接口(API)返回的数据大多是它。它的基本结构只有两种:对象(花括号,键值对)和数组(方括号,列表)。
pandas 读写 JSON 用 read_json 和 to_json,难点不在函数本身,而在 JSON 的”长相”千变万化——同样的表格数据,可以用好几种结构表达。这就是 orient 参数存在的意义。
读取:read_json
从文件、URL 或字符串读取:
import pandas as pd
from io import StringIO
df = pd.read_json("数据.json")
df = pd.read_json("https://example.com/api/data.json")
df = pd.read_json(StringIO('{"a": {"0": 1, "1": 2}}'))
不指定 orient 时,pandas 按默认方向(columns)解析,期望 JSON 是”外层列名、内层行号”的结构。最常见的情况:数据源是数组套对象(每行一个对象),这是 web API 的标配:
json_str = '[{"姓名": "张三", "年龄": 25}, {"姓名": "李四", "年龄": 30}]'
df = pd.read_json(StringIO(json_str), orient="records")
print(df)
Note默认方向也能解析这种数组,但显式写
orient="records"意图更清楚,也能避免某些边界情况的误判。写成什么方向,读的时候就指定什么方向。
orient:六种方向一次看清
to_json 默认输出 columns 方向,read_json 的默认方向与之对应。拿这个 DataFrame 举例:
df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]},
index=["x", "y", "z"])
各方向输出的结构:
print(df.to_json(orient="columns")) # 列在外:{"A":{"x":1,...},"B":{"x":4,...}}
print(df.to_json(orient="index")) # 行在外:{"x":{"A":1,"B":4},...}
print(df.to_json(orient="records")) # 数组套对象:[{"A":1,"B":4},...]
print(df.to_json(orient="split")) # 拆三份:{"columns":[...],"index":[...],"data":[...]}
print(df.to_json(orient="values")) # 纯数组:[[1,4],[2,5],[3,6]]
逐个说适用场景:
columns:默认方向,列名做外层键,适合 pandas 之间的往返index:行标签做外层键,和columns正好反着records:数组套对象,最贴近 web API 习惯,前端 JS 直接能用split:索引、列名、数据分开存,信息最完整,往返无损values:只要数据,省掉所有标签,最省体积
还有一个 table 方向,按 JSON Table Schema 格式输出,会带上 dtype 等元数据,适合完整存档。
Warning往返读取时 orient 必须一致:
to_json(orient="records")写出的文件,read_json(..., orient="records")才能正确读回。方向不匹配时数据会错位或报错。
写出:to_json
to_json 不传路径时返回 JSON 字符串,传路径则写文件:
s = df.to_json() # 返回字符串
df.to_json("输出.json", orient="records") # 写文件
# 含日期的数据
df2 = pd.DataFrame({"日期": pd.to_datetime(["2026-01-01", "2026-01-02"])})
print(df2.to_json(date_format="iso")) # 日期写成 ISO 格式
date_format="iso" 把日期写成 2026-01-01T00:00:00.000 这种可读格式;默认 "epoch" 是时间戳数字。跨系统交换数据时 iso 更保险。
JSONL:一行一个 JSON
JSONL(JSON Lines)格式每行是一个独立的 JSON 对象,日志文件、流式数据常用:
jsonl = '{"a": 1, "b": 2}\n{"a": 3, "b": 4}\n'
df = pd.read_json(StringIO(jsonl), lines=True)
print(df)
df.to_json("输出.jsonl", orient="records", lines=True)
读写都要配 orient="records" + lines=True。大文件还能配 chunksize 分块读:
with pd.read_json("大文件.jsonl", lines=True, chunksize=1000) as reader:
for chunk in reader:
print(chunk.shape) # 每次只读 1000 行
嵌套 JSON:json_normalize 展平
真实世界的 JSON 常常是嵌套的——对象套对象、对象套数组。read_json 只能解析扁平结构,嵌套的要用 json_normalize 展平。
先看对象嵌套:
data = [
{"id": 1, "name": {"first": "Coleen", "last": "Volk"}},
{"id": 2, "name": {"first": "Mark", "last": "Regner"}},
]
df = pd.json_normalize(data)
print(df) # name 展开成 name.first 和 name.last 两列
再看数组嵌套(学生列表挂在班级对象下),用 record_path 指定要展开的字段,meta 保留外围字段:
data = {
"school_name": "ABC 小学",
"class": "一年级",
"students": [
{"id": "A001", "name": "Tom", "math": 60},
{"id": "A002", "name": "James", "math": 89},
],
}
df = pd.json_normalize(data, record_path="students",
meta=["school_name", "class"])
print(df) # 每个学生一行,班级信息重复填充
max_level 参数控制嵌套展开的深度,默认全展开。
Tip实战口诀:扁平 JSON 用
read_json,嵌套 JSON 先json.loads解析成 Python 对象,再交给pd.json_normalize。
类型推断的小脾气
read_json 自动转类型时有几个”怪癖”,读回数据时要留意:
- 全是整数值的浮点列会被转成整数(
1.变成1) - 布尔列可能被转成整数(
True变成1) - 索引重建后的顺序不保证和序列化时一致
想保持原样,用 dtype 显式指定:
df = pd.read_json(StringIO(json_str), dtype={"price": "float64"})
重要数据的往返,建议显式配 dtype,别依赖自动推断。
HTML 表格:read_html
网页里的 <table> 标签可以直接变成 DataFrame:
tables = pd.read_html("https://example.com/table.html")
df = tables[0] # 返回列表,网页有几个表格就有几个元素
常用筛选参数:
tables = pd.read_html("page.html",
match="销售额", # 只留包含该文字的表格
attrs={"id": "data-table"}, # 按 HTML 属性定位
header=0, # 指定表头行
index_col=0) # 指定索引列
反向操作是 to_html,把 DataFrame 渲染成 HTML 表格字符串,方便嵌入网页或邮件:
html = df.to_html()
read_html 需要解析器:lxml,或 beautifulsoup4 + html5lib,用前先 pip install lxml beautifulsoup4。另外请遵守目标网站的 robots.txt,别高频抓取。
XML:read_xml
XML 和 HTML 同源,read_xml 把它解析成 DataFrame:
xml = """<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book category="cooking">
<title>Everyday Italian</title>
<author>Giada</author>
<year>2005</year>
</book>
<book category="children">
<title>Harry Potter</title>
<author>Rowling</author>
<year>2005</year>
</book>
</bookstore>"""
df = pd.read_xml(StringIO(xml))
print(df) # 每个 book 节点一行,子元素变列,属性也变列
XML 的数据可能藏在属性里(category="cooking"),也可能在子元素里。默认两者都读成列;只想取一种可以这样:
df = pd.read_xml(StringIO(xml), elems_only=True) # 只要子元素
df = pd.read_xml(StringIO(xml), attrs_only=True) # 只要属性
复杂文档用 xpath 选节点,比如 xpath="//book[year=2005]"。read_xml 需要 lxml 库,写 XML 用 to_xml。
剪贴板:read_clipboard
最后来一个轻量技巧:剪贴板。在 Excel 里复制一块区域,直接变成 DataFrame:
df = pd.read_clipboard() # 粘贴剪贴板内容
反向操作,把 DataFrame 复制到剪贴板,去 Excel 里粘贴:
df.to_clipboard()
处理临时数据、在 Excel 和 pandas 之间搬运小数据时,比存文件快得多。Linux 上需要 xclip 或 xsel 支持。
小结
read_json/to_json的核心是orient:records 贴近 API,split 往返无损,columns 是默认- 读写方向必须一致;日期用
date_format="iso" - JSONL 用
lines=True,大文件配chunksize分块 - 嵌套 JSON 用
pd.json_normalize,record_path展数组、meta保留外围字段 - 读回 JSON 注意类型怪癖:浮点转整数、布尔转整数,用
dtype固定 - HTML 表格
read_html返回列表,XML 用read_xml,都要装解析库 read_clipboard/to_clipboard在 Excel 和 pandas 之间零成本搬运
下一节把数据写进数据库:SQL 读写。