Web記事の本文をJavaで抽出する

指定したURLの記事本文を取ってくる処理がほしいなぁと思って、しかしいろいろ考えるのは面倒と思ったけど、少なくともJavaで簡単に使えるものがなさそうなので実装した。

参考にしたのはこちら。
HTMLからの本文自動抽出 - アドファイブ日記(ミラー版)

テキストが長いタグは本文、直下のタグは本文への寄与が多い、のようにしてスコアを求め、そして全体テキストに対してスコアが高ければ本文だろう、という感じ。

上記サイトと変えたところは、<li>タグの点数をさげているところ。読売の記事が最後に各カテゴリの説明をもっていて、記事より長くなりがちでそちらが抽出されることが多そうなので、点数をさげた。

前回のブログ記事で試すとちゃんと取れている。

ソースはこんな感じ。HTML処理にはjsoupを使っている。
https://jsoup.org/

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Map;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;

public class ArticleFinder {
    
    private record Score(int vol, double score, Element elm){
        double rate() {
            if (vol == 0) return 0;
            return score * score / vol;
        }
    }
    
    private static final double RATE = 0.7;
    private static final Map<String, Double> customRate = 
            Map.of("p", 1., "li", 0.3);
    private Score longest;
    private ArticleFinder() {
    }
    private Element findLongest(Element elm) {
        longest = null;
        calc(elm);
        return longest == null ? null : longest.elm();
    }
    private Score calc(Element elm) {
        int textlen = elm.ownText().length();
        double score = textlen;
        for (Element child : elm.children()) {
            var chScore = calc(child);
            textlen += chScore.vol();
            score += chScore.score() * 
                customRate.getOrDefault(child.tagName().toLowerCase(), RATE);
        }
        var result = new Score(textlen, score, elm);
        if (longest == null || longest.rate() < result.rate()) {
            longest = result;
        }
        return result;
    }
    
    public static String findArticle(String url) {
        try {
            var doc = Jsoup.connect(url).get();
            var body = doc.body();
            var elm = findArticleElement(body);
            return elm == null ? "" : elm.text();
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }
    public static Element findArticleElement(Element elm) {
        var fa = new ArticleFinder();
        return fa.findLongest(elm);
    }
    
    public static void main(String[] args) throws IOException {
        var url = "https://nowokay.hatenablog.com/entry/2023/02/15/221559";
        var text = findArticle(url);
        System.out.println(text);
    }
}

しかしもう、こういう処理は完全に「Pythonでやれ」という世界になってますな。