かれ4

かれこれ4個目のブログ

ページからコンテンツだけを抜き出すためのシンプルで簡単な方法。

はじめに

Scrapingをするときに、全てを手作業で取り出す。
1サイトであれば、それで良いでしょう。
しかし、100サイト、10,000サイト、1,000サイトといわゆる不特定サイトをスクレイピングする場合、 大変な作業量となります。

最初は楽しいのですが、だんだんと飽きてきますし、100サイトを超えてくると1サイトスクレイピングしようとしている間に、 前にやったサイトのマークアップが変わっていたり、苦痛を伴い始めます。

前置きが長くなりましたが、 クローラー/Webスクレイピング Advent Calendar 2017の9日目です。

世の中どこもかしこも、AI、AIと騒いでいるんだから、スクレイピングなんてAIにやらせとけばいいじゃない とも思いますが、

それは2014年のアドベントカレンダーでもう書いているので、 今日は逆にライトなやつ。簡単に実装できるやつを。

2014年の投稿はこちら blog.tottokug.com

コンテンツだけを抜き出したい

ブログやメディアのコンテンツなどからテキストを収集することはよくありますが、 その際に苦労するのが、コンテンツ以外のサイドバーや、ヘッダ、フッタのような部分を除外していくことです。 これらの部分には、複数のページで同一の内容が記載されていることも多く、扱いにくい単語の集合ができてしまうというのです。

これに対して、古くからアプローチ方法があります。 HTML::ExtractContentのような広く(?)知られたライブラリもあります。

詳しくはこれら記事を参照してもらうとして、、 blog.takuros.net Webページの本文抽出 (nakatani @ cybozu labs)

こんなようなことをもっと簡易に実装する方法を考えたいと思います。

今はHTMLのタグも増え、section/article/header/footerなんかもありますので、 当時よりはやりやすい可能性があります。

Evernote Web Clipper

今回やりたいことは 実装として、Evernote Web Clipperが近いです。 evernote.com

これは、記事を選択とするとある程度の精度で自動的に記事らしきところを選択してくれるという私もCmd+eをショートカットキーに登録しているくらい愛用しているブラウザのExtensionです。

実際に見ているページをクリップするときには下のような状態になり、自動的にコンテンツ部分が選択されています。

f:id:tottokug:20171209175854p:plain

そして、上下で選択範囲を広げたり、左右のカーソルキーで選択範囲を移動させたりできるスグレモノです。

このソースコードはブラウザのExtensionなので、簡単に見ることができますが、Licenseがよくわからないので、心の中にとどめておくことにします。

自作 簡易版コンテンツ抽出ロジック

この記事内では、HTMLはツリー構造のグラフと見ており、タグのことをノードと呼びますが、 HTML的に言うと、それはタグのことだと思ってください。

コンテンツ抽出を簡単に行うために、3つの指標を用いて各タグをスコアリングし、 スコアが一番大きかったノードをコンテンツとしています。

  1. HTMLを0として、タグの階層の深さタグの階層の深さ。(以下 d)
  2. ノードの配下に含まれる、テキスト量(文字数)。(以下 l)
  3. 子ノードの数。(以下 c)

例えば、とあるページのHTML構造がこのようなものだったとします。

ノードは[タグ名:含まれるテキストの文字数]という形で表記しています。

f:id:tottokug:20171209224551p:plain

このようなページがあったときに、下図の白い線、白い文字の部分を抜き出したいのです。

f:id:tottokug:20171209230235p:plain

このような構造になっているため、親のノードは子ノードよりも必ずテキスト量は多くなります。 なので、深さとテキスト量のバランスの良いところを導き出そうとしています。

動作原理

動作原理は、ざっくりというと、前述の3つの数字を使いスコアを出すことで、どのノードが一番スコアが高いのかを割り出し、 一番高いスコアをつけたところを、コンテンツとみなすようにしています。

そして、スコア(S)の付け方は

S=\sqrt[\frac{3}{4}]{l}\frac{d}{\sqrt{2c}}

このような数式で表します、 もし、同じテキスト量であった場合、深さが深くなるほど、Scoreは高くなります。
逆に、同じ深さだったとしたら、テキスト量が多いほうがScoreは高くなります。
また、自身に子供がたくさんいる場合は、Scoreは低くなります。
これらのバランスを適当に取ったものが、先程の数式です。 このスコアに対し、ノード名がSECTIONだったら、1.5倍とかやるとなお良いかと思います。

HTMLから始まり、各子ノードに対し、幅優先探索でScoreを比較しながらScoreが最高のノードを探せば なんとなくコンテンツっぽいところが抜き出せるようになっています。

最後に

この部分だけソース公開します。

チューニングして、やってみたらもっといい感じに取れるようになったとか、 あれば、PullRequestください。

Trimmer/Trimmer.java at master · tottokug/Trimmer · GitHub

HttpClientの部分とHtmlParserの部分はライセンスがうにゃむにゃなので、公開しませんが、 下記のような感じで使うと、

package com.tottokug;

import org.w3c.dom.Node;

