読者です 読者をやめる 読者になる 読者になる

いまさら聞けない? 初心者向けPlagger設定覚え書き その3

いまPla*1 その3です。
その1では「Filter::EntryFullText」について、その2では「CustomFeed::Config」について扱ってきました。今回はCustomFeed::Configで抽出したいサイトが複雑な場合にどうやって対応するかということに焦点を当てていきます。

その2ではサンプルとしてまなめはうすさんからニュースのタイトル、コメント、リンクを抽出するyamlファイルを作成しましたが、紹介したファイルでは日付が変わる部分での取得がうまくいかない、またニュースしか取得できないため雑記を読むことができないという欠点がありました。1つめに関しては正規表現を工夫すればなんとでもなりますが、2つめに関してはニュース部と日記部分でパターンそのものが違うので取得することが難しそうです。特に2つめの問題を解消する方法に関して取り扱います。


その2で作ったCustomFeed::Config用のyamlファイルは↓でした。
maname.yaml

author: Toshi
match: http://homepage1\.nifty\.com/maname/
extract: <DIV CLASS="news">(.*?)<BR /><A HREF="https?://[^"]*">(.*?)</A><BR />.*?<DIV CLASS="newsc">(.*?)</DIV>
extract_capture: title link body


これはextractでニュースとコメントにマッチするような正規表現が書いてあります。単純にそれだけ。ちなみにコメントがない場合には当然マッチしないのでニュースを取得できません。これを改善するために↓のようなコードを書きました。
maname_full.yaml

author: Toshi
match: http://homepage1\.nifty\.com/maname/
extract: <DIV CLASS="news">(.*?)</DIV>|<FONT COLOR="BLACK">((?!ニュース)*?)</FONT></A></DIV><BR /><DIV class="dm2">(.*?)</DIV>
extract_capture: news title text
extract_after_hook: |
  $data->{news} =~ s/\n//g;
  if($data->{news} =~ m|(.*?)<BR /><A HREF="https?://[^"]*?">(.*?)</A><BR />(.*?)<DIV CLASS="newsc">(.*)|){
    $data->{title} = $1;
    $data->{link} = $2;
    $data->{body} = $3;
    $data->{body} .= "<br />" . $4;
  }elsif($data->{news} =~ m|(.*?)<BR /><A HREF="https?://[^"]*?">(.*?)</A><BR />|){
    $data->{title} = $1;
    $data->{link} = $2;
  }elsif( length($data->{title})>0 && length($data->{text})>0){
    $data->{body} = $data->{title};
    $data->{body} .= "<br />" . $data->{text};
  }else{
    $data->{body} = $data->{news};
  }

ちょっと長いけど、たいしたことはしていません。

extract行
マッチさせたいものがニュース部分と日記部分、2種類あるのでOR演算子「|」で区切ってそれぞれに対応する2種類の正規表現を書きます。これでどっちかにマッチすれば抽出することができます。前半部分がニュース部分で後半が日記部分です。ニュース部分はコメントありなし両方に対応できるように緩くしてあります(ニュースとコメントごとまとめて取得してる)。日記部分の正規表現に[^ュ]と入っているのは「ニュース」という大見出しを取得するとおかしくなってしまう関係上つっこんであります。だからニュース以外の大見出しに「ュ」があると取得できません。「ニュース」という文字列が入らない文字列を意味する(?!ニュース)に変更しました(コメントでid:otsuneさんに指摘いただきました)。
extract_capture行
extractで2つの正規表現を並べましたが、抽出したものを代入する変数は種類毎に書くのではなくまとめて前から数えます。日記部分の最初のカッコ、([^ュ]*?)は都合2つめとなってtitleに代入されるというわけです。
extract_after_hook行?
コロンの後にスペースをあけて「|」のあと改行するとPerlの書式で本格的にいじることができます。ここではnewsにコメントが入っているかどうかの分岐と日記部分に文字が入っているかどうかを確認してbodyに入れています。Perl正規表現については色々なサイトで調べることができるので検索してみると良いと思います。

ちなみにconfig.yamlはその2で使ったものをそのまま使って大丈夫です。

今回のポイント
2種類以上のパターンに対応させたい場合にはOR演算子(|のことです)で区切って正規表現を2つ以上書くことができる。そのときのextract_captureは種類にこだわらず前から順番に全部並べて書く。


おんなじように考えて、everything is goneさんの日記とニュースを取得するyamlも書きました。
egone.yaml

author: Toshi
match: http://egone\.org/
extract: <p class="n[bgo]" style="[^"]+?"><a href="([^"]+?)" [^>]+?>((?:(?!</a).)*?)</a>(?:&nbsp;)?((?:(?!<p).)*?)</p>
<p class=dakome>(.*?)</p>|<p class="n[bgo]" style="[^"]+?"><a href="([^"]+?)" [^>]+?>(.*?)</a>(?:&nbsp;)?(.*?)</p>
|<p class=diary [^>]+?><b>(.*?)</b><br>(.*?)</p>
extract_capture: link title quote comment link title quote date diary
extract_after_hook: |
  if(length($data->{quote})>0){
    $data->{body} = $data->{quote};
    if(length($data->{comment})>0){
      $data->{body} .= "<br />" . $data->{comment};
    }
  }elsif(length($data->{date})>0 && length($data->{diary})>0){
    $data->{title} = $data->{date};
    $data->{body} = $data->{diary};
  }
extract
3つの正規表現が書いてあります。1つめがコメントありニュース、2つめがコメント無しニュース、3つめが雑記となっています。正規表現に重複がある場合、長くマッチする方から先に書いた方が良いと思います。(改行していますが本当は1行に書きます)
extract_capture
ここでは3つ分の正規表現による抽出したものを代入する変数を前から順に書いてあります。全部で9つもありますね。
extract_after_hook
引用があるかどうか、さらにコメントがあるかどうかを確認してbodyに代入しています。もしかしたらここは並列の方が良いかもしれないですね。最後に日記部分に何か代入されていたときにそれをbodyとtitleに代入しています。

このyamlでニュースと日記両方をうまく取得することができました。最終兵器PlaggerさんがCustomFeed::Config.pmを改造して対応していますがこっちの方がわかりやすいかなとおもいます。CustomFeed::Configを改造した方がきっと柔軟なんだろうけど。

config.yamlはこんなの。

global:
  assets_path: /PATH/Plagger/assets
  timezone: Asia/Tokyo
  log:
    level: debug

plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://egone.org/

  - module: CustomFeed::Config

  - module: Filter::Rule
    rule:
      module: Deduped

  - module: Publish::Gmail
    config:
      mailto:   xxx@gmail.com
      mailfrom: xxx+plagger@gmail.com

これで大体のニュースサイトには対応できるんじゃないかな?


いまPla シリーズ 目次はこちらから

*1:いまさら聞けないPlagger