XML本身只是带标签的文本,图片查看器不会把它当作图像来解码。想要用Python把XML变成图片,核心是先判断XML的性质:如果它本身是SVG矢量图,可以走cairosvg渲染;如果它只是普通结构化数据,就需要用ElementTree把节点解析出来,再通过Pillow或matplotlib生成位图。下面围绕这两种情况展开,给出可运行的实现代码和常见坑点。

一、先判断XML属于普通数据还是SVG矢量图
拿到一个XML文件后,不要急着写绘图代码,第一步要先看根元素的命名空间和本地标签名。普通数据型XML的根节点通常是 <root>、<config>、<items> 这类自定义名称,而SVG格式的XML根节点本地名固定是 svg,并且一般带有命名空间,例如 {http://www.w3.org/2000/svg}svg。通过这个特征可以快速判断后续走哪条处理路径。
使用Python内置的ElementTree解析根节点即可完成判断。下面的函数读取XML文件,统一提取根元素去掉命名空间后的本地名,如果判断为 svg 就直接返回 svg,否则返回 data。这样后续逻辑会清晰很多。
import xml.etree.ElementTree as ET
def detect_xml_kind(path):
tree = ET.parse(path)
root = tree.getroot()
tag = root.tag
# 去掉命名空间前缀
if '}' in tag:
tag = tag.split('}', 1)[1]
if tag.lower() == 'svg':
return 'svg'
return 'data'
print(detect_xml_kind('sample.xml'))
这个判断逻辑非常轻量,却决定了后续依赖库的选择。如果误把普通数据XML当成SVG交给cairosvg,通常会抛出解析错误;同样,把真正的SVG拿来逐节点用Pillow画,也会丢掉矢量缩放和路径信息,最终效果很差。因此类型判断不是多余的步骤,而是整个转换流程的第一道分水岭。
二、使用Pillow将普通XML数据绘制成图片
普通数据XML最常见的可视化形态是柱状图、表格或树状目录。Pillow 的优势在于你可以完全控制每一个像素的位置,绘制出来的图片布局很稳定,适合生成固定尺寸的报表配图。缺点也很明显:文字换行、居中、中文字体以及不同数据量下的缩放都要手工处理。对于数据量在10条以内的场景,Pillow手绘柱状图是一个很直观的方案。
以一个销售数据XML为例,假设文件 sales.xml 中包含根节点 sales,下面有多个 product 节点,每个节点带 name 属性,文本内容是销量数字。解析后将名称和数值组成列表,按数值降序排序,再创建画布绘制矩形柱子和对应文字。绘图时必须通过 ImageFont.truetype() 指定中文字体,否则中文会显示成方框。
import xml.etree.ElementTree as ET
from PIL import Image, ImageDraw, ImageFont
tree = ET.parse('sales.xml')
root = tree.getroot()
items = []
for node in root.findall('product'):
name = node.get('name', '未知')
value = int(node.text or 0)
items.append((name, value))
items.sort(key=lambda x: x[1], reverse=True)
width, height = 900, 600
img = Image.new('RGB', (width, height), 'white')
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(r"C:\Windows\Fonts\msyh.ttc", 18)
title_font = ImageFont.truetype(r"C:\Windows\Fonts\msyh.ttc", 26)
draw.text((350, 20), '销量统计', font=title_font, fill='#333333')
max_value = max(v for _, v in items) if items else 1
bar_width = 80
gap = 60
left = 100
top = 420
for i, (name, value) in enumerate(items[:8]):
x = left + i * (bar_width + gap)
bar_height = int(value / max_value * 300)
y = top - bar_height
draw.rectangle((x, y, x + bar_width, top), fill='#4A90D9')
draw.text((x - 5, top + 10), name, font=font, fill='#222222')
draw.text((x - 5, y - 25), str(value), font=font, fill='#222222')
img.save('chart.png')
上述代码只处理了前8条数据,因为画布宽度有限。如果XML里有几十条记录,仍然按同样间距绘制,柱子会挤在一起或者超出图片边界。此时可以改为横向条形图、缩小字号,或者根据数据量动态计算画布宽度。另一个容易忽略的问题是 prodnode.text 可能为 None,用 node.text or 0 可以避免类型转换异常。
对于表格类XML数据,Pillow同样适用。可以先计算表头宽度和行高,再逐行绘制矩形边框和文字。不过手写表格排版代码量会明显增加,如果只是为了快速看到数据形状,并且数据适合统计图,matplotlib 是更省力的选择。
三、SVG格式XML直接用cairosvg转成PNG
当检测到XML本身是SVG时,不需要再逐节点解析绘图。SVG的本质就是XML描述的矢量图形,里面的路径、填充色、文字元素都可以交给专门的渲染引擎处理。cairosvg 这个库依赖 cairocffi,能够把SVG源码或SVG文件渲染为PNG、PDF等格式。安装命令为 pip install cairosvg,使用起来非常简单。
下面的代码从文件读取SVG内容,指定输出宽度、高度和背景色,直接生成PNG图片。如果SVG中包含中文文字,需要确保系统里已经安装了支持中文的字体,否则渲染结果依然会出现空白或乱码。这一点和Pillow绘制中文时的问题本质相同。
import cairosvg
svg_path = 'chart.svg'
png_path = 'chart.png'
with open(svg_path, 'r', encoding='utf-8') as f:
svg_text = f.read()
cairosvg.svg2png(
bytestring=svg_text.encode('utf-8'),
write_to=png_path,
output_width=800,
output_height=600,
background_color='#FFFFFF'
)
print('saved', png_path)
如果手里的XML不是标准SVG,但里面保存的是数值型数据,并且你想快速生成折线图、散点图或饼图,matplotlib是更合适的工具。它最大的好处是坐标轴、图例和刻度都由库自动处理,不用像Pillow那样手工计算。把它们放在一起比较:Pillow灵活但写起来琐碎,matplotlib绘图快但样式调整相对受限,cairosvg则对SVG输入保真度最高。
四、中文显示、换行与输出尺寸的落地细节
中文字体是XML转图片时绕不开的问题。Pillow默认字体不支持中文,必须显式加载中文字体文件。Windows下常用 C:\Windows\Fonts\msyh.ttc,macOS下可以用 /System/Library/Fonts/PingFang.ttc,Linux发行版通常提供文泉驿或Noto字体。为了让代码可移植,也可以把字体文件复制到项目目录中,通过相对路径加载,避免依赖不同操作系统的绝对路径。
绘制长文本时,不能一次性把整段文字交给 draw.text(),否则内容会超出图片右边界。需要根据目标宽度逐字累加,计算当前行的像素宽度,超过限制就换行。下面是一个简化版的中文换行绘制函数,它利用 textbbox 获取文本实际宽度,适用于名称较长或备注字段较多的XML内容。
def draw_wrapped_text(draw, text, x, y, max_width, font, fill):
current_line = ''
for ch in text:
test_line = current_line + ch
bbox = draw.textbbox((0, 0), test_line, font=font)
if bbox[2] - bbox[0] > max_width:
draw.text((x, y), current_line, font=font, fill=fill)
current_line = ch
y += 26
else:
current_line = test_line
if current_line:
draw.text((x, y), current_line, font=font, fill=fill)
输出尺寸也和最终用途有关。屏幕展示时72 DPI已经足够,但用于打印或导出到文档时,建议设置更高分辨率。Pillow保存图片时可以传入 dpi=(300, 300),不过需要同步增大字号和画布尺寸,否则图片看起来会发虚;cairosvg则可以通过 scale 参数放大渲染,比单纯拉伸PNG清晰很多。
如果有多个XML文件要批量转换,可以用 pathlib.Path.glob('*.xml') 遍历目录,例如读取 C:\data\xml 下的所有XML,输出图片到 C:\data\png。批量处理时建议把SVG和普通数据XML分开,因为两者依赖的库和处理逻辑不同,混在一起很容易因为单个文件格式异常导致整个任务失败。整体来看,判断类型、选择合适的渲染方案、处理中文字体和输出尺寸,这三步做好后,XML转图片的流程就会稳定很多。