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.

Medium Python 3.9+ Aug 9, 2026 Automation & scripting 12 views 0 copies

Python code

53 lines
Python 3.9+
import 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

stdout
['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

  1. Use `html.unescape()` on cell text to convert HTML entities like `&amp;` into proper characters.
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Automation & scripting

Related tutorials and quizzes for this topic.