專項練習 · 8 分鐘閱讀

Python HTMLParser 練習:連結、文字與屬性

把解析器拆成小型處理器,更容易信任。這些練習每次只增加一個職責:收集標籤、讀取屬性、捕獲文字,最後組合成記錄。

從標籤收集器開始

重寫 handle_starttag,把標籤名加入列表。類要小到能在一屏內讀完。只做一件事的解析器,比一次提取所有內容的解析器更容易測試。

from html.parser import HTMLParser

class Tags(HTMLParser):
    def __init__(self):
        super().__init__()
        self.names = []
    def handle_starttag(self, tag, attrs):
        self.names.append(tag)

p = Tags()
p.feed("<article><h1>Title</h1><p>Text</p></article>")
print(p.names)

一個處理器,一個職責

讀取屬性但不要崩潰

attrs 是鍵值對列表,有些值可能是 None。先轉換成字典,再用 get。導航標記裡缺少 href 很正常,應跳過,而不是把 None 加入結果。

from html.parser import HTMLParser

class Links(HTMLParser):
    def __init__(self):
        super().__init__()
        self.links = []
    def handle_starttag(self, tag, attrs):
        values = dict(attrs)
        if tag == "a" and values.get("href"):
            self.links.append(values["href"])

p = Links()
p.feed('<a href="/one">One</a><a>No link</a><a href="/two">Two</a>')
print(p.links)

跳過缺失值

捕獲標籤之間的文字

handle_data 接收的是一段段文字,不是完整句子。逐段去掉首尾空白,忽略空字串,再決定如何連接。如果空白有意義,要有意識地保留,而不是依賴瀏覽器。

from html.parser import HTMLParser

class Text(HTMLParser):
    def __init__(self):
        super().__init__()
        self.parts = []
    def handle_data(self, data):
        text = data.strip()
        if text:
            self.parts.append(text)

p = Text()
p.feed("<h1>Title</h1><p>First sentence.</p>")
print(" ".join(p.parts))

清理後的文位元組點

解碼實體並保留來源

HTMLParser 會解碼 &amp; 這類字元引用。儲存提取文字時,把原始 URL 和簡短來源一起保留。解析器提供結構,來源資訊讓人可以核對結果。

from html import unescape

raw = "Tom &amp; Jerry"
clean = unescape(raw)
print(clean)
print(raw)

解碼並保留原值

把多個處理器組合成記錄

一個類同時收集連結和文字時,要顯式管理狀態,並為每個頁面建立新實例。除非明確清空所有列表,否則不要跨文件復用解析器;殘留狀態是重複記錄的常見來源。

from html.parser import HTMLParser

class Page(HTMLParser):
    def __init__(self):
        super().__init__()
        self.links = []
        self.text = []
    def handle_starttag(self, tag, attrs):
        href = dict(attrs).get("href")
        if tag == "a" and href:
            self.links.append(href)
    def handle_data(self, data):
        text = data.strip()
        if text:
            self.text.append(text)

p = Page()
p.feed('<h1>Docs</h1><a href="/docs">Read</a>')
print(p.links)
print(p.text)

兩個列表,一次走訪

你的練習清單

常見問題

HTMLParser 是完整瀏覽器嗎?

不是。它是 Python 程式碼裡的寬容分詞器,不執行 JavaScript,也不建構完整 DOM。

為什麼不用正規?

正規不理解巢狀和帶引號的屬性。解析器遵循文件結構,也更容易維護。

可以用 BeautifulSoup 嗎?

可以,前提是能安裝第三方包。標準函式庫 HTMLParser 足以處理簡單、結構良好的頁面,而且沒有依賴。

練習 HTML 解析

12 道題,覆蓋標籤、屬性、文位元組點、實體和解析器狀態。

打開練習

繼續閱讀: