Scrape HTML Tables in Python with html.parser
Extract data from HTML tables using Python's built-in html.parser module, without third-party dependencies, by overriding callback methods to track table, row, and cell states.
Python code
53 linesimport html.parser
from urllib.request import urlopen
class TableParser(html.parser.HTMLParser):
def __init__(self):
super().__init__()
self.in_table = False
self.in_row = False
self.in_cell = False
self.current_cell = []
self.rows = []
self.row = []
def handle_starttag(self, tag, attrs):
if tag == "table":
self.in_table = True
elif tag == "tr" and self.in_table:
self.in_row = True
self.row = []
elif tag in ("td", "th") and self.in_row:
self.in_cell = True
self.current_cell = []
def handle_data(self, data):
if self.in_cell:
self.current_cell.append(data.strip())
def handle_endtag(self, tag):
if tag == "td" or tag == "th":
if self.in_cell:
self.row.append(" ".join(self.current_cell).strip())
self.in_cell = False
elif tag == "tr":
if self.in_row:
self.rows.append(self.row)
self.in_row = False
elif tag == "table":
self.in_table = False
if __name__ == "__main__":
html_sample = """
<table>
<tr><th>Name</th><th>Age</th></tr>
<tr><td>Alice</td><td>30</td></tr>
<tr><td>Bob</td><td>25</td></tr>
</table>
"""
parser = TableParser()
parser.feed(html_sample)
for row in parser.rows:
print(row)
Output
['Name', 'Age']
['Alice', '30']
['Bob', '25']
How it works
This code subclasses html.parser.HTMLParser, which calls overridable methods whenever it encounters start tags, end tags, and text data. The parser tracks whether it is inside a table, row, and cell using boolean flags. When a cell (<td> or <th>) closes, the collected text is appended to the current row; when a row closes, the row is appended to the rows list. The strip() and join() calls clean whitespace and normalize multi-line cell content. Because everything uses the standard library, this works anywhere Python runs without extra installs.
Common mistakes
- Forgetting to reset `in_cell` and `current_cell` at the start of each cell, causing text from previous cells to bleed in.
- Not handling nested tables — the state flags get corrupted when a table appears inside another table.
- Assuming cells always contain plain text; markup like `<b>` inside a cell breaks text capture unless you also track nested tags.
- Using `feed()` on raw bytes instead of decoding the HTML string first, which raises `UnicodeDecodeError`.
Variations
- Use `html.unescape()` on cell text to convert HTML entities like `&` into proper characters.
- Switch to BeautifulSoup's `find_all('tr')` for more tolerant parsing of malformed HTML.
Real-world use cases
- Automating data extraction from internal wikis or documentation pages that expose metrics in HTML tables.
- Building a lightweight scraper for a small site's pricing or product listings without adding lxml or BeautifulSoup dependencies.
- Unit-testing simple HTML snippets in a test suite to verify markup structure without pulling in a full parsing library.
Sponsored
More from Automation & scripting
- Aggregate Log Errors Count by Hour in Python easy
- Automate Tweeting New Blog Posts in Python easy
- Automatically Clean Temporary Files from Applications Using Python medium
- Automatically Download the Latest Software Release from GitHub with Python medium
- Automatically Generate Charts from CSV Files with One Command medium
- Automatically Generate Hardware Inventory Reports in Python easy
Keep learning
Related tutorials and quizzes for this topic.