# `html.parser` --- 簡單的 HTML 和 XHTML 剖析器

原始碼：Lib/html/parser.py

該模組定義了一個類別 `HTMLParser`，是剖析 (parse) HTML（HyperText Mark-up Language、超文本標記語言）和 XHTML 格式文本檔案的基礎。

class html.parser.HTMLParser(*, convert_charrefs=True, scripting=False)

  建立一個能夠剖析無效標記的剖析器實例。

  如果 convert_charrefs 為 true （預設值），所有字元參照 (reference)（元素中的參照除外，像是 `script` 和 `style` ）將自動轉換為相應的 Unicode 字元。

  如果 scripting 為 false（預設值），`noscript` 元素的內容會被正常剖析；如果為 true，則會原樣回傳而不進行剖析。

  `HTMLParser` 實例被提供 HTML 資料，並在遇到開始標籤、結束標籤、文本、註解和其他標記元素時呼叫處理程式 (handler) 方法。使用者應該繼承 `HTMLParser` 並覆蓋其方法以實作所需的行為。

  此剖析器不檢查結束標籤是否與開始標籤匹配，也不會為透過結束外部元素來隱晦地被結束的元素呼叫結束標籤處理程式。

  在 3.4 版的變更: 新增關鍵字引數 convert_charrefs。

  在 3.5 版的變更: 引數 convert_charrefs 的預設值現在是 `True`。

  在 3.14.1 版的變更: 新增了 scripting 參數。

## HTML 剖析器應用程式範例

以下的基礎範例是一個簡單的 HTML 剖析器，它使用 `HTMLParser` 類別，當遇到開始標籤、結束標籤和資料時將它們印出：

    from html.parser import HTMLParser

    class MyHTMLParser(HTMLParser):
        def handle_starttag(self, tag, attrs):
            print("Encountered a start tag:", tag)

        def handle_endtag(self, tag):
            print("Encountered an end tag :", tag)

        def handle_data(self, data):
            print("Encountered some data  :", data)

    parser = MyHTMLParser()
    parser.feed('<html><head><title>Test</title></head>'
                '<body><h1>Parse me!</h1></body></html>')

輸出將是：

    Encountered a start tag: html
    Encountered a start tag: head
    Encountered a start tag: title
    Encountered some data  : Test
    Encountered an end tag : title
    Encountered an end tag : head
    Encountered a start tag: body
    Encountered a start tag: h1
    Encountered some data  : Parse me!
    Encountered an end tag : h1
    Encountered an end tag : body
    Encountered an end tag : html

## `HTMLParser` 方法

`HTMLParser` 實例具有以下方法：

HTMLParser.feed(data)

  向剖析器提供一些文本。只要它由完整的元素組成，它就會被處理；不完整的資料會被緩衝，直到輸入更多資料或呼叫 `close()`。 data 必須是 `str`。

HTMLParser.close()

  強制處理所有緩衝資料，如同它後面跟有文件結束標籤一樣。此方法可能有被衍生類別重新定義，以在輸入末尾定義額外的處理，但重新定義的版本仍應要呼叫 `HTMLParser` 基底類別方法 `close()`。

HTMLParser.reset()

  重置實例。丟棄所有未處理的資料。這在實例化時被會隱晦地呼叫。

HTMLParser.getpos()

  回傳目前列號 (line number) 和偏移量 (offset)。

HTMLParser.get_starttag_text()

  回傳最近開啟 (open) 的開始標籤的文本。這對於結構化處理通常不必要，但在處理「已部署」的 HTML 或以最少的更改重新生成輸入（可以保留屬性之間的空白等）時可能很有用。

當遇到資料或標記元素時將呼叫以下方法，並且它們應在子類別中被覆蓋。基底類別實作什麼都不做（除了 `handle_startendtag()`）：

HTMLParser.handle_starttag(tag, attrs)

  呼叫此方法來處理元素的開始標籤（例如 `<div id="main">`）。

  The tag argument is the name of the tag converted to lower case. The attrs argument is a list of `(name, value)` pairs containing the attributes found inside the tag's `<>` brackets. The name will be translated to lower case, and quotes in the value have been removed, and character and entity references have been replaced. For empty attributes, value is `None`.

  例如，對於標籤 `<A HREF="https://www.cwi.nl/">`，這個方法會以 `handle_starttag('a', [('href', 'https://www.cwi.nl/')])` 的形式被呼叫。

  在屬性值中來自 `html.entities` 的所有實體參照都會被替換。

HTMLParser.handle_endtag(tag)

  呼叫此方法來處理元素的結束標籤（例如 `</div>`）。

  tag 引數是轉換為小寫的標籤名稱。

HTMLParser.handle_startendtag(tag, attrs)

  與 `handle_starttag()` 類似，但在剖析器遇到 XHTML 樣式的空標籤 (`<img ... />`) 時呼叫。這個方法可能被需要這個特定詞彙資訊 (lexical information) 的子類別覆蓋；預設實作只是呼叫 `handle_starttag()` 和 `handle_endtag()`。

HTMLParser.handle_data(data)

  呼叫此方法來處理任意資料（例如文本節點與像是 `script` 和 `style` 元素的內容）。

HTMLParser.handle_entityref(name)

  呼叫此方法來處理形式為 `&name;` （例如 `&gt;`）的附名字元參照，其中 name 是一般實體參照（例如 `'gt'`）。此方法只有在 convert_charrefs 為 false 時才會被呼叫。

