2011/02/17

ContentExtractorのプラグインの仕様

NILScriptに同梱の「Notebook.ng」でWebページから本文を抽出するために用意されている「ContentExtractor」では、本文の抽出方法をプラグインとして追加できるようになっています。
現在のところ、はてなダイアリーやFC2ブログなどの代表的なブログサービスや、Vector、窓の杜、ITMediaなどのIT系情報サイトに対応するプラグインなどが用意されていますが、各自でプラグインを作成すればどんなサイトにでも対応させられます。
今回は、ContentExtractorプラグインの仕様について説明します。

基本的な定義

ContentExtractorのプラグインは、NILScriptのディレクトリ配下の「plugins\ContentExtractor」ディレクトリ内にプラグイン名のディレクトリを作って格納します。
各プラグインのディレクトリには、プラグイン名に拡張子「.ng」を付けたファイル名でプラグインのスクリプトファイルを格納します。
スクリプトファイル内には、以下のようなオブジェクト定義を記述します。

({
    description:String(<![CDATA[
        http://wiredvision.jp/の巡回と記事本文抽出。
    ]]>),
    level:50,
    rules:{
        atom:{
            urlPattern:/^http:\/\/(wiredvision\.jp|rss\.rssad\.jp\/rss\/wiredvision)\/.*\/atom\.xml$/i,
            inherit:"std/atom",
            childContentBodyPattern:true,
            replace:['NoTracker'],
        },
        article:{
            urlPattern:/^http:\/\/wiredvision\.jp\/.*\.html$/i,
            contentPattern:/<!--\s*google_ad_section_start\(name=s2\)\s*-->([\s\S]*?)<!--\s*google_ad_section_end\(name=s2\)\s*-->/i,
            sectionLinkPattern:/(<p><a href="http:\/\/wiredvision\.jp\/[^">]+">[^<]*へ続く<\/a><\/p>)/i,
            replace:[
                
            ],
        },
    },
})

オブジェクトの「rules」メンバの下にページの解析ルールを列挙し、各ルールのurlPatternメンバで、そのルールが対応しているページのURLにマッチする正規表現を指定します。
上記の「atom」ルールの「inherit:"std/atom"」は、stdプラグインで定義されている「atom」ルールを継承し上書きすることを示しています。
std/atomルールは、Atomフィードから各entry要素のlinkやcontentを子アイテムとして抽出するためのルールです。他に「std/rss」や「std/rdf」もあります。
これらのルールでフィードから項目を抽出する際には、アクセス解析用の中継URLをリダイレクト先のURLに置換したり広告項目を除去する機能などが自動的に適用されるようになっています。
「childContentBodyPattern:true」は、content要素を無視してlinkが示すWebページから対応するルールで抽出した本文を本文として使用することを示しています。
これは、フィードに含まれている本文が途中で終って「続きを読む」のリンクになっている場合に本当の本文を保存させるためなどに使用します。
「replace:['NoTracker']」は、フィードのsummary要素に仕込まれているアクセス解析用の不可視の画像を取り除く処理を定義しています。replaceの詳細については後で説明します。
このようなフィード用定義は、urlPatternとinheritの値以外はほとんど使い回せるので、コピペ・改編して利用するといいでしょう。

「article」ルールでは、記事のページから本文を抽出するルールを定義しています。
ここでは、「contentPattern」に指定された正規表現に従って、ページのHTMLから本文の部分が抽出されます。この時、正規表現中に「()」が使われている場合は、最初の括弧にマッチした部分が抜き出し結果となり、括弧がない場合はマッチ範囲全体が使われます。これは、前後の部分を目印に使いつつ、抜き出し結果には含めたくない場合などのための仕様です。途中に「(p|div)」のような括弧を使ったパターンを含めたい場合は、「(?:p|div)」のように、キャプチャ無し括弧を使ってください。
上記の例のように、コンテキストマッチング型広告サービスを利用しているサイトでは、本文の範囲を認識させるために埋め込まれたコメントタグを利用するのが手っ取り早いでしょう。

