通过HTTP GET获取XML文件是接口对接、配置同步和数据采集中的常见需求。Python标准库的urllib虽然也能完成请求,但requests提供了更友好的API,自动处理连接池、编码探测和异常分类。本文围绕requests.get展开,覆盖从发起请求到解析XML再到流式下载的完整链路,并给出可直接运行的示例。

使用requests.get发起GET请求并检查响应
最基础的下载方式只有几行代码。先调用requests.get传入目标URL,服务器返回的Response对象中包含了状态码、响应头和响应体。拿到响应后不要急着读取正文,应该先调用raise_for_status检查HTTP状态码,如果返回404或500会直接抛出HTTPError,避免后续解析错误。
import requests
url = "https://ipipp.com/data/config.xml"
response = requests.get(url, timeout=10)
response.raise_for_status()
print(response.status_code)
print(response.headers.get("Content-Type"))
xml_content = response.text
print(xml_content[:200])
代码中的timeout参数指定了连接和读取的超时时间,单位是秒。如果不设置,requests会一直等待服务器响应,可能导致线程被长时间阻塞。另一个值得关注的点是响应头的Content-Type字段。正常的XML接口通常返回application/xml或text/xml,但也有服务会错误地返回text/html甚至application/octet-stream。因此不要完全依赖响应头判断内容类型,如果请求的URL以.xml结尾或者接口文档明确返回XML,可以继续处理。
需要自定义请求头时,可以通过headers参数传入字典。例如某些接口要求Accept头声明期望的响应格式,否则会返回HTML错误页。下面是一个带自定义头的示例:
import requests
url = "https://ipipp.com/api/metadata"
headers = {
"Accept": "application/xml, text/xml, */*",
"User-Agent": "python-xml-client/1.0"
}
response = requests.get(url, headers=headers, timeout=15)
response.raise_for_status()
print(response.text[:300])
发起GET请求时还可以通过params字典传递查询参数,requests会自动进行URL编码。如果接口需要认证,可使用auth参数或直接在请求头中加Authorization。这些能力让requests在处理不同XML数据源时都能保持简洁。
处理XML编码与解析常见问题
获取到响应文本后,最常见的坑是编码不一致。requests会根据响应头中的Content-Type来猜测编码,但服务器可能漏掉charset,或者声明错误。例如XML文件本身是UTF-8,响应头却写着ISO-8859-1,直接读response.text就会出现乱码。解决办法是手动指定response.encoding属性后再获取文本。
import requests url = "https://ipipp.com/data/utf8-config.xml" response = requests.get(url, timeout=10) response.raise_for_status() # 如果确认返回内容是UTF-8,手动覆盖编码 response.encoding = "utf-8" xml_text = response.text # 也可以根据XML声明动态判断 # 例如先取响应前200字节看是否包含 encoding="UTF-8"
如果不想手动指定,也可以直接使用response.content获取原始字节,再通过decode方法按实际编码解码。需要注意XML文件首部的声明<?xml version="1.0" encoding="UTF-8"?>,其中encoding属性才是解析器真正关心的编码信息。ElementTree可以直接接受字节流,它会自动根据XML声明处理编码,因此更推荐把response.content直接传给解析器,而不是先转成字符串。
解析XML时,标准库xml.etree.ElementTree足够应对大多数场景。以下示例展示如何遍历节点和提取属性:
import requests
import xml.etree.ElementTree as ET
url = "https://ipipp.com/api/items.xml"
response = requests.get(url, timeout=10)
response.raise_for_status()
root = ET.fromstring(response.content)
for item in root.findall("item"):
item_id = item.get("id")
name = item.findtext("name")
print(f"ID: {item_id}, Name: {name}")
上述代码中response.content是字节串,ET.fromstring会读取XML声明并正确解码。如果使用response.text传入字符串,且之前手动设置过response.encoding,也能工作,但多余一步转换。对于包含默认命名空间的XML,findall需要使用带命名空间的路径,例如{http://ipipp.com/ns}item。如果命名空间复杂,可以考虑使用lxml库,它对XPath和命名空间处理更完善。
另一个常见问题是XML内容中的实体与特殊字符。ElementTree在解析时会自动处理<、&等实体,但如果XML本身不合法,例如缺少闭合标签,就会抛出ParseError。因此解析部分应当放在try块中,并记录原始响应内容方便排查。
增强下载健壮性:超时、重试与流式处理
生产环境中网络波动不可避免,一次简单的GET请求可能在DNS解析、TCP握手或读取响应体时失败。requests抛出的常见异常包括Timeout、ConnectionError和HTTPError。如果直接让异常向上传递,任务可能中断。更稳妥的做法是在调用处捕获这些异常,并根据需要重试一定次数。
import requests
import time
def download_xml(url, retries=3, timeout=10):
last_exc = None
for attempt in range(retries):
try:
response = requests.get(url, timeout=timeout)
response.raise_for_status()
return response
except (requests.Timeout, requests.ConnectionError, requests.HTTPError) as exc:
last_exc = exc
print(f"第{attempt + 1}次请求失败: {exc}")
time.sleep(2 ** attempt) # 简单指数退避
raise last_exc
response = download_xml("https://ipipp.com/data/maybe-fail.xml")
print(response.text[:300])
上面的重试逻辑使用了指数退避,第一次失败等待2秒,第二次等待4秒,避免短时间内密集重试造成服务器压力。实际项目中还可以加入只对幂等GET请求重试的判断,但这里的下载场景本身是幂等的,重试不会产生副作用。如果服务器返回503或429状态码,raise_for_status会触发HTTPError,同样进入重试流程。
当XML文件非常大,比如几十MB甚至上百MB,直接使用response.text会把整个文件载入内存,可能导致内存占用过高。此时应该使用流式下载,开启stream=True,并按块写入磁盘。这种方式的额外好处是可以在下载过程中暂停或取消。
import requests
url = "https://ipipp.com/large/dataset.xml"
output_path = "dataset.xml"
with requests.get(url, stream=True, timeout=30) as response:
response.raise_for_status()
with open(output_path, "wb") as file:
for chunk in response.iter_content(chunk_size=8192):
file.write(chunk)
print(f"文件已保存至 {output_path}")
iter_content的chunk_size决定了每次读取的字节数,一般设为8192或16384即可。流式下载时不能直接访问response.text,因为文本属性会一次性读取全部内容。保存完成后可以再按需解析文件,例如使用ET.parse(output_path)进行增量处理。如果服务器不支持断点续传,也可以先下载到临时文件,校验完整后再重命名。
还有一点需要留意:下载过程中要检查HTTP状态码。在流式模式下,raise_for_status依然有效,但必须在开始迭代iter_content之前调用。否则可能已经写入了一部分错误页面的内容。处理大文件时建议先检查Content-Length头,与磁盘剩余空间比较,避免写到一半磁盘空间不足。
总结与选型建议
通过requests下载XML文件的核心流程可以概括为:发起GET请求、检查响应状态、处理编码、解析XML、必要时流式落盘。小型XML文件可以直接使用response.text或response.content解析,大型文件则优先使用stream=True配合iter_content。解析库方面,简单结构用标准库ElementTree足够,涉及复杂命名空间或XPath的话再引入lxml。
实际应用时还需要考虑安全性和稳定性。例如对返回内容做长度限制,防止恶意或异常响应占用过多内存;对敏感URL使用HTTPS;在日志中避免打印完整的认证信息。掌握这些细节后,XML文件下载就能稳定接入到数据管道中。
Python requestsXML下载HTTP GET修改时间:2026-10-02 01:07:22