19.1. HTMLParser --- HTML および XHTML のシンプルなパーサー

注釈

HTMLParser モジュールは Python 3 で html.parser に改名されました。ソースを 3 用に移行する際には 2to3 がインポートを自動的に直してくれます。

バージョン 2.2 で追加.

ソースコード: Lib/HTMLParser.py


このモジュールでは HTMLParser クラスを定義します。このクラスは HTML (ハイパーテキスト記述言語、 HyperText Mark-up Language) および XHTML で書式化されているテキストファイルを解釈するための基礎となります。 htmllib にあるパーサーと違って、このパーサーは sgmllib の SGML パーサーに基づいてはいません。

class HTMLParser.HTMLParser

HTMLParser インスタンスは、HTML データが入力されると、開始タグ、終了タグ、およびその他の要素が見つかる度にハンドラーメソッドを呼び出します。各メソッドの挙動を実装するには HTMLParser サブクラスを使ってそれぞれを上書きして行います。

HTMLParser クラスは引数なしでインスタンス化します。

htmllib のパーサーと違い、このパーサーは終了タグが開始タグと一致しているか調べたり、外側のタグ要素が閉じるときに内側で明示的に閉じられていないタグ要素のタグ終了ハンドラを呼び出したりはしません。

例外も定義されています:

exception HTMLParser.HTMLParseError

HTMLParser は正しくないマークアップを処理することが出来ますが、あるケースにおいてはこの例外が発生しえます。これは、パース中にエラーに遭遇した場合の例外です。この例外は三つの属性を提供しています: msg はエラーの内容を説明する簡単なメッセージ、lineno は壊れたマークアップ構造を検出した場所の行番号、offset は問題のマークアップ構造の行内での開始位置を示す文字数です。

19.1.1. HTML パーサーアプリケーションの例

基礎的な例として、HTMLParser クラスを使い、発見した開始タグ、終了タグ、およびデータを出力する、シンプルな HTML パーサーを以下に示します:

from HTMLParser import HTMLParser

# create a subclass and override the handler methods
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

# instantiate the parser and fed it some HTML
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

19.1.2. HTMLParser メソッド

HTMLParser インスタンスは以下のメソッドを提供します:

HTMLParser.feed(data)

パーサーにテキストを入力します。入力が完全なタグ要素で構成されている場合に限り処理が行われます; 不完全なデータであった場合、新たにデータが入力されるか、close() が呼び出されるまでバッファーされます。 dataunicode でも str でも構いませんが、 unicode の方が良いです。

HTMLParser.close()

全てのバッファされているデータについて、その後にファイル終了マークが続いているとみなして強制的に処理を行います。このメソッドは入力データの終端で行うべき追加処理を定義するために派生クラスで上書きすることができますが、再定義を行ったクラスでは常に、 HTMLParser 基底クラスのメソッド close() を呼び出さなくてはなりません。

HTMLParser.reset()

インスタンスをリセットします。未処理のデータはすべて失われます。インスタンス化の際に暗黙的に呼び出されます。

HTMLParser.getpos()

現在の行番号およびオフセット値を返します。

HTMLParser.get_starttag_text()

最も最近開かれた開始タグのテキスト部分を返します。このテキストは必ずしも元データを構造化する上で必須ではありませんが、 "広く知られている (as deployed)" 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/')]) として呼び出されます。

バージョン 2.6 で変更: 属性中の htmlentitydefs の全てのエンティティ参照が置換されるようになりました。

HTMLParser.handle_endtag(tag)

このメソッドは要素の終了タグを扱うために呼び出されます (例: </div>)。

引数 tag はタグの名前で、小文字に変換されます。

HTMLParser.handle_startendtag(tag, attrs)

handle_starttag() と似ていますが、パーサーが XHTML 形式の空タグ (<img ... />) を見つける度に呼び出されます。この特定の字句情報が必要な場合にこのメソッドをサブクラスで上書きすることができます; 既定の実装では、単に handle_starttag() および handle_endtag() を呼び出します。

HTMLParser.handle_data(data)

このメソッドは任意のデータを処理するために呼び出されます (例: テキストノードおよび <script>...</script> aや <style>...</style> の内容)。

HTMLParser.handle_entityref(name)

このメソッドは &name; 形式の名前指定文字参照 (例: &gt;) を処理するために呼び出されます。name は一般実体参照になります (例: 'gt')。

HTMLParser.handle_charref(name)

このメソッドは &#NNN; あるいは &#xNNN; 形式の 10進および 16 進数値文字参照を処理するために呼び出されます。例えば、&gt; と等価な 10 進数は &#62; で、16進数は &#x3E; になります。この場合、メソッドは '62' あるいは 'x3E' を受け取ります。

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)

処理指令 (processing instruction) が見つかった場合に呼び出されます。data には、処理指令全体が含まれ、例えば <?proc color='red'> という処理指令の場合、handle_pi("proc color='red'") のように呼び出されます。

注釈

The HTMLParser クラスでは、処理指令に SGML の構文を使用します。末尾に '?' がある XHTML の処理指令では、 '?'data に含まれることになります。

HTMLParser.unknown_decl(data)

このメソッドはパーサーが未知の宣言を読み込んだ時に呼び出されます。

パラメーター data<![...]> マークアップ内の宣言の内容全体になります。これは派生クラスで上書きする時に役立つ場合があります。

19.1.3. 例

以下のクラスは、より多くの例を示すのに用いられるパーサーの実装です:

from HTMLParser import HTMLParser
from htmlentitydefs 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 = unichr(name2codepoint[name])
        print "Named ent:", c

    def handle_charref(self, name):
        if name.startswith('x'):
            c = unichr(int(name[1:], 16))
        else:
            c = unichr(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

それ以上のパースを行わずに、scriptstyle 要素の内容をそのまま返します:

>>> 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!</strong>");</script>')
Start tag: script
     attr: ('type', 'text/javascript')
Data     : alert("<strong>hello!</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.feed('&gt;&#62;&#x3E;')
Named ent: >
Num ent  : >
Num ent  : >

不完全なチャンクを feed() に処理させても、handle_data() は 1 回以上呼び出される場合があります:

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