推荐用 ET.fromstring(r.content) 直接解析 bytes,它自动识别 XML 声明中的 encoding;若声明缺失或冲突,需手动 decode;大文件用 iterparse 流式处理。

requests.get() 直接获取 XML 内容没问题,但响应体默认是 bytes
Python requests 发起 GET 请求拿到 XML,本质和拿 HTML、JSON 没区别——response.content 是原始字节流,response.text 是解码后的字符串。很多人卡在第一步:用 response.text 解析时报编码错误,比如 UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff。
- XML 声明里常带编码(如
<?xml version="1.0" encoding="GBK"?>),但requests不会自动按这个解码,它只看 HTTPContent-Type头里的charset - 如果服务端没返回
charset,response.text默认用utf-8解码,而实际是GB2312或ISO-8859-1,就直接崩 - 稳妥做法是先用
response.content拿 bytes,再手动按 XML 声明或业务约定解码;或者交给xml.etree.ElementTree自己处理(它能识别声明)
用 xml.etree.ElementTree.fromstring() 解析 raw bytes 最省心
别先 decode 再 parse,直接把 response.content 丢给 ET.fromstring() —— 它内部会自动检测 XML 声明里的 encoding 属性,并正确解码。这是最常见也最不容易翻车的方式。
-
ET.fromstring(response.content)支持 UTF-8、UTF-16、GBK 等主流编码,只要 XML 声明写对了就能认出来 - 如果 XML 没声明 encoding(不规范但存在),且内容含非 ASCII 字符,
fromstring()可能抛ParseError;此时得先猜编码,用response.content.decode('gbk')得到字符串再传入 - 注意:不要用
ET.parse()直接传 URL,它不支持网络路径,只能读本地文件或 file-like object
import requests import xml.etree.ElementTree as ET <p>r = requests.get('<a href="https://www.php.cn/link/73693853a57a48e11cdea2a77e88a501">https://www.php.cn/link/73693853a57a48e11cdea2a77e88a501</a>') r.raise_for_status() # 别忘了检查 HTTP 状态码 root = ET.fromstring(r.content) # ✅ 推荐:bytes 直接喂给 fromstring
遇到乱码或解析失败,优先检查 Content-Type 和 XML 声明是否冲突
服务器返回的 Content-Type 头(如 text/xml; charset=ISO-8859-1)和 XML 文件开头的 encoding 声明(如 encoding="UTF-8")打架,是 XML 解析出问题的头号原因。
- 用
print(r.headers.get('content-type'))和print(r.content[:100])快速确认两者是否一致 - 若不一致,以 XML 声明为准(它是 XML 规范强制要求的),忽略
Content-Type里的charset - 某些老旧系统返回
Content-Type: text/xml却不带charset,还用 GBK 编码——这时候必须手动 decode:r.content.decode('gbk')
大 XML 文件别用 .content 全部加载进内存
如果 XML 超过几 MB,response.content 会一次性把整个响应体读进内存,容易 OOM。这时得用流式解析,比如 xml.etree.ElementTree.iterparse() 配合 response.raw。
立即学习 “Python 免费学习笔记(深入)”;
-
response.raw是未解码的 socket 流,需设stream=True才可用:requests.get(url, stream=True) -
iterparse()边读边解析,适合处理<item>这类重复子节点,不用一次性载入整棵树 - 记得调用
response.raw.close()或用with requests.get(……, stream=True) as r:确保连接释放
r = requests.get('https://example.com/big.xml', stream=True) r.raise_for_status() for event, elem in ET.iterparse(r.raw, events=('start', 'end')): if event == 'end' and elem.tag == 'record': print(elem.find('title').text) elem.clear() # ⚠️ 关键:清空已处理节点,防内存堆积
XML 的坑不在“怎么下”,而在“怎么不崩”。真正麻烦的永远是编码混乱、声明缺失、服务端瞎配 Content-Type——这些没法靠换库解决,得一层层 inspect 响应头和原始字节。