この記事は Python Advent Calendar 2024 シリーズ3 8日目の記事です。
標準ライブラリだけでもHTMLタグ除けるもん!
はじめに
Python界の七尾百合子(図書室の暴走特急)目指してます1。nikkieです。
HTMLからタグを除いてテキストを取り出す実装、標準ライブラリだけでもできることを知りました。
目次
- はじめに
- 目次
- html.parser
- html.parserを使ってタグを除く
- 改行を含むHTMLからHTMLタグを除く
- 終わりに
- P.S. HTMLタグを除くメソッドを提供するライブラリ
- P.S. BeautifulSoupの"html.parser"って
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
handle_starttag()
メソッドは、開始タグ(<head>
や<title>
)で呼び出されているtag
引数にはタグの名前("head"
や"title"
)- https://docs.python.org/ja/3/library/html.parser.html#html.parser.HTMLParser.handle_starttag
handle_endtag()
メソッドは、終了タグ(</head>
や</title>
)で呼び出されているtag
引数にはタグの名前("head"
や"title"
)- https://docs.python.org/ja/3/library/html.parser.html#html.parser.HTMLParser.handle_endtag
handle_data()
メソッドは、タグが付与されたデータで呼び出されているdata
引数にはデータ(<title>Test</title>
の"Test"
)- https://docs.python.org/ja/3/library/html.parser.html#html.parser.HTMLParser.handle_data
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!
変更点
self.strict
属性はHTMLParser
は持たず、この実装でも使っていなかったので削除self.convert_charrefs= True
はsuper().__init__()
でやっていて(convert_charrefs=True
というデフォルト値)、重複なので削除self.reset()
は「インスタンス化の際に暗黙的に呼び出され」るとのことで、super().__init__()
しているので重複。削除
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'
これに対応する実装はいくつか考えられると思いますが、今回は以下としました
d
が空白文字でのみ構成されるのであれば、インメモリストリームには書かない- インメモリストリームに書く時に、末尾に改行文字を追加する
% 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タグを除くメソッドを提供するライブラリ
- beautifulsoup4で
get_text()
- pyparsing4に
strip_html_tags()
がある! - gensimの
strip_tags()
他にもまだまだあると思います
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()
が実装されています!