Hatena::ブログ(Diary)

Kazzzの日記 このページをアンテナに追加 RSSフィード

2010-07-10

[][]Tagsoup HTMLパース時のエンコーディング


Tagsoupを使って、対象のURLからHTMLをパースするサービスを書いている。

InputStream ins;
ContentHandler contentHandler;
try {
    Parser parser = new Parser();
    if ( contentHandler != null ) {
        parser.setContentHandler(contentHandler);
    }
    parser.setFeature(Parser.ignoreBogonsFeature, false);
    parser.setProperty(Parser.schemaProperty, HtmlParser.schema);
    
    parser.parse(new InputSource(new InputStreamReader(ins)));
    return parser;
} catch (UnsupportedEncodingException e) {
    :
} catch (IOException e) {
    :
} catch (SAXException e) {
    :
}


HTMLのパーサなのだからContentType:ヘッダのcharsetを見て処理するのだろうと思っていたのだがそんなことは無く、上記のように普通に組むと日本語を含むUNICODE文字は普通に文字化けする。

ソースコードを見ると、普通にストリームを食っているだけのようなので、IANAエンコーディング名を明示的に設定すると文字化けは止まった。
String encoding = "Windows-31J"; 
parser.parse(new InputSource(new InputStreamReader(ins, encoding)));

同ソースコードを見ていて気がついたのだが、Tagsoupのパーサは"AutoDetector"と呼ばれる自動検出のためのオブジェクトを外部から注入することができるようになっており、こいつを使って自動認識をさせることもできそうだ。

デフォルトでは単にストリームを返すだけの実装になっているので、
private void setup() {
    if (theSchema == null) theSchema = new HTMLSchema();
    if (theScanner == null) theScanner = new HTMLScanner();
    if (theAutoDetector == null) {
        theAutoDetector = new AutoDetector() {
            public Reader autoDetectingReader(InputStream i) {
                return new InputStreamReader(i);
            }
        };
    }
    :
    :
}

カスタムなAutoDetectorを設定することになるだろう。
    Parser parser = new Parser();
    if ( contentHandler != null ) {
        parser.setContentHandler(contentHandler);
    }
    parser.setFeature(Parser.ignoreBogonsFeature, false);
    parser.setProperty(Parser.schemaProperty, HtmlParser.schema);
    parser.setProperty(Parser.autoDetectorProperty
            , new AutoDetector(){
                @Override
                public Reader autoDetectingReader(InputStream i) {
                    //例) Apache HttpClientのHeaderにおけるContentType 〜 charset=でエンコードする
                    Header contentTypes = httpResponse.getFirstHeader("Content-Type");
                    String encoding = contentTypes.getElements()[0].getParameterByName("charset").getValue();
                    return new InputStreamReader(ins, encoding);
                }});
    
    parser.parse(new InputSource(new InputStreamReader(ins)));
    return parser;

Tagsoupだが一般的なXMLSAXパーサ同様に使用出来るため、使い勝手が良い。問題は処理スピードだろうが、Webスクレイピングをスマートフォン上で実行すること自体がイリーガルな訳で、気にする所ではないだろう。

peacepeace 2011/02/13 01:58 ヘッダにcharsetが指定されない場合、どのようにエンコードすべきでしょうか?
parseしてcharsetを抽出するとかしないと駄目でしょうか?

KazzzKazzz 2011/02/13 16:10 基本的には宣言部分で文字セットを指定して正しくヘッダがセットされるようにすべきですが、残念ながらそのようなHTMLばかりではありませんので、仰る通りパースして要素中の"charset="や"encoding="を検出するのがベターかと思います。

peacepeace 2011/02/13 18:00 やはり抽出してエンコードし直した方がよいのですね。
とりあえずInputStreamReaderにデフォルトの文字セットencodingを指定してパースし、
ContentHandlerでタグ要素とcharsetを抽出してみたのですが、そのcharsetで
変換し直しても文字化けしたままでした。やり方が間違っているのか不明ですが…。

String temp = new String("タグ要素".getBytes(encoding), charset);

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/Kazzz/20100710/p1
Connection: close