不正な(malformed)HTMLテキストを修正する
Web上のHTMLテキストには不正な(malformed)形式を持っている場合があります。
malformedなHTMLテキストにに対してはBeautifulSoupやHTMLParserなどがうまく機能しません。
例えば以下の3つのパターンはBeautifulSoupでは例外が生じます。
1. タグの閉じ>が無い
<div class="invalid_start_tag" <a src="http://...">something</a> </div>
2. 閉じタグに属性がある
</div class="invalid_end_tag">
3. scriptタグ中に閉じタグがある
<script language="javascript"><!-- function splitTag() { return '</scr' + 'ipt>'; } //--> </script>
Pythonの勉強ついでにパターン1,2を修正する関数formalize_htmlを作成しました。
パターン3については修正できていません。ただしHTMLテキストからのテキスト抽出などが目的であれば、scriptタグやコメントタグごと削除すれば問題は無いと思います。
勉強のために、doctestというものを試しに使用しています。
import re from sgmllib import SGMLParser _RE_SCRIPT = re.compile(r'<script.*?>.*?</script>', re.MULTILINE | re.DOTALL | re.IGNORECASE) def _remove_script_tags(htmltext): return _RE_SCRIPT.sub('', htmltext) _RE_COMMENT = re.compile(r'<!--.*?-->', re.MULTILINE | re.DOTALL) def _remove_comment_tags(htmltext): return _RE_COMMENT.sub('', htmltext) _RE_STYLE = re.compile(r'<style.*?>.*?</style>', re.MULTILINE | re.DOTALL | re.IGNORECASE) def _remove_style_tags(htmltext): return _RE_STYLE.sub('', htmltext) def _escape_htmltext(htmltext): escape_patterns = (('<', '<'), ('>', '>'), ('\'', '''), ('"', '"')) escaped_text = htmltext for from_text, to_text in escape_patterns: escaped_text = escaped_text.replace(from_text, to_text) return escaped_text class _HTMLFormalizer(SGMLParser): def __init__(self): self._data = [] SGMLParser.__init__(self) def unknown_starttag(self, tag, attributes): self._data.append('<{0}'.format(tag)) for key, value in attributes: self._data.append(' {0}="{1}"'.format(key, _escape_htmltext(value))) self._data.append('>') def unknown_endtag(self, tag): tag = tag.split()[0].lower() self._data.append('</{0}>'.format(tag)) def handle_data(self, data): self._data.append(_escape_htmltext(data)) def append_data(self, data): self._data.append(data) def get_data(self): return self._data def formalize_html(htmltext, keep_newlines=True, remove_scripts=True, remove_comments=True, remove_styles=True): ''' >>> malformed_htmltext = """ ... <html> ... <meta> ... <head> ... <title>TITLE</title> ... <script language="javascript"><!-- ... function splitTag() { return '</scr' + 'ipt>'; } ... function max(a, b) { return a > b ? a : b; } ... //--> ... </script> ... </head> ... <body class="invalid_start_tag" ... id="id_invalid" ... <img src="http://..."> ... <div> ... a > b ? a : b; ... </div class="invalid_end_tag"> ... </body> ... </html>""" >>> print formalize_html(malformed_htmltext) <BLANKLINE> <html> <meta> <head> <title>TITLE</title> <BLANKLINE> </head> <BLANKLINE> <BLANKLINE> <body class="invalid_start_tag" id="id_invalid"><img src="http://..."> <div> a > b ? a : b; </div> </body> </html> <BLANKLINE> ''' text = htmltext if remove_scripts: text = _remove_script_tags(text) if remove_comments: text = _remove_comment_tags(text) if remove_styles: text = _remove_style_tags(text) formalizer = _HTMLFormalizer() for line in text.splitlines(): formalizer.feed(line) if keep_newlines: formalizer.append_data('\n') formalizer.close() return ''.join(formalizer.get_data()) def _test(): import doctest doctest.testmod() if __name__ == '__main__': _test()