public class App {
  // 引数はURLが来ます。
  public static void main(String[] args) {
    for (String u : args) {
      // HttpClientという、Htmlとってくる。
      String body = HttpClient.fetch(u);
      // 汚いHTMLきれいにしたりした後に、 org.w3c.dom.Nodeにして返す
      Node d = HtmlParser.parse(body);
      // 公開したTrimmer.javaが最強のノードを取り出す。
      Node n = Trimmer.trim(d);
      System.out.println("******************************************************************************");
      System.out.println("TAG = " + n.getNodeName());
      System.out.println("CONTENTS = " + n.getTextContent().replaceAll("([\n|\\s])+", "$1"));
    }
  }
}

適当にmainメソッド書いて実行すると、こんな感じで取得できます。

$ java com.tottokug.App  "https://qiita.com/advent-calendar/2017/crawler"

******************************************************************************
TAG = DIV
CONTENTS = Advent Calendar2017クローラー/Webスクレイピング Advent Calendar 2017 24 67 247 Subscribeクローラー/スクレイピングに関する話題ならなんでも誰でも OK な Advent Calendar です。
Webからどうやって情報を集めるか、いろいろな方法を共有しましょう。
例
言語別のクローラー/スクレイピング方法
ノンプログラムで使えるサービス
やっぱりExcel最高!!
情報収集に関する注意点(著作権法、岡崎図書館事件)
クローラー/スクレイピング本について
過去のやつ
2016年: https://qiita.com/advent-calendar/2016/crawler
2015年: http://qiita.com/advent-calendar/2015/crawler
2014年: http://qiita.com/advent-calendar/2014/crawler
Category: Web TechnologiesOwner: takurosTweetToot Calendar Sponsor PRSunMonTueWedThuFriSat26272829301 yyanoWgetしてスクレイピングする2 watameScrapyを使ったスクレイピング3 KaminoHirokiおまえもスクレイピングしてやろうかー!! ~PHP7でWebサイトのデータぶっこ抜いてMarkdown化したお話~4 KentFujiijsサイトをスクレイピングするなら、seleniumよりsplash!5 Azunyan1111【毎秒1万リクエスト!?】Go言語で始める爆速Webスクレイピング【Golang】6 wordijpPhantomJSを使うわけ7 paperlefthand日本学生支援機構奨学金の返済状況を取得8 numa08kotlin.js とか puppeteer とかOverwrite9 tottokug@githubノOverwrite10 rhoboroscrapydの話でも11 lldev2クローラー開発エッセンス12 mojibakeoPHP with Goutte で攻める実用的なクローラアプリケーション構成みたいな13 igaraHeadless Chromeで画像収集するお話14 Pctg-x8Rustの方と合わせでHeadless Chromeを使った記事を書きます15 miseyuなんか書きますよー16 massa142 2017年度版 tseを使って未投稿があるQiita Advent Calendarをさらす17 yamachaaaanなんか書く [1]クローリングハックの書評18 sakamossanスクレイピングしてRSSにする19 binnmti読んだ漫画を登録するサイトを作っているので、出版社各社をスクレイピングしています20 Notch44なんか拾ってきて解析かけます21 ys_tydy何か書きます!22 taptappunGoogle画像検索をハックして、大量の画像を簡単に集められるようにした話23 takuya_1stなんかかく24 jesushill何か書きます!25 yyano横串を縦串にしてみる()262728293012 / 1 yyanoWgetしてスクレイピングする12 / 2 watameScrapyを使ったスクレイピング12 / 3 KaminoHirokiおまえもスクレイピングしてやろうかー!! ~PHP7でWebサイトのデータぶっこ抜いてMarkdown化したお話~12 / 4 KentFujiijsサイトをスクレイピングするなら、seleniumよりsplash!12 / 5 Azunyan1111【毎秒1万リクエスト!?】Go言語で始める爆速Webスクレイピング【Golang】12 / 6 wordijpPhantomJSを使うわけ12 / 7 paperlefthand日本学生支援機構奨学金の返済状況を取得12 / 8 numa08kotlin.js とか puppeteer とかOverwrite12 / 9 tottokug@githubノOverwrite12 / 10 rhoboroscrapydの話でも12 / 11 lldev2クローラー開発エッセンス12 / 12 mojibakeoPHP with Goutte で攻める実用的なクローラアプリケーション構成みたいな12 / 13 igaraHeadless Chromeで画像収集するお話12 / 14 Pctg-x8Rustの方と合わせでHeadless Chromeを使った記事を書きます12 / 15 miseyuなんか書きますよー12 / 16 massa142 2017年度版 tseを使って未投稿があるQiita Advent Calendarをさらす12 / 17 yamachaaaanなんか書く [1]クローリングハックの書評12 / 18 sakamossanスクレイピングしてRSSにする12 / 19 binnmti読んだ漫画を登録するサイトを作っているので、出版社各社をスクレイピングしています12 / 20 Notch44なんか拾ってきて解析かけます12 / 21 ys_tydy何か書きます!12 / 22 taptappunGoogle画像検索をハックして、大量の画像を簡単に集められるようにした話12 / 23 takuya_1stなんかかく12 / 24 jesushill何か書きます!12 / 25 yyano横串を縦串にしてみる(仮)

こんな感じで抜け出せるので、遊んでみてください。

先程の数式だけだと、 ソースコードが貼ってあるページなんかだと、その部分だけが抜き出される可能性が高かったりとまだまだ課題はありますが、 一般的なニュースサイトやメディアだときれいに抜けたりするので、お試しください。