影刀RPA XML文件处理:解析与生成
署名:林焱
什么情况用什么
RPA流程中遇到XML格式的配置文件(如SVG、OFD发票、Sitemap),或者调用WebService接口返回XML格式数据。虽然JSON更流行,但很多老系统和企业级接口仍使用XML。
| 场景 | 推荐库 | 特点 |
|---|---|---|
| 简单XML解析 | xml.etree.ElementTree | 标准库,无需安装 |
| 复杂XML操作 | lxml | 支持XPath,功能强大 |
| XML生成 | xml.etree.ElementTree | 树形构建 |
怎么做
一:解析XML
importxml.etree.ElementTreeasET xml_string='''<?xml version="1.0" encoding="UTF-8"?> <orders> <order id="001"> <customer>张三</customer> <items> <item name="商品A" price="99.9" qty="2"/> <item name="商品B" price="199.0" qty="1"/> </items> <total>398.8</total> </order> <order id="002"> <customer>李四</customer> <items> <item name="商品C" price="50.0" qty="3"/> </items> <total>150.0</total> </order> </orders> '''# 解析XML字符串root=ET.fromstring(xml_string)# 遍历所有orderfororderinroot.findall("order"):order_id=order.get("id")# 属性customer=order.find("customer").text# 子元素文本total=order.find("total").textprint(f"订单号:{order_id}, 客户:{customer}, 总价:{total}")# 遍历items下的itemforiteminorder.findall("items/item"):name=item.get("name")price=item.get("price")qty=item.get("qty")print(f" 商品:{name}, 价格:{price}, 数量:{qty}")二:读取XML文件
拼多多店群自动化上架方案
importxml.etree.ElementTreeasET# 从文件读取tree=ET.parse(r"D:\config\settings.xml")root=tree.getroot()# root.tag → 根标签名# root.attrib → 根属性字典print(f"根标签:{root.tag}")# 递归遍历所有元素defprint_element(elem,level=0):indent=" "*level text=elem.text.strip()ifelem.textandelem.text.strip()else""attrs=" ".join(f'{k}="{v}"'fork,vinelem.attrib.items())iftext:print(f"{indent}<{elem.tag}{attrs}>{text}</{elem.tag}>")else:print(f"{indent}<{elem.tag}{attrs}>")forchildinelem:print_element(child,level+1)print_element(root)三:生成XML
importxml.etree.ElementTreeasET# 创建XML树root=ET.Element("report")root.set("date","2024-06-15")root.set("type","daily")# 添加子元素summary=ET.SubElement(root,"summary")summary.set("currency","CNY")ET.SubElement(summary,"total_orders").text="150"ET.SubElement(summary,"total_amount").text="45678.90"ET.SubElement(summary,"avg_amount").text="304.53"# 添加明细details=ET.SubElement(root,"details")orders=[{"id":"001","customer":"张三","amount":"199.0"},{"id":"002","customer":"李四","amount":"350.5"},]fororderinorders:order_elem=ET.SubElement(details,"order")order_elem.set("id",order["id"])ET.SubElement(order_elem,"customer").text=order["customer"]ET.SubElement(order_elem,"amount").text=order["amount"]# 生成XML字符串xml_str=ET.tostring(root,encoding="unicode")# 美化输出(标准库不支持缩进,Python 3.9+有ET.indent)ET.indent(root,space=" ",level=0)xml_str=ET.tostring(root,encoding="unicode")print(xml_str)# 写入文件tree=ET.ElementTree(root)tree.write(r"D:\报告\daily_report.xml",encoding="utf-8",xml_declaration=True)四:用XPath查找(lxml)
fromlxmlimportetree# 解析tree=etree.fromstring(xml_string.encode("utf-8"))# XPath查找# 所有订单的客户名customers=tree.xpath("//customer/text()")print(f"所有客户:{customers}")# 总价大于200的订单big_orders=tree.xpath("//order[total > 200]/@id")print(f"大额订单:{big_orders}")# 第一个订单的所有商品名items=tree.xpath("(//order)[1]/items/item/@name")print(f"商品名:{items}")# 带条件的查找:数量大于1的商品bulk_items=tree.xpath("//item[@qty > 1]/@name")print(f"多数量商品:{bulk_items}")完整流程:Sitemap解析采集
importxml.etree.ElementTreeasETimportrequests# yd_input: sitemap_urlsitemap_url=yd_input.get("sitemap_url","https://example.com/sitemap.xml")# 下载并解析Sitemapresp=requests.get(sitemap_url,timeout=30)root=ET.fromstring(resp.content)# 提取所有URLurls=[]# Sitemap命名空间ns={"ns":"http://www.sitemaps.org/schemas/sitemap/0.9"}forurl_eleminroot.findall("ns:url",ns):loc=url_elem.find("ns:loc",ns)lastmod=url_elem.find("ns:lastmod",ns)iflocisnotNone:url_info={"url":loc.text,"lastmod":lastmod.textiflastmodisnotNoneelse""}urls.append(url_info)print(f"共找到{len(urls)}个URL")# 去重seen=set()unique_urls=[]foruinurls:ifu["url"]notinseen:seen.add(u["url"])unique_urls.append(u)print(f"去重后{len(unique_urls)}个URL")yd_output={"status":"ok","urls":unique_urls,"count":len(unique_urls)}有什么坑
坑一:XML命名空间导致findall找不到元素
现象:XML有xmlns命名空间声明,findall("order")返回空列表。
原因:带命名空间的XML,元素名需要加上命名空间前缀才能匹配。
解决:
importxml.etree.ElementTreeasET# XML有命名空间:# <orders xmlns="http://example.com/orders"># <order id="001">...</order># </orders># 错误:直接用标签名找不到# orders = root.findall("order") ❌ 返回空# 正确1:用完整命名空间ns={"d":"http://example.com/orders"}orders=root.findall("d:order",ns)# 正确2:用通配符orders=root.findall(".//{http://example.com/orders}order")# 正确3:去掉命名空间后再解析foreleminroot.iter():if"}"inelem.tag:elem.tag=elem.tag.split("}")[1]# 去掉命名空间部分# 现在可以直接用标签名orders=root.findall("order")坑二:XML中的特殊字符
现象:生成XML时,文本包含<、>、&等字符,导致解析报错。
原因:XML中<、>、&、"、'是特殊字符,必须转义。
解决:用ElementTree自动转义,或手动处理:
TEMU店群如何管理运营?
importxml.etree.ElementTreeasETfromxml.sax.saxutilsimportescape,unescape# ElementTree自动转义elem=ET.Element("note")elem.text="价格 < 100 & 库存 > 0"xml_str=ET.tostring(elem,encoding="unicode")# 自动转义为: <note>价格 < 100 & 库存 > 0</note># 手动转义text="a < b & c > d"escaped=escape(text)# a < b & c > d# 手动反转义original=unescape(escaped)# a < b & c > d坑三:解析XML时编码错误
现象:解析包含中文的XML文件报UnicodeDecodeError或ParseError。
原因:文件实际编码与XML声明中的编码不一致,或者文件有BOM头。
解决:
importxml.etree.ElementTreeasET# 方案1:以bytes方式读取,让ET自动检测编码withopen(r"D:\config\中文.xml","rb")asf:content=f.read()root=ET.fromstring(content)# ET根据XML声明自动解码# 方案2:先去掉BOM头withopen(r"D:\config\中文.xml","rb")asf:content=f.read()ifcontent[:3]==b'\xef\xbb\xbf':# UTF-8 BOMcontent=content[3:]root=ET.fromstring(content)# 方案3:用lxml,更健壮fromlxmlimportetree tree=etree.parse(r"D:\config\中文.xml")root=tree.getroot()坑四:生成的XML没有缩进和换行
现象:用ElementTree生成的XML是一行,没有缩进,难以阅读。
原因:ElementTree默认不格式化输出。
解决:
importxml.etree.ElementTreeasET# Python 3.9+:用ET.indentroot=ET.Element("root")ET.SubElement(root,"child").text="hello"ET.indent(root,space=" ")# 缩进2空格xml_str=ET.tostring(root,encoding="unicode")print(xml_str)# <root># <child>hello</child># </root># Python 3.8及以下:用minidom美化importxml.dom.minidom xml_str=ET.tostring(root,encoding="unicode")dom=xml.dom.minidom.parseString(xml_str)pretty=dom.toprettyxml(indent=" ")print(pretty)