正規表現で抜き出しパターンを指定する際に特に注意すべきなのは「.」は改行にマッチしないということです。改行を含むあらゆる文字列にマッチさせるためには「[\s\S]」などと指定しなければなりません。
また、量指定子「*」や「+」は、そのままでは「貪欲」にマッチングを行うため、[\s\S]と組み合わせて使用する場合は、「?」を付けて「無欲」にする必要があります。
これを忘れて「<h1>aaa</h1><p>AAAA</p><h1>bbb</h1><p>BBBB</p>」のようなHTMLに対して「/<h1>.*<\/h1>/i」のようなマッチングを行うと、「<h1>aaa</h1><p>AAAA</p><h1>bbb</h1>」までが一致してしまったりします。
今回のcontentPatternの場合、終了タグが1回しか出現しないので貪欲でも問題はありませんが、無欲の方が余計な検索が行われず高速に動作するはずです。

「sectionLinkPattern」では、本文が複数のページに分かれているときに、以降のページにリンクしている部分を抽出・結合するためのパターンを指定します。
また、この例では登場しませんが、ページのタイトルをtitleタグ以外から抽出したい場合に指定する「titlePattern」や、ページの概要を抽出する「summaryPattern」などもあります。

これらの抜き出しパターンでは、正規表現以外にもいくつかの抽出方法が指定できます。
HTMLの要素に付けられたid属性で抽出するには、「contentPattern:['#contentBody']」のように指定します。
「contentPattern:function(html){/*何らかの処理*/;return(content);}」のように、HTML文字列を引数に受取り抜き出した文字列を返す関数を指定することも可能です。
他にもいくつかの方法がありますが、この3つを覚えておけばほとんどの状況には対応できるでしょう。


HTMLからの記事一覧の抽出

RSSフィードなどを配信していないサイトで、記事一覧のページから記事を抽出したい場合は、「childContentPattern」を使用します。
以下は、「vector」プラグインで定義されているカテゴリ別ソフト一覧のページの定義例です。

        soft_category:{
            urlPattern:/^http:\/\/www\.vector\.co\.jp\/vpack\/filearea\/(?:win|mac)\/.*$/i,
            childContentPattern:/(<LI>\s*<A HREF="[^">]*\/se\d+\.html">[^<]*<\/A>\s*<IMG[^>]*>[\s\S]*?<BR>[\s\S]*?<\/LI>)/ig,
            childContentTitlePattern:/<a href="[^">]*\/se\d+\.html">(.*?)<\/a>/i,
            childContentInternalLinkPattern:/(<a href="[^">]*\/se\d+\.html">.*?<\/a>)/i,
            childContentSummaryPattern:/<\/A>([\s\S]*?)<\/LI>/i,
            childContentBodyPattern:true,
            
            sectionLinkPattern:/<div class="pagenav">([\s\S]*?)<\/div>/i,
            replace:[
                'NoImageLink',
                [/<img\b[^>]*alt="([^">]*)"[^>]*>([\s\S]*?)<br[^>]*>/ig,'[$1] $2'],
            ],
        },

まず、「childContentPattern」で各子アイテムのHTMLを抜き出します。
「childContentTitlePattern」、「childContentInternalLinkPattern」、「childContentSummaryPattern」は、抜き出した子アイテムのHTMLからタイトル、ページ本体へのリンク、概要を抜き出す定義です。
一覧の項目に本文が丸ごと含まれている場合は、「childContentBodyPattern」で本文の抜き出し定義を指定しますが、この例では本文はリンク先のページから抜き出す必要があるため、「childContentBodyPattern:true」を指定しています。
また、複数ページに分かれた一覧を全て取り出すため、「sectionLinkPattern」も指定しています。
std/atomなどのルールも、このようにして定義されています。


