Perlの正規表現についての質問です。ある掲示板のデータをPerlのLWP::UserAgentを使い、取得して保存するスクリプトを作ろうとしています。


掲示板にアクセスしてHTMLデータを取得するところまではできました。これをそのまま保存するだけではなく、「タイトル」「投稿者名」「投稿時間」「本文」といった具合に該当箇所だけを抜き出して保存したいと思っています。

そうなると、HTML全データ($dataとします)からパターンマッチでそれぞれのデータのみを取得する必要がありますが、この$dataからそれぞれのデータをパターンで抜き出す方法が分かりません。

現在までにやってみた方法です。
$data =~ /(タイトルを抜き出すパターン)/g;
$data =~ /(名前を抜き出すパターン)/g;
...
$title = $1;
$name = $2;

しかしこれだと、後方参照の最後の値のみが記憶されているのみで、意図するような結果が得られません。こんな風に、ひとかたまりのデータから複数のパターンマッチで抜き出すには、どうすればよいのでしょうか。ざっと調べただけでは分かりませんでした。よろしくお願いします。

回答の条件
  • 1人2回まで
  • 登録:2008/08/17 02:02:25
  • 終了:2008/08/18 01:28:54

ベストアンサー

id:tezcello No.4

tezcello回答回数459ベストアンサー獲得回数692008/08/17 22:40:14

ポイント24pt

ソースを見てみました。

目的の投稿内容は、途中に改行を含んでいますね。

.(ピリオド)は改行文字を含まないのではありませんか?

  perl から離れて久しいので、チョット調べてみました。

  /s を付けないと改行は含まれない様ですね。

  http://www.kt.rim.or.jp/~kbk/perl-5.8/perlreref.html

id:mine-D

おお!おっしゃるとおり/sをつけてみたら本文取れました!すごい。

「掲示板を新しくしたので<br>トです。」

という感じで改行周辺で抜け落ちてしまっていますが、もう少しやってみますね。うーむやっぱり聞いてよかった。ありがとうございます。

追記:文字が抜け落ちる現象は、どうもUTF8のフラグが怪しいかなぁという印象なので、上記のencode部分を全部euc-jpにしてみたら、無事取得する事ができました。お答えいただいたみなさん、ありがとうございました。はまってしまっていたので大助かりです。

2008/08/18 01:27:36

その他の回答(3件)

id:rubikitch No.1

るびきち回答回数120ベストアンサー獲得回数222008/08/17 02:34:26

ポイント23pt

正規表現マッチの後で変数に代入する。

$data =~ /(タイトルを抜き出すパターン)/g;
$title = $1;
$data =~ /(名前を抜き出すパターン)/g;
$name = $1;
...

あるいは先読み正規表現を使いましょう。

$data =~ /(?=.*?タイトルを抜き出すパターン)(?=.*?名前を抜き出すパターン)/g
# gはいらないかも
$title = $1;
$name = $2;
id:mine-D

ありがとうございます!

先読み正規表現というのは初めてで、ちょっとググッたところすぐには理解できそうにない感じがしたので、正規表現マッチの後で変数に代入する方法でやってみました。

ところが、$1に入るデータが重複するという現象が起きてしまいました。書き込み日時や書き込み内容も取得するので、それらもまとめると

$data =~ /(タイトルを抜き出すパターン)/g;
$title = $1;
$data =~ /(名前を抜き出すパターン)/g;
$name = $1;
$data =~ /(日時を抜き出すパターン)/g;
$date = $1;
$data =~ /(内容を抜き出すパターン)/g;
$content = $1;
print $title, $name, $date, $content;

こんな感じになったのですが、結果は「タイトル、名前、日時、日時」と表示されてしまいます。うーむ…。

追記:どうも、最後の$contentがうまくマッチできていないだけのような気がしてきました。もうちょっとやってみます。

2008/08/17 14:08:53
id:b-wind No.2

b-wind回答回数3344ベストアンサー獲得回数4402008/08/17 02:35:44

ポイント23pt

文字列が出現する順番が決まっているなら、

$data =~ /(タイトルを抜き出すパターン).*?(名前を抜き出すパターン)/gm;
$title = $1;
$name = $2;

とでもしておけばいいはず。


順番が決まっていないなら、地道に

$data =~ /(タイトルを抜き出すパターン)/g;
$title = $1;
$data =~ /(名前を抜き出すパターン)/g;
$name = $1;

