Python处理XML与JSON的库详解
一、JSON处理:json标准库
工作机制与原理
json模块使用基于事件的流式解析器,在解析时将JSON文本转换为Python原生数据结构:
- 词法分析:将字符流分解为token(字符串、数字、布尔值等)
- 语法分析:根据JSON语法规则构建抽象语法树
- 对象映射:将JSON对象映射为Python字典,数组映射为列表
核心用法与示例
import json
# ============ 基础序列化/反序列化 ============
data = {
"name": "张三",
"age": 30,
"skills": ["Python", "Java"],
"married": False,
"address": {
"city": "北京",
"zipcode": "100000"
}
}
# 序列化为JSON字符串
json_str = json.dumps(data, ensure_ascii=False, indent=2)
print(f"JSON字符串:\n{json_str}")
# 反序列化
parsed_data = json.loads(json_str)
print(f"反序列化后类型: {type(parsed_data)}") # <class 'dict'>
# ============ 文件操作 ============
# 写入文件
with open('data.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# 读取文件
with open('data.json', 'r', encoding='utf-8') as f:
loaded_data = json.load(f)
print(f"从文件加载: {loaded_data['name']}")
# ============ 高级功能 ============
# 自定义序列化(处理日期对象)
from datetime import datetime
class Person:
def __init__(self, name, birth_date):
self.name = name
self.birth_date = birth_date
def custom_serializer(obj):
if isinstance(obj, datetime):
return obj.isoformat()
elif isinstance(obj, Person):
return {"name": obj.name, "birth_date": obj.birth_date}
raise TypeError(f"无法序列化 {type(obj)}")
person = Person("李四", datetime(1990, 5, 15))
json_str = json.dumps(person.__dict__, default=custom_serializer, ensure_ascii=False)
print(f"自定义序列化: {json_str}")
# 使用自定义解码器
class CustomDecoder(json.JSONDecoder):
def decode(self, s):
result = super().decode(s)
# 对解析结果进行后处理
if isinstance(result, dict) and 'birth_date' in result:
result['birth_date'] = datetime.fromisoformat(result['birth_date'])
return result
json_with_date = '{"name": "王五", "birth_date": "1985-08-20T10:30:00"}'
decoded = json.loads(json_with_date, cls=CustomDecoder)
print(f"自定义解码后类型: {type(decoded['birth_date'])}")
# ============ 性能优化 ============
import json
import ujson # 第三方高性能库,需安装
# 比较性能
import time
large_data = [{"id": i, "value": f"item_{i}"} for i in range(10000)]
# 标准json库
start = time.time()
json_str = json.dumps(large_data)
standard_time = time.time() - start
# ujson(如果已安装)
try:
import ujson
start = time.time()
json_str = ujson.dumps(large_data)
ujson_time = time.time() - start
print(f"标准json时间: {standard_time:.3f}s, ujson时间: {ujson_time:.3f}s")
except ImportError:
pass
JSON库重要特性
- 编码控制:
· ensure_ascii=False:允许非ASCII字符(如中文)直接输出
· indent参数:美化输出,便于阅读
· sort_keys=True:按键排序,确保输出稳定 - 错误处理:
try:
# 解析可能无效的JSON
data = json.loads('{"invalid": json}')
except json.JSONDecodeError as e:
print(f"JSON解析错误: {e.msg}, 位置: {e.pos}, 行: {e.lineno}, 列: {e.colno}")
二、XML处理:xml.etree.ElementTree标准库
工作机制与原理
ElementTree使用DOM-like的内存树模型:
- 解析阶段:一次性读取整个XML文档,构建节点树
- 节点表示:每个XML元素对应一个Element对象
- 树形结构:父子关系和兄弟关系通过对象引用维护
核心用法与示例
import xml.etree.ElementTree as ET
from io import StringIO
# ============ 解析XML ============
xml_content = """
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book category="编程">
<title lang="zh">Python编程入门</title>
<author>张教授</author>
<year>2023</year>
<price>89.00</price>
</book>
<book category="小说">
<title lang="zh">三体</title>
<author>刘慈欣</author>
<year>2008</year>
<price>59.00</price>
</book>
</bookstore>
"""
# 从字符串解析
root = ET.fromstring(xml_content)
print(f"根元素标签: {root.tag}") # bookstore
# 从文件解析
with open('books.xml', 'w', encoding='utf-8') as f:
f.write(xml_content)
tree = ET.parse('books.xml')
root = tree.getroot()
# ============ 遍历与查询 ============
# 遍历所有子元素
print("\n=== 所有书籍 ===")
for book in root.findall('book'):
category = book.get('category')
title = book.find('title').text
author = book.find('author').text
print(f"类别: {category}, 书名: {title}, 作者: {author}")
# 使用XPath表达式(有限支持)
print("\n=== 编程类书籍 ===")
programming_books = root.findall(".//book[@category='编程']")
for book in programming_books:
print(f"书名: {book.find('title').text}")
# ============ 创建与修改XML ============
# 创建新元素
new_book = ET.Element('book')
new_book.set('category', '历史')
title_elem = ET.SubElement(new_book, 'title')
title_elem.text = '中国通史'
title_elem.set('lang', 'zh')
ET.SubElement(new_book, 'author').text = '钱穆'
ET.SubElement(new_book, 'year').text = '1950'
ET.SubElement(new_book, 'price').text = '75.00'
# 添加到根元素
root.append(new_book)
# 修改现有元素
for price_elem in root.iter('price'):
old_price = float(price_elem.text)
new_price = old_price * 1.1 # 涨价10%
price_elem.text = f"{new_price:.2f}"
price_elem.set('updated', 'true')
# ============ 命名空间处理 ============
namespace_xml = """
<root xmlns:bk="http://example.com/books">
<bk:book>
<bk:title>命名空间示例</bk:title>
</bk:book>
</root>
"""
ns_root = ET.fromstring(namespace_xml)
# 注册命名空间
ns = {'bk': 'http://example.com/books'}
titles = ns_root.findall('bk:book/bk:title', ns)
print(f"\n命名空间查询结果: {titles[0].text if titles else '未找到'}")
# ============ 序列化输出 ============
# 美化输出
from xml.dom import minidom
def prettify(elem):
"""将ElementTree转换为格式化的XML字符串"""
rough_string = ET.tostring(elem, encoding='unicode')
reparsed = minidom.parseString(rough_string)
return reparsed.toprettyxml(indent=" ")
print("\n=== 美化后的XML ===")
print(prettify(root)[:500]) # 只显示前500字符
# 写入文件
tree.write('updated_books.xml',
encoding='utf-8',
xml_declaration=True,
short_empty_elements=False)
# ============ 流式解析(处理大文件) ============
class BookHandler:
"""SAX-like事件处理器"""
def __init__(self):
self.current_data = ""
self.current_book = {}
self.books = []
def start_element(self, name, attrs):
if name == 'book':
self.current_book = {'category': attrs.get('category', '')}
def end_element(self, name):
if name == 'title':
self.current_book['title'] = self.current_data.strip()
elif name == 'book':
self.books.append(self.current_book)
self.current_data = ""
def char_data(self, data):
self.current_data += data
# 使用iterparse进行流式解析
print("\n=== 流式解析 ===")
context = ET.iterparse(StringIO(xml_content), events=('start', 'end'))
handler = BookHandler()
for event, elem in context:
if event == 'start':
handler.start_element(elem.tag, elem.attrib)
elif event == 'end':
handler.end_element(elem.tag)
# 及时清理已处理的元素,节省内存
if elem.tag == 'book':
elem.clear()
print(f"解析到的书籍数量: {len(handler.books)}")
for book in handler.books:
print(f" - {book.get('title')}")
XML库重要特性
- 解析模式对比:
# DOM模式(ElementTree默认):整个文档加载到内存
tree = ET.parse('large.xml') # 大文件可能内存不足
# SAX模式(流式解析):边读边处理
for event, elem in ET.iterparse('huge.xml'):
if elem.tag == 'record':
process_record(elem)
elem.clear() # 关键:及时释放内存
- XPath支持:
# ElementTree的XPath是子集,完整XPath需lxml
# 基本查询
root.findall(".//book[price>50]") # 价格>50的书
root.findall("book[last()]") # 最后一本书
- 安全注意事项:
# 防御XXE攻击(XML外部实体攻击)
parser = ET.XMLParser()
parser.entity = {} # 禁用外部实体
# 或使用defusedxml
from defusedxml.ElementTree import parse
safe_tree = parse('input.xml')
三、第三方库补充
- lxml(功能更强大的XML处理)
from lxml import etree
# 完整XPath 2.0支持
tree = etree.parse('books.xml')
# 复杂查询
expensive_books = tree.xpath("//book[price > 80]/title/text()")
print(f"高价书籍: {expensive_books}")
# XSLT转换
xslt = etree.XSLT(etree.parse('transform.xslt'))
result = xslt(tree)
print(result)
- orjson/ujson(高性能JSON)
pip install orjson ujson
import orjson
import datetime
# 自动处理更多数据类型
data = {
"date": datetime.datetime.now(),
"set": {1, 2, 3},
"bytes": b"hello"
}
# orjson直接支持更多Python类型
json_bytes = orjson.dumps(data)
print(orjson.loads(json_bytes))
四、工作机制深度对比
特性 JSON (json库) XML (ElementTree)
解析模型 流式解析(pull解析器) DOM树模型(可选的SAX式迭代)
内存使用 较低,可逐段解析 较高,默认全文档加载
速度 极快(C实现) 中等(纯Python实现)
数据映射 直接映射为Python字典/列表 映射为自定义Element对象
查询能力 无内置查询语言 有限XPath,需lxml获得完整支持
类型系统 简单类型(字符串、数字、布尔等) 所有值都是字符串,需手动转换
扩展性 通过hook函数自定义 通过子类化扩展
底层原理图示
JSON解析流程:
原始文本 → 词法分析器 → tokens流 → 语法分析器 → Python对象树
↓
状态机驱动
XML解析流程:
原始文本 → 解析器 → 元素事件流 → 构建Element树 → 内存中的节点树
↓
SAX事件或DOM构建
五、最佳实践建议
- 数据交换首选JSON:除非有特定需求(如文档标记、遗留系统)
- 大文件处理:
· JSON:使用ijson库进行流式解析
· XML:使用iterparse并及时clear()已处理元素 - 安全性:
· JSON:注意反序列化时代码注入风险(避免使用eval)
· XML:必须防御XXE攻击,使用defusedxml - 性能优化:
# JSON性能对比 import json import simplejson # C扩展版本 import orjson # Rust实现 # XML性能对比 import xml.etree.ElementTree as ET # 纯Python from lxml import etree # C实现,更快 - 配置文件场景:
# JSON配置(推荐)
config = json.load(open('config.json'))
# XML配置(复杂结构时)
tree = ET.parse('config.xml')
# 使用XPath查询配置项
# 考虑使用YAML或TOML作为替代
选择建议:日常API开发用json库,复杂XML处理用lxml,超大文件用流式解析,安全场景用defusedxml防护。
7729

被折叠的 条评论
为什么被折叠?