HTMLParser.handle_charref(name)

  呼叫此方法來處理 `&#NNN;` 和 `&#xNNN;` 形式的十進位和十六進位數字字元參照。例如，`&gt;` 的十進位等效為 `&#62;`，而十六進位為 `&#x3E;`；在這種情況下，該方法將收到 `'62'` 或 `'x3E'`。此方法只有在 convert_charrefs 為 false 時才會被呼叫。

HTMLParser.handle_comment(data)

  當遇到註解時呼叫此方法（例如 `<!--comment-->`）。

  舉例來說，註解 `<!-- comment -->` 會使得此方法被以引數 `' comment '` 來呼叫。

  Internet Explorer 條件式註解（conditional comments, condcoms）的內容也會被發送到這個方法，故以 `<!--[if IE 9]>IE9-specific content<![endif]-->` 為例，這個方法將會收到 `'[if IE 9]>IE9-specific content<![endif]'`。

HTMLParser.handle_decl(decl)

  呼叫此方法來處理 HTML 文件類型聲明 (doctype declaration)（例如 `<!DOCTYPE html>`）。

  decl 參數將是 `<!...>` 標記內聲明部分的全部內容（例如 `'DOCTYPE html'`）。

HTMLParser.handle_pi(data)

  遇到處理指示 (processing instruction) 時會呼叫的方法。 data 參數將包含整個處理指示。例如，對於處理指示 `<?proc color='red'>`，這個方法將以 `handle_pi("proc color='red'")` 形式被呼叫。它旨在被衍生類別覆蓋；基底類別實作中什麼都不做。

  備註

  `HTMLParser` 類別使用 SGML 語法規則來處理指示。使用有 `?` 跟隨在後面的 XHTML 處理指示將導致 `?` 被包含在 data 中。

HTMLParser.unknown_decl(data)

  當剖析器讀取無法識別的聲明時會呼叫此方法。

  data 參數將是 `<![...]>` 標記內聲明的全部內容。有時被衍生類別被覆蓋會是好用的。在基底類別實作中什麼都不做。

## 範例

以下類別實作了一個剖析器，將用於解說更多範例：

    from html.parser import HTMLParser
    from html.entities import name2codepoint

    class MyHTMLParser(HTMLParser):
        def handle_starttag(self, tag, attrs):
            print("Start tag:", tag)
            for attr in attrs:
                print("     attr:", attr)

        def handle_endtag(self, tag):
            print("End tag  :", tag)

        def handle_data(self, data):
            print("Data     :", data)

        def handle_comment(self, data):
            print("Comment  :", data)

        def handle_entityref(self, name):
            c = chr(name2codepoint[name])
            print("Named ent:", c)

        def handle_charref(self, name):
            if name.startswith('x'):
                c = chr(int(name[1:], 16))
            else:
                c = chr(int(name))
            print("Num ent  :", c)

        def handle_decl(self, data):
            print("Decl     :", data)

    parser = MyHTMLParser()

剖析文件類型：

    >>> parser.feed('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" '
    ...             '"http://www.w3.org/TR/html4/strict.dtd">')
    Decl     : DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"

剖析一個具有一些屬性和標題的元素：

    >>> parser.feed('<img src="python-logo.png" alt="The Python logo">')
    Start tag: img
         attr: ('src', 'python-logo.png')
         attr: ('alt', 'The Python logo')
    >>>
    >>> parser.feed('<h1>Python</h1>')
    Start tag: h1
    Data     : Python
    End tag  : h1

像是 `script` 和 `style` 這類元素的內容將按原樣回傳，無需進一步的剖析：

    >>> parser.feed('<style type="text/css">#python { color: green }</style>')
    Start tag: style
         attr: ('type', 'text/css')
    Data     : #python { color: green }
    End tag  : style

    >>> parser.feed('<script type="text/javascript">'
    ...             'alert("<strong>hello! &#9786;</strong>");</script>')
    Start tag: script
         attr: ('type', 'text/javascript')
    Data     : alert("<strong>hello! &#9786;</strong>");
    End tag  : script

Attribute names are converted to lowercase, quotes from attribute values removed, and `None` is returned as value for empty attributes (such as `checked`):

    >>> parser.feed("<input TYPE='checkbox' checked required='' disabled=disabled>")
    Start tag: input
         attr: ('type', 'checkbox')
         attr: ('checked', None)
         attr: ('required', '')
         attr: ('disabled', 'disabled')

剖析註解：

    >>> parser.feed('<!--a comment-->'
    ...             '<!--[if IE 9]>IE-specific content<![endif]-->')
    Comment  : a comment
    Comment  : [if IE 9]>IE-specific content<![endif]

剖析附名 (named) 且為數值的 (numeric) 字元參照，並將它們轉換為正確的字元（注意：這 3 個參照都等同於 `'>'`）：

    >>> parser = MyHTMLParser()
    >>> parser.feed('&gt;&#62;&#x3E;')
    Data     : >>>

    >>> parser = MyHTMLParser(convert_charrefs=False)
    >>> parser.feed('&gt;&#62;&#x3E;')
    Named ent: >
    Num ent  : >
    Num ent  : >

將不完整的區塊提供給 `feed()` 是可行的，但是如果 convert_charrefs 為 false，`handle_data()` 可能會被呼叫多次：

    >>> for chunk in ['<sp', 'an>buff', 'ered', ' text</s', 'pan>']:
    ...     parser.feed(chunk)
    ...
    Start tag: span
    Data     : buff
    Data     : ered
    Data     :  text
    End tag  : span

也能夠剖析無效的 HTML（例如未加引號的屬性）：

    >>> parser.feed('<p><a class=link href=#main>tag soup</p ></a>')
    Start tag: p
    Start tag: a
         attr: ('class', 'link')
         attr: ('href', '#main')
    Data     : tag soup
    End tag  : p
    End tag  : a