# `html.parser` --- 简单的 HTML 和 XHTML 解析器

源代码： Lib/html/parser.py

这个模块定义了一个 `HTMLParser` 类，为 HTML（超文本标记语言）和 XHTML 文本文件解析提供基础。

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

  创建一个能解析无效标记的解析器实例。

  如果 convert_charrefs 为真值（默认），则所有字符引用（除了元素中的字符引用 `script` 和 `style` 除外）都会自动转换为对应的 Unicode 字符。

  如果 scripting 为假值（默认），则 `noscript` 元素中的内容将被正常解析；如果为真值，它将不加解析地原样返回。

  一个 `HTMLParser` 类的实例用来接受 HTML 数据，并在标记开始、标记结束、文本、注释和其他元素标记出现的时候调用对应的方法。要实现具体的行为，请使用 `HTMLParser` 的子类并重写其方法。

  这个解析器不检查结束标记是否与开始标记匹配，也不会因外层元素完毕而隐式关闭了的元素引发结束标记处理。

  在 3.4 版本发生变更: convert_charrefs 关键字参数被添加。

  在 3.5 版本发生变更: convert_charrefs 参数的默认值现在为 `True`。

  在 3.13.10 版本发生变更: 增加了 scripting 形参。

## HTML 解析器的示例程序

作为一个基础示例，下面是一个使用 `HTMLParser` 类来打印所遇到的开始标记、结束标记和数据的简单 HTML 解析器：

    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()

  返回当前行号和偏移值。

HTMLParser.get_starttag_text()

  返回最近打开的开始标记中的文本。 结构化处理时通常应该不需要这个，但在处理“已部署”的 HTML 或是在以最小改变来重新生成输入时可能会有用处（例如可以保留属性间的空格等）。

下列方法将在遇到数据或者标记元素的时候被调用。他们需要在子类中重写。基类的实现中没有任何实际操作（除了 `handle_startendtag()` ）：

HTMLParser.handle_starttag(tag, attrs)

  调用此方法来处理一个元素的开始标记 (例如 `<div id="main">`)。

  tag 参数是小写的标记名。attrs 参数是一个 `(name, value)` 形式的列表，包含了所有在标记的 `<>` 括号中找到的属性。name 转换为小写，value 的引号被去除，字符和实体引用都会被替换。

  实例中，对于标签 `<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 ... />`）。这个方法能被需要这种特殊词法信息的子类重写；默认实现仅简单调用 `handle_starttag()` 和 `handle_endtag()` 。

HTMLParser.handle_data(data)

  调用此方法来处理任意数据（例如文本节点和元素如 `script` 和 `style` 中的内容）。

HTMLParser.handle_entityref(name)

  调用此方法来处理 `&name;` 形式的命名字符引用 (例如 `&gt;`)，其中 name 是通用的实体引用 (例如 `'gt'`)。 此方法仅在 convert_charrefs 为假值时会被调用。

HTMLParser.handle_charref(name)

  调用此方法来处理 `&#NNN;` 和 `&#xNNN;` 形式的十进制和十六进制数字字符引用。 例如，`&gt;` 的等价十进制形式为 `&#62;`，而十六进制形式为 `&#x3E;`；在此情况下该方法将收到 `'62'` 或 `'x3E'`。 此方法仅在 convert_charrefs 为假值时会被调用。

HTMLParser.handle_comment(data)

  这个方法在遇到注释的时候被调用（例如： `<!--comment-->` ）。

  例如， `<!-- comment -->` 这个注释会用 `' comment '` 作为参数调用此方法。

  Internet Explorer 条件注释（condcoms）的内容也被发送到这个方法，因此，对于 `<!--[if IE 9]>IE9-specific content<![endif]-->` ，这个方法将接收到 `'[if IE 9]>IE9-specific content<![endif]'` 。

HTMLParser.handle_decl(decl)

  这个方法用来处理 HTML doctype 申明（例如 `<!DOCTYPE html>` ）。

  decl 形参为 `<!...>` 标记中的所有内容（例如： `'DOCTYPE html'` ）。

HTMLParser.handle_pi(data)

  此方法在遇到处理指令的时候被调用。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()

解析一个 doctype：

    >>> 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

解析注释：

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

解析名称型和数字型字符引用并将其转换为正确的字符 (注意：这 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 为假值则 `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