Python Basics: Extract HTML Details with HTMLParser
A local HTML report may need a link inventory, a title, or a quick check for images without alternative text. HTMLParser exposes tags and text through callbacks, so a small script can collect those details without regular expressions. These examples parse fixed strings only; they do not fetch pages or launch a browser.
Prepare a scratch directory
These standalone examples were tested on Linux with Python 3.12.14. Use Python 3.6 or newer for the syntax shown here. External HTML should be processed with a maintained, patched Python runtime. Use Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. No third-party packages are required. Run without -O so the assertions remain enabled.
Create a fresh directory and keep using the same terminal:
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1Save each named source below as UTF-8. Avoid filenames that shadow standard-library modules.
1. Collect link targets from start tags
Save as collect_report_links.py:
from html.parser import HTMLParser
class LinkCollector(HTMLParser):
def __init__(self):
super().__init__(convert_charrefs=True)
self.targets = []
def handle_starttag(self, tag, attrs):
if tag == "a":
href = dict(attrs).get("href")
if href is not None:
self.targets.append(href)
document = (
'<A HREF="/build?mode=full&page=2">Build</A>'
'<a href="#notes">Notes</a><a id="end">End</a>'
)
parser = LinkCollector()
parser.feed(document)
parser.close()
assert parser.targets == ["/build?mode=full&page=2", "#notes"]
for target in parser.targets:
print(target)Run it:
python3 -B collect_report_links.pyOutput:
/build?mode=full&page=2
#notesTag and attribute names arrive in lowercase, and attribute character references are decoded. An anchor without href is absent from this inventory. The returned strings are not validated URLs: collecting a link does not make it safe to visit. See handle_starttag.
2. Accumulate a title across input chunks
Save as read_chunked_html_title.py:
from html.parser import HTMLParser
class TitleReader(HTMLParser):
def __init__(self):
super().__init__(convert_charrefs=True)
self.in_title = False
self.parts = []
def handle_starttag(self, tag, attrs):
if tag == "title":
self.in_title = True
def handle_endtag(self, tag):
if tag == "title":
self.in_title = False
def handle_data(self, data):
if self.in_title:
self.parts.append(data)
parser = TitleReader()
for chunk in ("<head><tit", "le>Release &am", "p; Notes</title></head>"):
parser.feed(chunk)
parser.close()
title = " ".join("".join(parser.parts).split())
assert title == "Release & Notes"
print("Title:", title)Run it:
python3 -B read_chunked_html_title.pyOutput:
Title: Release & Notesfeed accepts partial input and buffers incomplete constructs. Join callback fragments before normalizing whitespace; adding a space between every fragment could split a word. This reader assumes one ordinary title element, as in the fixture. Call close after the last chunk.
3. Distinguish missing and empty alt attributes
Save as check_image_alt_attributes.py:
from html.parser import HTMLParser
class ImageInventory(HTMLParser):
def __init__(self):
super().__init__()
self.rows = []
def handle_starttag(self, tag, attrs):
if tag != "img":
return
attributes = dict(attrs)
alt = attributes.get("alt")
if alt is None:
status = "missing alt value"
elif alt == "":
status = "empty alt value"
else:
status = "nonempty alt value"
line, column = self.getpos()
self.rows.append((line, column, status))
document = (
'<img src="chart.png" alt="Checks by day">\n'
'<img src="divider.png" alt="">\n'
'<img src="preview.png">'
)
parser = ImageInventory()
parser.feed(document)
parser.close()
assert parser.rows == [
(1, 0, "nonempty alt value"),
(2, 0, "empty alt value"),
(3, 0, "missing alt value"),
]
for line, column, status in parser.rows:
print(f"line {line}, column {column}: {status}")Run it:
python3 -B check_image_alt_attributes.pyOutput:
line 1, column 0: nonempty alt value
line 2, column 0: empty alt value
line 3, column 0: missing alt valueThis inventory records the source position of each image and separates an absent value from an intentionally empty string. It does not judge whether text is useful or an image is decorative. getpos uses one-based line numbers and zero-based offsets; the fixture starts every tag in column zero.
HTMLParser is an event parser, not a browser DOM or an HTML sanitizer. It does not enforce matching start and end tags. These small collectors assume the illustrated markup shape; malformed or unusual documents need deliberate handling. Keep input sizes bounded, and never treat extracted text, attributes, or links as trusted instructions.
Clean up
In the same terminal, remove only the named sources, return to the original directory, and remove the empty scratch directory:
rm -- collect_report_links.py read_chunked_html_title.py check_image_alt_attributes.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirIf rmdir reports remaining files, inspect them instead of deleting unknown contents.
References
Acknowledgements
This post was created with assistance from AI.