概要

HTML から文章などを抽出して解析する際の Tips をまとめたいと思います。 今回は、HTML 中に出現する単語を数える場合を想定しています。

このような解析の際に問題になるのは、文字コードと言語かと思います。

そこで、Python で文字コードと言語を判定する方法をまとめてみます。

文字コード判定

1. HTTP ヘッダーの charset を確認する

まずは HTTP ヘッダーの情報を利用するのが確実かと思います。 ただし、返された charset に Python が対応してない場合があるので、 codecs.lookup() で確認しています。 対応していない場合、もしくは charset の記述が間違っている場合は LookupError となります。

import urllib
import codecs
url = 'http://example.com'
response = urllib.urlopen(url)
charset = response.headers.getparam('charset')
html = response.read()
   
if charset != '':
    try:
        codecs.lookup(charset)
        html = html.decode(charset, 'replace')
    except:
        pass

2. head タグ内の meta charset を確認する

HTML 中に書かれている charset は特にスペルミスなどで間違っている ことが多いので、注意する必要があります。 また、x-sjis や x-euc は Python の文字コード判定に弾かれるので それぞれ Shift_JIS と EUC-JP として扱うようにしています。

import re
char_re = re.compile(r"(?is)content=[\"'].*?;\s*charset=(.*?)[\"']")
enc_dic = {"x-sjis": "Shift_JIS", "x-euc": "EUC-JP"}
result = char_re.search(html)
if result is not None:
    enc = resulg.group(1)
    if enc in enc_dic:
        enc = enc_dic[enc]
else:
    enc = ''

3. Try and Error で手当たりしだいに decode する

ここに関してはPython Tips を参考、というかほぼそのまま使わせていただいています。

判定する文字コードの順番を変えてますが、これは確かこっちの方が正解率が 高かったとかそういう理由だったと思います。

encodings = [
        "ascii",
        "utf-8",
        "euc-jp",
        "cp932",
        "iso-2022-jp",
]


def detect( text ):
        bestScore = -1
        bestEnc = None
        for enc in encodings:
                try:
                        unicode( text, enc )
                except UnicodeDecodeError, err:
                        if err.end > bestScore:
                                bestScore = err.end
                                bestEnc = enc
                else:
                        return {
                                "encoding": enc,
                                "confidence": 1.0,
                        }

        return {
                "encoding": bestEnc,
                "confidence": bestScore / (bestScore + 2.0),

4. chardet を使う

chardet(Universal Encode Detector) とは Python 用の文字コード判定モジュールです。 精度は決して悪くないんですが、処理が重たいので優先順位を低めにして 使用しています。

import chardet
enc = chardet.detect(html)['encoding']

言語判定

ngram.py と Lingua::LanguageGuesser の言語モデルを組み合わせる方法を 使ってました。

ngram.py: http://thomas.mangin.me.uk/

ページ右側の検索窓で ngram.py で検索するとダウンロードリンクを見つけることができます。

もしくは ngram.py から直接。

Lingua::LanguageGuesser: http://gensen.dl.itc.u-tokyo.ac.jp/LanguageGuesser/hajimete_monogatari.html

この二つのモジュールは TextCat という Perl で書かれた言語判定スクリプトの

  • Python 移植版 → ngram.py
  • Perl モジュール化 & UTF-8 への対応を強化 → Lingua::LanguageGuesser

という関係性があります。

ngram.py を動かすには言語モデル(言語ごとのプロファイル)を記録したファイルが必要です。配布元では TextCat の言語モデルを使用するように、とされてますがこれに Lingua::LanguageGuesser の言語モデルを使用することで UTF-8 へ対応できるようにします。

ちなみに、UTF-8 かどうかで判定に使用する言語モデルを変更する必要が あるので、使用する場合はその辺りも考慮する必要があります。

imporg ngram

n = ngram._NGram()
if enc == 'UTF-8':
    l = ngram.NGram('/path/to/utf8/modeldir')
else:
    l = ngram.NGram('/path/to/nonutf8/modeldir')
lang = l.classify(text)

いろいろはしょってますがこんな感じで。ちなみに、LanguageGuesser の 言語モデルは /usr/lib/perl5/site_perl/5.8.8/Lingua/LM_utf8/ 辺りにありました(CentOS の場合)。

一つ注意点として。この方法だと、例えば英語スパムが大量に付けられた ブログ記事を英語と判定してしまったり、ソースコードが大量に書かれた 記事を英語として判定してしまったり、といったことが起こりえます。

そのような場合には、あらかじめ記事本文のみを抽出したり、 ソースコードを取り除くといった処理が必要になります。