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