CodeRigor

FREE LESSON

Extract anchors with structured HTML parsing

Collect href values and nested anchor text with explicit state, missing-value rules and whitespace policy.

A hyperlink has two useful pieces of evidence: a destination reference in href, and a label in the element’s content. Those pieces have different boundaries. In <a href="guide.html">Read <strong>the guide</strong></a>, the attribute supplies guide.html; the label spans two text callbacks and a nested element. Taking only the first text callback loses information. Taking everything after the start tag until the next closing tag stops at </strong> too early.

The label spans nested elements
start / 2
The destination belongs to the anchor attribute; Read and the guide are text descendants of the same anchor.
The label spans nested elements
The destination belongs to the anchor attribute; Read and the guide are text descendants of the same anchor.

Use parser state to express the question. When an a start event arrives, save its href and reset a list of chunks. While the anchor is active, append data. Ignore nested inline start/end events. At the matching a end event, join the chunks, apply the label policy and append a (href, label) tuple. Reset state before the next anchor. Comments contribute neither destination nor label text.

The fixtures use nonnested anchors, unique attribute names and ordinary inline children. They exclude script/style content within anchors. This keeps one active anchor sufficient. HTMLParser does not validate those restrictions or repair arbitrary broken HTML into a browser DOM. The build retains completed anchors only; an unfinished anchor at end of input produces no record. The later inventory lessons need only href start events, so their deliberately different contract can inventory an unfinished source anchor too.

Python
from html.parser import HTMLParser

class Hrefs(HTMLParser):
    def handle_starttag(self, tag, attrs):
        if tag == "a":
            href = dict(attrs).get("href")
            if href is not None:
                print(repr(href))

parser = Hrefs()
parser.feed('<a>name</a><a href="">self</a><a href>unset</a>')
parser.close()

Missing, empty and valueless are three source cases. A missing attribute is absent from the pairs; a valueless attribute has value None; an explicitly empty attribute has value "". This course records the empty string because resolving it against a document URL has a meaningful result. A truthiness test would incorrectly discard it. The parser also decodes &amp; in href values, so extraction returns the actual reference rather than its HTML serialization.

One active anchor
start / 2
Nested inline markup leaves the active anchor open; only its own end event emits the tuple.
One active anchor
Nested inline markup leaves the active anchor open; only its own end event emits the tuple.

Define whitespace before writing tests. Our label rule concatenates chunks with no added separators, then uses " ".join(text.split()) to collapse whitespace runs and trim ends. <a>one<em>two</em></a> therefore labels onetwo, while an actual space before the em gives one two. We do not infer layout spacing, hidden CSS content, image alt text or an accessible name. An image-only anchor has an empty extracted text label.

Python
chunks = [" Read ", "the", "\n guide "]
print(" ".join("".join(chunks).split()))
print("".join(["one", "two"]))

Build the extractor and test adjacent anchors, uppercase attribute names, entities, empty hrefs, inline nesting and an unfinished anchor. Preserve source order and duplicates: repeated occurrences may later matter to a report. Keep relative references unchanged at this stage. Resolving a URL requires its document base, and extraction alone has not supplied that policy.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Watch 21:20–23:50 for anchor tags, href attributes and destination examples. Trace where the reference lives; callback state, nested text and the exact extraction contract are taught locally.