如何通过字典传递lxml/etree提取规则至函数?或利用pandas.read_xml的xpath参数?
问题描述
我需要从分层结构的XML文件中提取数据并导入至DataFrame。由于XML存在层级,若不编写XSLT脚本进行扁平化处理,无法直接使用便捷的pandas.read_xml()函数。考虑到XSLT会降低代码可读性,目前我采用lxml包的objectify()函数实现提取,相关代码如下:
示例XML结构
<validElementaryExchanges majorRelease="3" minorRelease="0" majorRevision="0" minorRevision="44038" xmlns="http://www.EcoInvent.org/EcoSpold02"> <elementaryExchange id="38a622c6-f086-4763-a952-7c6b3b1c42ba" unitId="487df68b-4994-4027-8fdc-a4dc298257b7" formula="C4H10O2" casNumber="000110-63-4"> <name xml:lang="en">1,4-Butanediol</name> <unitName xml:lang="en">kg</unitName> <compartment subcompartmentId="e8d7772c-55ca-4dd7-b605-fee5ae764578"> <compartment xml:lang="en">air</compartment> <subcompartment xml:lang="en">urban air close to ground</subcompartment> </compartment> <synonym xml:lang="en">butane-1,4-diol</synonym> <synonym xml:lang="en">Butylene glycol</synonym> <property propertyId="6393c14b-db78-445d-a47b-c0cb866a1b25" amount="0" /> <property propertyId="6d9e1462-80e3-4f10-b3f4-71febd6f1168" amount="0" /> <property propertyId="a9358458-9724-4f03-b622-106eda248916" amount="0" /> <property propertyId="c74c3729-e577-4081-b572-a283d2561a75" amount="0.533098393070742" /> <property propertyId="3a0af1d6-04c3-41c6-a3da-92c4f61e0eaa" amount="1" /> <property propertyId="67f102e2-9cb6-4d20-aa16-bf74d8a03326" amount="1" /> </elementaryExchange> </validElementaryExchanges>
当前实现代码
def extract_metadata(node): return { 'id': node.get('id'), 'name': node.name.text, 'comp': node.compartment.compartment.text, 'subcomp': node.compartment.subcompartment.text } root = lxml.objectify.parse(file).getroot() df = pd.DataFrame([extract_metadata(i) for i in root.iterchildren()])
我希望将提取逻辑封装为可接收提取规则字典的函数,示例设想如下:
dict_data_extraction = { 'id': "node.get('id')", 'name': "node.name.text", 'comp': "node.compartment.compartment.text", 'subcomp': "node.compartment.subcompartment.text" } df = extract_data(file, dict_data_extraction)
请问传递etree/lxml提取规则至函数的最佳方式是什么?或者是否有简洁的方法利用pandas.read_xml()函数的xpath参数实现该需求?
解决方案
一、封装可接收提取规则的安全函数
不建议用字符串形式传递提取逻辑(存在注入风险且调试困难),推荐用XPath表达式或者可调用对象作为规则,更安全且可读性更强。
方案1:使用XPath表达式规则字典
利用lxml的XPath支持,结合XML命名空间(你的XML包含xmlns="http://www.EcoInvent.org/EcoSpold02")实现:
import lxml.etree as ET import pandas as pd def extract_data(file_path, extraction_rules, namespaces=None): tree = ET.parse(file_path) root = tree.getroot() # 默认使用示例中的命名空间 ns = namespaces or {'es': 'http://www.EcoInvent.org/EcoSpold02'} rows = [] # 遍历所有目标节点 for node in root.xpath('es:elementaryExchange', namespaces=ns): row = {} for col_name, xpath_expr in extraction_rules.items(): # 执行XPath查询,取第一个匹配结果 result = node.xpath(xpath_expr, namespaces=ns) row[col_name] = result[0] if result else None rows.append(row) return pd.DataFrame(rows) # 定义提取规则:键为DataFrame列名,值为相对节点的XPath表达式 extraction_rules = { 'id': '@id', 'name': 'es:name/text()', 'comp': 'es:compartment/es:compartment/text()', 'subcomp': 'es:compartment/es:subcompartment/text()' } df = extract_data('your_file.xml', extraction_rules)
方案2:使用可调用对象规则字典
如果更习惯lxml objectify的语法,可用lambda函数作为规则值:
from lxml import objectify import pandas as pd def extract_data(file_path, extraction_rules): root = objectify.parse(file_path).getroot() rows = [] for node in root.iterchildren(): row = {} for col_name, extract_func in extraction_rules.items(): row[col_name] = extract_func(node) rows.append(row) return pd.DataFrame(rows) # 定义提取规则:键为列名,值为接收node参数的lambda函数 extraction_rules = { 'id': lambda n: n.get('id'), 'name': lambda n: n.name.text, 'comp': lambda n: n.compartment.compartment.text, 'subcomp': lambda n: n.compartment.subcompartment.text } df = extract_data('your_file.xml', extraction_rules)
二、直接使用pandas.read_xml实现
pandas.read_xml支持通过xpath参数指定目标节点,结合namespaces处理XML命名空间,无需XSLT即可扁平化提取数据:
import pandas as pd # 定义命名空间 namespaces = {'es': 'http://www.EcoInvent.org/EcoSpold02'} # 读取XML并提取字段 df = pd.read_xml( 'your_file.xml', xpath='//es:elementaryExchange', namespaces=namespaces, # 列名与对应XPath表达式(相对elementaryExchange节点) xpath_exprs={ 'id': '@id', 'name': 'es:name/text()', 'comp': 'es:compartment/es:compartment/text()', 'subcomp': 'es:compartment/es:subcompartment/text()' } )
这个方法最简洁,直接利用pandas内置功能,无需额外封装函数,同时避免了XSLT的复杂性。
内容的提问来源于stack exchange,提问作者Wasserwaage
相关产品推荐
相关产品推荐