とでもするしかないかな。

id:mine-D

b-windさんいつも感謝です。

最初のやり方はrubikitchさんがおっしゃる先読み正規表現という事でよろしいでしょうか。実はまったく知らなかったので、分かりやすく説明しているサイトがあれば、ポインタ示していただけると助かります。ざっとググってみたんですが今ひとつ理解できませんでした。

後の方のやり方では、上で述べたように$1に重複してデータが入ってしまうようです。

2008/08/17 13:56:49
id:rubikitch No.3

るびきち回答回数120ベストアンサー獲得回数222008/08/17 15:47:20

ポイント21pt

RubyだがPerlとほぼ互換の正規表現を使っているので理解の助けになれば。

id:mine-D

ありがとうございます。具体的に書かないと分かりにくいですよね。特に問題ないと思いますので、取得したい掲示板のアドレスと、今書いているコードをそのまま書きます。

http://otd1.jbbs.livedoor.jp/18972/bbs_plain?base=1&range=1

こちらの掲示板なのですが、上記パラメータのbase=1の部分をインクリメントして行って、3000件くらいある書き込みデータを引っ張ってこようとしています。

かなり古いHTMLなので名前やタイトルを取得するのにフォントタグ等のパターンで判別するしかないという状態です。

現時点でのコードです。まだループはしていません。

 #!/usr/bin/perl

use strict;
use Encode;
use Encode::Guess qw/euc-jp shift-jis/;
use LWP::UserAgent;

my $ua = LWP::UserAgent->new;

my $url = 'http://otd1.jbbs.livedoor.jp/18972/bbs_plain?base=        1&range=1';

my $request = HTTP::Request->new('GET', $url);

$ua->request($request, \&parse);

sub parse{
  my($data, $response, $protocol) = @_;
  my $decoder = Encode::Guess->guess($data);
  my $string = $decoder->decode($data);

  chomp $data;

  $string =~ /<a name="(.+)">/g;
  my $serial=  $1;

  $string =~ /<font color="#ff0000">(?!Powered by)(.+)<\/font>/g;
  my $title =  $1;
  $title = encode('utf8', $title);

  $string =~ /<font color="#000080">(.+)<\/font>/g;
  my $name =  $1;
  $name = encode('utf8', $name);
	
  $string =~ /<td nowrap>(.+)<\/td>/g;
  my $mydate = $1;
	
  $string =~ /<font color="#006000">(.+)<\/font>/g;
  my $content = $1;
  $content = encode('utf8', $content);
	
  print $serial, $title, $name, $mydate, $content;
}

結果は「1新しい掲示板のテストREDHOT1999/11/07 03:291999/11/07 03:29」となり、最後の$contentがきちんと取得できていないようです。

何度も見直したのですが、なぜマッチしないのか分かりません。見落としている点があると思いますので、ご指摘いただければ幸いです。

2008/08/17 16:18:05
id:tezcello No.4

tezcello回答回数459ベストアンサー獲得回数692008/08/17 22:40:14ここでベストアンサー

ポイント24pt

ソースを見てみました。

目的の投稿内容は、途中に改行を含んでいますね。

.(ピリオド)は改行文字を含まないのではありませんか?

  perl から離れて久しいので、チョット調べてみました。

  /s を付けないと改行は含まれない様ですね。

  http://www.kt.rim.or.jp/~kbk/perl-5.8/perlreref.html

id:mine-D

おお!おっしゃるとおり/sをつけてみたら本文取れました!すごい。

「掲示板を新しくしたので<br>トです。」

という感じで改行周辺で抜け落ちてしまっていますが、もう少しやってみますね。うーむやっぱり聞いてよかった。ありがとうございます。

追記:文字が抜け落ちる現象は、どうもUTF8のフラグが怪しいかなぁという印象なので、上記のencode部分を全部euc-jpにしてみたら、無事取得する事ができました。お答えいただいたみなさん、ありがとうございました。はまってしまっていたので大助かりです。

2008/08/18 01:27:36
  • id:rubikitch
    具体的なデータがあれば解決は早いのだが…
    正規表現マッチがうまくいかない原因は、欲張りマッチと非欲張りマッチの関係かもしれぬ。

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

絞り込み :
はてなココの「ともだち」を表示します。
回答リクエストを送信したユーザーはいません