How to Parse an XML File in Python
Parsing XML (eXtensible Markup Language) is a common task when working with configuration files, web service responses, or data interchange formats. Python provides several built‑in and third‑party libraries that make reading, navigating, and manipulating XML straightforward. This guide walks you through the most popular approaches, explains the underlying concepts, and offers practical code snippets you can adapt to your own projects.
Introduction
XML is a hierarchical, text‑based format that uses tags to define elements and attributes. So elementTree** (often abbreviated as ET), which is lightweight and sufficient for most use cases. Even so, dom. In Python, the standard library includes **xml.Even so, because of its tree‑like structure, parsing XML requires a parser that can interpret the nesting relationships and expose them in a programmable way. That's why etree. For more advanced features—such as XPath support, schema validation, or handling large files with minimal memory footprint—developers turn to lxml or xml.minidom.
The main keyword for this article is “how to parse an XML file in Python”. Throughout the text we will also use related LSI terms such as XML parsing, ElementTree, lxml, DOM, SAX, and XPath to reinforce topical relevance without stuffing That's the part that actually makes a difference. No workaround needed..
Choosing the Right Parser
| Parser | Pros | Cons | Typical Use Case |
|---|---|---|---|
| xml.etree.ElementTree | Built‑in, simple API, low overhead | Limited XPath, no schema validation | Small‑to‑medium files, quick scripts |
| lxml.Here's the thing — etree | Full XPath 1. 0, XSLT, schema validation, high performance | External dependency (pip install lxml) | Large files, complex queries, production systems |
| xml.dom.minidom | DOM‑style navigation, familiar to Java/XML developers | Verbose, higher memory usage | When you need a true DOM tree |
| **xml. |
For most beginners, ElementTree offers the best balance of simplicity and functionality. The examples below focus on ET, with a brief note on how to achieve the same tasks with lxml where relevant.
Step‑by‑Step Guide Using ElementTree
1. Import the Module
import xml.etree.ElementTree as ET
2. Load the XML File
ElementTree can parse from a file path, a file‑like object, or a string. The most common pattern is:
tree = ET.parse('data.xml') # returns an ElementTree object
root = tree.getroot() # returns the root Element
If the XML is already in a string, use ET.fromstring(xml_string) instead.
3. Inspect the Structure
Each element has a tag, optional attrib (a dictionary), and a list of child elements. You can print a quick overview:
def dump_element(elem, indent=0):
print(' ' * indent + f"{elem.tag}", end='')
if elem.attrib:
print(f" {elem.attrib}", end='')
print()
for child in elem:
dump_element(child, indent + 2)
dump_element(root)
4. Find Specific Elements
ElementTree supports a limited XPath‑like syntax via find() and findall().
- Find the first matching element
# Example: Python Guide
title_elem = root.find('.//title') # .// means any depth
print(title_elem.text) # -> Python Guide
- Find all matching elements
for author in root.findall('.//author'):
print(author.text)
5. Access Attributes and Text
Attributes are accessed like a dictionary; element text is available via the .text property (note that whitespace‑only text may be None).
price_elem = root.find('.//price')
price = price_elem.attrib.get('currency') # e.g., 'USD'
value = float(price_elem.text) # convert to numeric if needed
6. Modify the XML Tree
You can add, remove, or change elements and attributes in‑memory:
# Add a new element
new_chapter = ET.SubElement(root.find('book'), 'chapter')
new_chapter.text = 'Introduction to XML'
# Change an attribute
price_elem.set('currency', 'EUR')
# Remove an element
root.remove(root.find('obsolete_section'))
7. Write the Modified Tree Back to Disk
tree.write('updated_data.xml', encoding='utf-8', xml_declaration=True)
The xml_declaration=True argument ensures the file starts with <?On the flip side, xml version='1. 0' encoding='utf-8'?> Nothing fancy..
Scientific Explanation: How ElementTree Works Internally
ElementTree builds a lightweight tree of Element objects. Each Element stores:
tag: the element name (string)attrib: a dictionary of attributestext: text directly inside the element (before any child)tail: text that follows the element (after its closing tag, up to the next sibling)- A list of child
Elementreferences
When ET.parse() is called, the underlying expat parser (a fast, non‑validating SAX‑style parser) reads the XML stream and notifies the ElementTree builder of start/end events. The builder then creates or updates Element instances accordingly, linking them via parent‑child pointers. This two‑stage approach (SAX → DOM‑lite) yields low memory overhead while still offering random access to any node No workaround needed..
Because the tree resides in RAM, parsing very large files (hundreds of MB or more) can become costly. In such cases, switching to iterative parsing with ET.iterparse() or using lxml’s objectify mode is advisable.
Advanced Techniques
Using lxml for Full XPath Support
If you need complex queries (e.g., selecting elements based on attribute values or positional predicates), install lxml:
pip install lxml
Then:
from lxml import etree
tree = etree.parse('data.xml')
root = tree.getroot()
# XPath: find all elements with currency='USD'
prices = root.xpath("//price[@currency='USD']")
for p in prices:
print(p.text)
Streaming Large Files with iterparse
iterparse() yields events (start, end) as the file is read, letting you clear processed parts to free memory:
context = ET.iterparse('huge.xml', events=('start', 'end'))
for event, elem in context:
if event == 'end' and elem.tag == 'record':
# process elem …
process_record(elem)
# free memory
elem.clear()
# also clear ancestors if needed
while elem.getprevious() is not None:
del elem.getparent()[0]
8. Handling Namespaces Gracefully
XML documents often use namespaces to avoid name collisions. ElementTree does not automatically resolve them, so you must manage the namespace URIs yourself Not complicated — just consistent..
import xml.etree.ElementTree as ET
# Define a mapping for the custom namespace
ns = {'ns': 'http://example.com/schema'}
tree = ET.parse('data_ns.xml')
root = tree.getroot()
# Find elements that belong to the namespace
for elem in root.findall('ns:item', ns):
print(elem.text)
# Create a new element with the same namespace
new_item = ET.SubElement(root, f"{{{ns['ns']}}}item")
new_item.text = "New entry"
The {namespace-uri} syntax lets you attach elements to a specific namespace without importing a separate schema definition.
9. Customizing Output Formatting
When you write an XML tree back to disk, ElementTree provides several knobs to control the appearance of the result.
tree.write('formatted.xml',
encoding='utf-8',
xml_declaration=True,
method='xml',
pretty_print=True) # only works with Python ≥3.9
method='html'– serializes as HTML (invalid for strict XML).short_empty_elements=False– expands<tag/>to<tag></tag>for readability.default_namespace– sets a default namespace for the whole document.
These options are handy when you need human‑readable output or want to comply with a particular XML style guide.
10. Error‑Resilient Parsing
Parsing malformed XML is inevitable in real‑world pipelines. ElementTree raises ParseError when it encounters unexpected constructs. Catching and handling these errors lets your script continue processing valid portions.
import xml.etree.ElementTree as ET
def safe_parse(path):
try:
return ET.Still, parse(path)
except ET. ParseError as e:
print(f"Parse error in {path}: {e}")
# Optionally, fall back to a partially‑parsed tree
# (ET.
tree = safe_parse('data.xml')
if tree is not None:
root = tree.getroot()
# continue with normal processing
If you must tolerate severely broken documents, consider lxml with recover_mode=True, which attempts to reconstruct a tree even after serious violations Worth knowing..
11. Integrating ElementTree with Databases
Storing XML directly in relational databases is common. ElementTree simplifies both extraction and generation of XML fragments for SQL INSERT or UPDATE statements.
import sqlite3
import xml.etree.ElementTree as ET
# Assume a table: items(id INTEGER PRIMARY KEY, data XML)
conn = sqlite3.connect('catalog.db')
cur = conn.cursor()
# Build an XML fragment for a new product
product = ET.Element('product')
ET.SubElement(product, 'name').text = 'Wireless Mouse'
ET.SubElement(product, 'price').text = '29.99'
ET.SubElement(product, 'stock').text = '150'
# Convert to a string for SQL
xml_str = ET.tostring(product, encoding='unicode')
cur.execute('INSERT INTO items (data) VALUES (?)', (xml_str,))
conn.commit()
Once you later retrieve the data, you can parse the stored string on the fly:
cur.execute('SELECT data FROM items WHERE id = ?', (5,))
row = cur.fetchone()
if row:
root = ET.fromstring(row[0])
print(root.find('name').text)
This pattern is useful for semi‑structured attributes that do not fit neatly into fixed columns.
12. Performance Tips & Common Pitfalls
| Tip | Why it matters | How to apply |
|---|---|---|
| Avoid deep nesting | Each Element object carries overhead; deep trees increase memory usage and slow traversal. |
Flatten where possible or use iterative processing (iterparse). parse(source, parser=parser)`. |
| Clear processed elements | When streaming large files, retaining processed nodes can exhaust RAM. | |
| Reuse parsers | Creating a new XMLParser for each file incurs setup cost. getprevious() is not None: del elem.getparent()[0]. clear() and remove ancestors with `while elem. |
|
Prefer lxml for heavy loads |
lxml is implemented in C and offers better speed for large documents and XPath. On the flip side, |
Use `elem. |