nikkie-ftnextの日記

イベントレポートや読書メモを発信

Python標準ライブラリの html.parser で、HTMLからタグを除く

この記事は Python Advent Calendar 2024 シリーズ3 8日目の記事です。
標準ライブラリだけでもHTMLタグ除けるもん!


はじめに

Python界の七尾百合子図書室の暴走特急)目指してます1。nikkieです。

HTMLからタグを除いてテキストを取り出す実装、標準ライブラリだけでもできることを知りました。

目次

html.parser

HTML と XHTML のシンプルなパーサー

HTML パーサーアプリケーションの例」を見ます

動作環境は Python 3.12.6 です。

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

出力はドキュメントと一致するので、ここでは前半だけ示します。

% python example.py
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

html.parserを使ってタグを除く

StackOverflowより

from io import StringIO
from html.parser import HTMLParser

class HtmlTagStripper(HTMLParser):
    def __init__(self):
        super().__init__()
        self.text = StringIO()

    def handle_data(self, d):
        self.text.write(d)

    def get_data(self):
        text = self.text.getvalue()
        self.text = StringIO()
        return text

def strip_tags(html):
    s = HtmlTagStripper()
    s.feed(html)
    return s.get_data()

html = '<html><head><title>Test</title></head><body><h1>Parse me!</h1></body></html>'
print(strip_tags(html))
% python strip_tags.py
TestParse me!

変更点

StackOverflowで見つけた実装の本質は、テキストのインメモリストリーム2を用意し、handle_data()メソッドでHTMLタグ以外(=開始タグでも終了タグでもないデータ)をそこに書いていくことです。
連続実行できるように、get_data()メソッドで、テキストのインメモリストリームを空にするように変更しました

改行を含むHTMLからHTMLタグを除く

今回タグを除きたいHTMLは、ファイルに保存されており、改行やインデントがあります。
(例は、BeautifulSoupのドキュメントより)

<html>
 <head>
  <title>
   The Dormouse's story
  </title>
 </head>
 <body>
  <p class="title">
   <b>
    The Dormouse's story
   </b>
  </p>
 </body>
</html>
% python strip_tags.py



   The Dormouse's story





    The Dormouse's story




タグの後ろの改行文字や、タグの前のインデントがデータ扱いでインメモリストリームに溜まっていくので、上の実装では改行やインデント込みでテキストが取り出されます。
handle_data()で渡ってくるdを出力してみました。

d='\n '
d='\n  '
d="\n   The Dormouse's story\n  "
d='\n '
d='\n '
d='\n  '
d='\n   '
d="\n    The Dormouse's story\n   "
d='\n  '
d='\n '
d='\n'
d='\n'

これに対応する実装はいくつか考えられると思いますが、今回は以下としました

% python strip_tags.py
The Dormouse's story
The Dormouse's story

この実装によりデータごとに改行が入るので、「TestParse me!」となっていたHTMLでは

Test
Parse me!

と変わります。
今回考えているケースではこちらの方がよいように思われました。

終わりに

標準ライブラリだけで、HTMLからタグを除くことができました🙌

html.parser は初めて触ったライブラリでしたが、の扱いですね。
抽象構文木の走査の経験があった3分、理解が進みました。

P.S. HTMLタグを除くメソッドを提供するライブラリ

他にもまだまだあると思います

P.S. BeautifulSoupの"html.parser"って

標準ライブラリの html.parser のようでした。

https://code.launchpad.net/beautifulsoup/ からソースコードをclone。
4.12.3 タグを見ます。

bs4/builder/_htmlparser.py

class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):
    """A subclass of the Python standard library's HTMLParser class, which
    listens for HTMLParser events and translates them into calls
    to Beautiful Soup's tree construction API.
    """

handle_starttag()handle_endtag()handle_data()が実装されています!


  1. ありたい七尾百合子に対して、実際のところは田中琴葉です。「いけません」の1つ
  2. インメモリストリームは、過去のPyCon JPに個人的に決定版な発表があります
  3. Visitorパターンで触りました
  4. 直近触りました