在 Python 中生成 XML 的最快方法
Fastest way to generate XML in Python
我有两个 tables(从数据库中提取的 csv 文件),一个包含订单,第二个包含与订单 table 相关的项目。我需要从这两个文件构建一个 XML 文件以具有这种结构(由于可读性而简化):
<ORDERS>
<ORDER>
<ORDER_ID>11039515178</ORDER_ID>
<CUSTOMER_ID>394556458</CUSTOMER_ID>
<ITEMS>
<ITEM>
<PRODUCT_ID>1401817</PRODUCT_ID>
<AMOUNT>2</AMOUNT>
</ITEM>
<ITEM>
<PRODUCT_ID>1138857</PRODUCT_ID>
<AMOUNT>10</AMOUNT>
</ITEM>
<ITEM>
<PRODUCT_ID>4707595</PRODUCT_ID>
<AMOUNT>15</AMOUNT>
</ITEM>
</ITEMS>
</ORDER>
</ORDERS>
我使用此代码生成 XML 对象。它被简化为代码的主要结构,因此易于阅读:
import xml.etree.ElementTree as ET
import pandas as pd
order = pd.read_csv("order.csv", encoding='utf8', keep_default_na=False, dtype=str)
order_item = pd.read_csv("order_item.csv", encoding='utf8', keep_default_na=False, dtype=str)
# create XML
xml_orrder = ET.Element('ORDERS')
for row in order.itertuples():
item = ET.SubElement(xml_orrder, 'ORDER')
o_id = ET.Element('ORDER_ID')
o_id.text = row.order_id
item.append(o_id)
customer = ET.Element('CUSTOMER_ID')
customer.text = row.customer_id
item.append(customer)
order_item_id = order_item[order_item['order_id'] == row.order_id]
items = ET.SubElement(item, 'ITEMS')
for order_row in order_item_id.itertuples():
single_item = ET.SubElement(items, 'ITEM')
item_id = ET.Element('PRODUCT_ID')
item_id.text = order_row.product_id
single_item.append(item_id)
quantity = ET.Element('AMOUNT')
quantity.text = order_row.quantity_ordered
single_item.append(quantity)
我的问题是它运行的时间长得令人难以置信(每 1000 个订单大约需要 15 分钟,每个订单大约有 20 件商品)。我想我在这里做错了什么,但我找不到。有没有办法加快速度?使用另一个库?
我试过使用 itertuples() 而不是 iterrows()。但这不是很有帮助。
编辑:
我的数据是这样的:
order = pd.DataFrame({"order_id": range(1000000,1000010,1),
"customer_id": np.random.RandomState(0).randint(1000,2000,10)})
order_item = pd.DataFrame({"order_id": np.random.RandomState(0).randint(1000000,1000010,100),
"product_id": np.random.RandomState(0).randint(1000,2000,100),
"amount": np.random.RandomState(0).randint(1,100,100)})
order_item.sort_values(by="order_id",inplace=True,ignore_index=True)
我不确定你的数据是什么样的,所以我希望这对你有用,我花了几秒钟来处理大约 5000 行:
import pandas as pd
import lxml.etree as et
df_order = pd.read_csv("order.csv", encoding='utf8', keep_default_na=False, dtype=str)
df_order_item = pd.read_csv("order_items.csv", encoding='utf8', keep_default_na=False, dtype=str)
new_orders = df_order.merge(df_order_item, 'left', left_on='order_id', right_on='order_id')
orders = et.Element('ORDERS')
for order_id in new_orders['order_id'].unique():
rows = new_orders[new_orders['order_id'] == order_id]
customer_id = int(rows['customer_id'].unique())
order = et.SubElement(orders, 'ORDER')
o_id = et.SubElement(order, 'ORDER_ID')
o_id.text = order_id
c_id = et.SubElement(order, 'CUSTOMER_ID')
c_id.text = str(customer_id)
items = et.SubElement(order, 'ITEMS')
for product in rows.itertuples():
item = et.SubElement(items, 'ITEM')
p_id = et.SubElement(item, 'PRODUCT_ID')
p_id.text = product.product_id
amount = et.SubElement(item, 'AMOUNT')
amount.text = product.quantity_ordered
在编写 XML 或 HTML 时,以文本方式编写通常比增加构建 in-memory XML 文档的费用更快。您可以直接编写文件或使用模板语言(如 jinja 2)。以下是使用多行 f-strings 编写具有所需间距的文档的示例。由于 XML 不关心换行或漂亮的打印,我倾向于在没有额外间距的情况下写作。
代码有点难看,但恕我直言,所有模板都是如此。
import pandas as pd
order = pd.read_csv("order.csv", encoding='utf8', keep_default_na=False, dtype=str)
order_item = pd.read_csv("order_item.csv", encoding='utf8', keep_default_na=False, dtype=str)
with open("out.xml", "w") as outfile:
outfile.write("""\
<ORDERS>
""")
for row in order.itertuples():
outfile.write(
f"""\
<ORDER>
<ORDER_ID>{row.order_id}</ORDER_ID>
<CUSTOMER_ID>f{row.customer_id)</CUSOMTER_ID>
""")
outfile.write(f"""\
<ITEMS>
""")
order_item_id = order_item[order_item['order_id'] == row.order_id]
for order_row in order_item_id.itertuples():
outfile.write(f"""\
<ITEM>
<PRODUCT_ID>{order_row.product_id}</PRODUCT_ID>
<AMOUNT>{order_row.quantity_ordered}</AMOUNT>
</ITEM>
"""
outfile.write("""\
</ITEMS>
""")
outfile.write("""\
</ORDERS>
</ORDER>"""
显然 pandas to_xml
不处理这种层次结构。您可以将它的一部分直接写入 xml 文件并在 sub-df:
分组上使用 to_xml
df = order.merge(order_item, on='order_id')
with open('output.xml', 'w') as f:
f.write('<ORDERS>')
for (ord_id, cust_id), sub_df in df.groupby(['order_id', 'customer_id']):
f.write(f'\n<ORDER>\n<ORDER_ID>{ord_id}</ORDER_ID>\n<CUSTOMER_ID>{cust_id}</CUSTOMER_ID>\n')
f.write(sub_df.to_xml(root_name='ITEMS', row_name='ITEM', xml_declaration=False, elem_cols=['product_id', 'amount'], index=False))
f.write(f'\n</ORDER>')
f.write('\n</ORDERS>')
如果您发现任何性能改进,请告诉我们!
注意:您还可以使用 kwarg parser=
('lxml' 或 'etree')
选择 xml-parser
我已经尝试了上面提到的一些方法,但显着加快整个过程的一件事是使那些嵌套的 <ITEMS>
标签已经在数据库中。我们正在使用雪花,我使用 LISTAGG 按功能分组在 order_item table 上做了一个简单的分组:
CREATE OR REPLACE TABLE "wrk_order_item" AS
SELECT
"order_id",
'<ITEMS>' || LISTAGG('<ITEM><PRODUCT_ID>' || "product_id" || '</PRODUCT_ID>'
|| '<AMOUNT>' || "quantity_ordered" || '</AMOUNT>'
|| '<PRICE>' || "sell_price" || '</PRICE></ITEM>') || '</ITEMS>' AS "items"
FROM "ORDER_ITEM"
GROUP BY "order_id";
将其与订单 table 合并,并在 python 脚本中删除了订单 table 每次迭代中项目数据框的创建。两个代码(雪花,python)现在都在几秒钟内完成。
我有两个 tables(从数据库中提取的 csv 文件),一个包含订单,第二个包含与订单 table 相关的项目。我需要从这两个文件构建一个 XML 文件以具有这种结构(由于可读性而简化):
<ORDERS>
<ORDER>
<ORDER_ID>11039515178</ORDER_ID>
<CUSTOMER_ID>394556458</CUSTOMER_ID>
<ITEMS>
<ITEM>
<PRODUCT_ID>1401817</PRODUCT_ID>
<AMOUNT>2</AMOUNT>
</ITEM>
<ITEM>
<PRODUCT_ID>1138857</PRODUCT_ID>
<AMOUNT>10</AMOUNT>
</ITEM>
<ITEM>
<PRODUCT_ID>4707595</PRODUCT_ID>
<AMOUNT>15</AMOUNT>
</ITEM>
</ITEMS>
</ORDER>
</ORDERS>
我使用此代码生成 XML 对象。它被简化为代码的主要结构,因此易于阅读:
import xml.etree.ElementTree as ET
import pandas as pd
order = pd.read_csv("order.csv", encoding='utf8', keep_default_na=False, dtype=str)
order_item = pd.read_csv("order_item.csv", encoding='utf8', keep_default_na=False, dtype=str)
# create XML
xml_orrder = ET.Element('ORDERS')
for row in order.itertuples():
item = ET.SubElement(xml_orrder, 'ORDER')
o_id = ET.Element('ORDER_ID')
o_id.text = row.order_id
item.append(o_id)
customer = ET.Element('CUSTOMER_ID')
customer.text = row.customer_id
item.append(customer)
order_item_id = order_item[order_item['order_id'] == row.order_id]
items = ET.SubElement(item, 'ITEMS')
for order_row in order_item_id.itertuples():
single_item = ET.SubElement(items, 'ITEM')
item_id = ET.Element('PRODUCT_ID')
item_id.text = order_row.product_id
single_item.append(item_id)
quantity = ET.Element('AMOUNT')
quantity.text = order_row.quantity_ordered
single_item.append(quantity)
我的问题是它运行的时间长得令人难以置信(每 1000 个订单大约需要 15 分钟,每个订单大约有 20 件商品)。我想我在这里做错了什么,但我找不到。有没有办法加快速度?使用另一个库? 我试过使用 itertuples() 而不是 iterrows()。但这不是很有帮助。
编辑:
我的数据是这样的:
order = pd.DataFrame({"order_id": range(1000000,1000010,1),
"customer_id": np.random.RandomState(0).randint(1000,2000,10)})
order_item = pd.DataFrame({"order_id": np.random.RandomState(0).randint(1000000,1000010,100),
"product_id": np.random.RandomState(0).randint(1000,2000,100),
"amount": np.random.RandomState(0).randint(1,100,100)})
order_item.sort_values(by="order_id",inplace=True,ignore_index=True)
我不确定你的数据是什么样的,所以我希望这对你有用,我花了几秒钟来处理大约 5000 行:
import pandas as pd
import lxml.etree as et
df_order = pd.read_csv("order.csv", encoding='utf8', keep_default_na=False, dtype=str)
df_order_item = pd.read_csv("order_items.csv", encoding='utf8', keep_default_na=False, dtype=str)
new_orders = df_order.merge(df_order_item, 'left', left_on='order_id', right_on='order_id')
orders = et.Element('ORDERS')
for order_id in new_orders['order_id'].unique():
rows = new_orders[new_orders['order_id'] == order_id]
customer_id = int(rows['customer_id'].unique())
order = et.SubElement(orders, 'ORDER')
o_id = et.SubElement(order, 'ORDER_ID')
o_id.text = order_id
c_id = et.SubElement(order, 'CUSTOMER_ID')
c_id.text = str(customer_id)
items = et.SubElement(order, 'ITEMS')
for product in rows.itertuples():
item = et.SubElement(items, 'ITEM')
p_id = et.SubElement(item, 'PRODUCT_ID')
p_id.text = product.product_id
amount = et.SubElement(item, 'AMOUNT')
amount.text = product.quantity_ordered
在编写 XML 或 HTML 时,以文本方式编写通常比增加构建 in-memory XML 文档的费用更快。您可以直接编写文件或使用模板语言(如 jinja 2)。以下是使用多行 f-strings 编写具有所需间距的文档的示例。由于 XML 不关心换行或漂亮的打印,我倾向于在没有额外间距的情况下写作。
代码有点难看,但恕我直言,所有模板都是如此。
import pandas as pd
order = pd.read_csv("order.csv", encoding='utf8', keep_default_na=False, dtype=str)
order_item = pd.read_csv("order_item.csv", encoding='utf8', keep_default_na=False, dtype=str)
with open("out.xml", "w") as outfile:
outfile.write("""\
<ORDERS>
""")
for row in order.itertuples():
outfile.write(
f"""\
<ORDER>
<ORDER_ID>{row.order_id}</ORDER_ID>
<CUSTOMER_ID>f{row.customer_id)</CUSOMTER_ID>
""")
outfile.write(f"""\
<ITEMS>
""")
order_item_id = order_item[order_item['order_id'] == row.order_id]
for order_row in order_item_id.itertuples():
outfile.write(f"""\
<ITEM>
<PRODUCT_ID>{order_row.product_id}</PRODUCT_ID>
<AMOUNT>{order_row.quantity_ordered}</AMOUNT>
</ITEM>
"""
outfile.write("""\
</ITEMS>
""")
outfile.write("""\
</ORDERS>
</ORDER>"""
显然 pandas to_xml
不处理这种层次结构。您可以将它的一部分直接写入 xml 文件并在 sub-df:
to_xml
df = order.merge(order_item, on='order_id')
with open('output.xml', 'w') as f:
f.write('<ORDERS>')
for (ord_id, cust_id), sub_df in df.groupby(['order_id', 'customer_id']):
f.write(f'\n<ORDER>\n<ORDER_ID>{ord_id}</ORDER_ID>\n<CUSTOMER_ID>{cust_id}</CUSTOMER_ID>\n')
f.write(sub_df.to_xml(root_name='ITEMS', row_name='ITEM', xml_declaration=False, elem_cols=['product_id', 'amount'], index=False))
f.write(f'\n</ORDER>')
f.write('\n</ORDERS>')
如果您发现任何性能改进,请告诉我们!
注意:您还可以使用 kwarg parser=
('lxml' 或 'etree')
我已经尝试了上面提到的一些方法,但显着加快整个过程的一件事是使那些嵌套的 <ITEMS>
标签已经在数据库中。我们正在使用雪花,我使用 LISTAGG 按功能分组在 order_item table 上做了一个简单的分组:
CREATE OR REPLACE TABLE "wrk_order_item" AS
SELECT
"order_id",
'<ITEMS>' || LISTAGG('<ITEM><PRODUCT_ID>' || "product_id" || '</PRODUCT_ID>'
|| '<AMOUNT>' || "quantity_ordered" || '</AMOUNT>'
|| '<PRICE>' || "sell_price" || '</PRICE></ITEM>') || '</ITEMS>' AS "items"
FROM "ORDER_ITEM"
GROUP BY "order_id";
将其与订单 table 合并,并在 python 脚本中删除了订单 table 每次迭代中项目数据框的创建。两个代码(雪花,python)现在都在几秒钟内完成。