replaceによる置換ルール

contentPatternなどで抜き出した範囲に広告やブログパーツなどの不要部分が含まれてしまう場合は、「replace」に置換ルールの配列を記述して削除などを行います。
配列の要素として単なる正規表現を指定すると、その正規表現に一致する箇所が空文字列に置換されて削除されます。この時、全ての出現箇所を削除するには、正規表現に「g」オプションを付ける必要があることに注意してください。
削除ではなく別の内容に置換したい場合には、「[/<b>([\s\S]*?)<\/b>/ig,'$1']」のように、正規表現と置換文字列の2要素からなる配列を指定します。第2要素には、文字列のreplace()メソッドの第2引数と同じように、関数を指定することも可能です。
よく使われる置換ルールは、「HTMLRewriter」という機能のプラグインで定義されており、ルール名を単に文字列として指定するだけで使用できます。
標準で用意されている置換ルールの一覧と説明は、「plugins\HTMLRewriter\Cleanup\Cleanup.ng」内に書かれています。



画像の保存

contentPatternなどで抽出された本文にimgタグで埋め込まれている画像は、抽出時に自動的に保存されますが、aタグでリンクされているだけの画像は、そのままでは保存されません。
画像が保存されるようにするには、以下のようにして明示的にダウンロードすべきコンテンツのURLであることを指定します。
        image:{
            urlPattern:/^http:\/\/blog-imgs-\d+-origin\.fc2\.com\/.*\.(?:jpe?g|png|gif|bmp)$/i,
            download:true,
        },

本文からリンクされているのが画像ファイル本体でなく画像を表示するためのHTMLである場合には、URLの変換定義を用意することで、画像本体のみを保存し直接リンクさせられます。
URLの変換定義の方式には、「urlRewrite」と「urlRedirect」の2つがあります。
単なるURLの正規表現置換で画像本体のURLが得られ、HTMLにアクセスしていなくても画像に直接アクセス出来る場合は、以下のように「urlRewrite」で置換文字列を指定します。置換後のURLを保存させるための定義も別途必要であることに注意してください。

        image_html:{
            urlPattern:/^(http:\/\/japan\.cnet\.com\/)image\/l\/(.*\.(?:jpg|jpeg|png|gif|bmp))$/i,
            urlRewrite:"$1$2",
        },
        image:{
            urlPattern:/^http:\/\/japan\.cnet\.com\/(?:story_image|image|storage)\/.*\.(jpg|jpeg|png|gif|bmp)$/i,
            download:true,
        },

HTMLのURLから画像本体のURLが定まらない場合や、HTMLにアクセスしてCookieの取得などを行わないと画像本体へのアクセスが出来ない場合などは、以下のように「urlRedirect」に、HTML内から画像本体のURLが含まれる部分を抽出する定義を記述します。この定義で抜き出された範囲に最初に出現するhref=やsrc=のリンク先URLが、変換後のURLとなります。

        softnews_ss:{
            urlPattern:/^http:\/\/www\.vector\.co\.jp\/magazine\/softnews\/\d+\/n\d+_pic\.html$/i,
            urlRedirect:/<td [^>]*>\s*<a href="[^">]*">(<img [^>]*>)<\/a>/i,
        },

また、フィードから抜き出されたURLに「?ref=rss」のようなゴミがくっついている場合なども、urlRewriteによるURLの置換を定義しておくと良いでしょう。


ContentExtractorのルール定義の主要部分の説明は以上です。
まだ説明されていない機能やテクニックもいくつかあるので、「doc\ContentExtractor.txt」の説明や、「plugins\ContentExtractor\」内の標準プラグイン内の実際の定義なども参照してみてください。

出来上がったプラグインを配布したい場合は、ZIPで圧縮してアップロードするなどしてください。そのうち、プラグインの配布や入手、アップデートなどを行う機能も実装される予定です。

0 件のコメント:

コメントを投稿