指定した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でやれ」という世界になってますな。