DOBON.NET DOBON.NETプログラミング掲示板過去ログ

正規表現について教えて下さい。

環境/言語:[xp/vb2005]
分類:[.NET]

2011/09/18(Sun) 05:18:13 編集(投稿者)

どぼんさんの[HTML内のリンクを抽出する]を参考にさせてもらっています。
http://dobon.net/vb/dotnet/string/regexmatch.html

このサンプルコードで、リンクアドレスとリンク文字を抽出は出来ました。

"<a\s+[^>]*href\s*=\s*(?:(?<quot>[""'])(?<url>.*?)\k<quot>|" + _




一つのページにリンクアドレスは沢山あるので、以下のアドレスから始まるリンクアドレスとリンク文字だけを抽出したい場合は正規表現はどう書いたらよいでしょうか?

リンクアドレス
http://rank.hogehoge.jp



一応、[正規表現の基本]を参考にいろいろ試したのですが、上手くいきませんでした。

\sはスペースやTAB
[^>]は、>以外の文字

*、+、?、ここらへんが難しいです。
2011/09/18(Sun) 06:59:31 編集(投稿者)

これだとエラーでダメでした。


"<a\s+[^>]*href\s*=\s*(?://rank.hogehoge.jp(?<quot>[""'])(?<url>.*?)\k<quot>|" + _
"(?<url>[^\s>]+))[^>]*>(?<text>.*?)"←A閉じは省略しています。
こんにちは。
(※掲示板への記述のため括弧が全角です。)

正規表現は慣れていないとさっぱりだと思いますので…
せっかくどぼんさんが名前つきの構成体がある正しく動くサンプルを用意してくださってるのですから、
まずはそのパターンの各部の意味を分解して理解して手を出すべきではないでしょうか。
中身をきちんと把握せずにいきなり本題用の加工を試すようなことをすると、
あからさまにおかしな位置への記述をすることになり更に分からなくなると思います。
(たまたま試したデータで動いたので合っていると思って組み込んでしまい後で大変なことになる羽目にもなります。)

どぼんさんの同ページではとてもチェックが楽な「正規表現テストツール」がダウンロードできます。
このツールでは構成体の名前こそ表示されませんが、
これで実行したら、quote・url・textそれぞれに一体何が入るか把握できますから、
どの部分にhttp://rank.hogehoge.jpを書けばよい(よさそう)か分かります。
元のパターンはクォートありの記述となしの記述に対応するため長くなっています。
(なお、VB用のコードで"が重複してるのはVB用のエスケープですので注意してください。)

改行して分かりやすくすると
<a\s+[^>]*
href\s*=\s*
(?:
    (?<quot>["'])(?<url>.*?)\k<quot>
    |
    (?<url>[^\s>]+)
)
[^>]*>
(?<text>.*?)
</a>
です。
これについて何となく書くと、
<aの後、必ずひとつ以上のホワイトスペースの連続があり(aタグ確定)、
その後ろに>以外の連続の後hrefがあり、
ホワイトスペース文字の連続(なくても可)、
=、
ホワイトスペースの連続(なくても可)、
※そしてここからがURLの本題で、|はORなので、前後でクォートあり・なし用の二種類の記述。
    |の前はクォート付きの記述への対応で、
        「"」ないし「'」をquotの名のグループ化構成体としてキャプチャ
        (※"がふたつ並んでるのはVBで"を文字として使用するためです。)
        あらゆる文字の連続(空でも可)を最短マッチにてurlの名のグループ化構成体としてキャプチャ、
        \k<quot>の記述は前方参照体で、もう一回クォートにマッチ(閉じ側。)、
    |の後ろはクォートなし記述への対応で、
    ホワイトスペース文字と>のいずれでもないもののひとつ以上の連続をurlの名のグループ化構成体としてキャプチャ、
その後Aタグ内の他の属性の部分を読み捨てるのに「>以外の連続」(空も可)、
>、
次に最短マッチであらゆる文字の連続(空も可)をtextの名のグループ化構成体としてキャプチャ、
後は</a>、
と構成されています。

ところで今回の要件にマッチするのを構成する場合、
確かにぱっと見ではこのサンプルからURLの部分をもうちょっと限定すればよいように見えますが、
実際には、クォートあり(前)の方はかなり簡単で普通に可能ですが
クォートなし(後ろ)の方では同じやり方では厳密に正確なものは表現できません。
(URLのマッチ部の外が単純な「[^>]*>」に固定されているためです。)

<a href=http://rank.hogehoge.jp/vb/index.html>1ほげのVBの小部屋トップページ</a>
<a href=http://rank.hogehoge.jp>2ほげのトップページ</a>
<a href=http://rank.hogehoge.jp/>3ほげのトップページ</a>
<a href=http://rank.hogehoge.jp/vb/index.html a>4ほげのVBの小部屋トップページ</a>
<a href=http://rank.hogehoge.jp a>5ほげのトップページ</a>
<a href=http://rank.hogehoge.jp/ a>6ほげのトップページ</a>
<a href=http://rank.hogehoge.jpppvb.com/index.html a>7ほげのVBの小部屋トップページ</a>
↑これはマッチしないで欲しい
<a href="aiueo">8sdfsffs</a>
↑これはマッチしない
<a href="http://rank.hogehoge.jp/vb/index.html">9ほげのVBの小部屋トップページ</a>
<a href="http://rank.hogehoge.jp">10ほげのトップページ</a>
<a href="http://rank.hogehoge.jp/">11ほげのトップページ</a>
<a href='http://rank.hogehoge.jp/vb/index.html'>12ほげのVBの小部屋トップページ</a>
<a href='http://rank.hogehoge.jp'>13ほげのトップページ</a>
<a href='http://rank.hogehoge.jp/'>14ほげのトップページ</a>

頑張って書き換えて上のリストを処理すると分かりますが、何となく絞り込みを記述した形では、
2を拾おうとすると「[^>]*>」の都合で7が途中でぶった切られた形でついてきてしまうか、
7が来ない代わりに2も来なくなってしまいます。

作るソフトの対象HTMLが限定的でそもそもクォートも必ずあるなら一気に短縮して
<a\s+[^>]*
href\s*=\s*
(?:
    (?<quot>["'])(?<url>http://rank\.hogehoge\.jp(/?|/.*?))\k<quot>
)
[^>]*>
(?<text>.*?)
</a>
だけでよいでしょうが、用途が汎用で万人向けツールならはっきりいってだめだと思いますし、
結局用途・要求の範囲次第です。
ちなみに
<a\s+[^>]*
href\s*=\s*
(?:
    (?<quot>["'])(?<url>http://rank\.hogehoge\.jp(/?|/.*?))\k<quot>
    |
    (?<url>http://rank\.hogehoge\.jp/[^\s>]+)
    |
    ((?<url>http://rank\.hogehoge\.jp/?)\s+)
    |
    (?<url>http://rank\.hogehoge\.jp/)
    |
    (?<url>http://rank\.hogehoge\.jp)
)
[^>]*>
(?<text>.*?)
</a>
とすると2が取れるけど7もhttp://rank.hogehoge.jpまででぶった切られた状態でマッチしてしまうパターン、
逆に最後のORの行をなくすと7は来ませんが2もマッチしなくなります。


汎用の場合、
サンプルのパターンはベースにしないで要件用のパターンを最初から作成するか、
もしくは、
サンプルのまま一旦マッチさせてそのうちURLの部分に「"」なり「'」がないものは補った状態にしつつ、
マッチした分だけのデータを抽出して今度はそのデータに対し、
上に書いたクォートあり専用の短い絞り込みのパターンを実行するという手もあります。
…何度手間になり実行コストはもったいないかもしれませんが、
対象がHTMLテキスト等の場合はサイズがせいぜいしれてる場合が多いので
ややこしいのが嫌ならこういう方法でもよいと思います。(処理するデータ量次第です。)


ちなみに、汎用の場合は処理対象のHTMLがまちまちなので、
HTMLではタグの中でも好き勝手改行できますから検出処理に入る前に適切にデータを整形しておく必要があります。
また、不整なHTMLを読むと途中から狂います。

※なお、分かりやすいようパターンは改行してありますが、実際にはくっつけないと使えませんので。
また、VBでは"はエスケープする必要があるのでふたつ必要です。(注意です。)
■No29065に返信(ザナドゥさんの記事)
> 2011/09/18(Sun) 06:59:31 編集(投稿者)
>
> これだとエラーでダメでした。
>
>
> "<a\s+[^>]*href\s*=\s*(?://rank.hogehoge.jp(?<quot>[""'])(?<url>.*?)\k<quot>|" + _
> "(?<url>[^\s>]+))[^>]*>(?<text>.*?)"←A閉じは省略しています。


エラーがでるのは ?:の直後に//rank.hogehoge.jpを書くと

<a href = //rank.hogehoge"...">...</a>

を抽出することになるからでは?
とん。さん、返信有難う御座います。

とても分かりやすくご説明いただき感謝です。
「正規表現テストツール」は、そういうふうに利用できるのですね。
こんな便利なものとは知りませんでした。


|はorというのを最近知ったぐらいのレベルなので、大変助かりました。
これは何度も試してみないと分からないですね。

アドバイスの通り、一度マッチさせて、そこから再度絞り込んで抽出という方法を取りたいと思います。

有難うございました。




itiさん

返信有難う御座います。

初歩的な、.の前に\を入れ無効化する処理もしていませんでした。

もっと勉強します。
解決済み!
2011/09/18(Sun) 22:14:55 編集(投稿者)
2011/09/18(Sun) 22:13:37 編集(投稿者)

こんばんは。
追加を書いてる間に今返信が入ってしまいましたが…

既に確認できているかもしれませんが、ザナドゥさんので(.を\.にしても)マッチするのは、
例えば
<a href=//rank.hogehoge.jp"http://www.hyahoo.co.jp/">やふー。</a>へのリンクはここです。<br />文字をクリックしてね。<br />
なんて行等になってしまいますよ。実際にはこのような文はありえませんので、正常なHTMLではこれにヒットするものはないでしょう。
しかも、マッチしてしまうようなものがあったとしても、
(?<text>.*?)では最短マッチングなので何も入らないと思います。
かといって?を消して最長マッチングにすると今度は行末まで入ってしまうので、
a閉じタグ用の指定は省略しては駄目なものです。
(リンク内に他のタグを使ってる記述の可能性もありますから、「.」を「[^<]」にしても対処できません。)


一応、どぼんさんのものはベースになってないパターンですが名称を踏襲した記述を挙げておきます。
いくつも方法はあるでしょうし、もっときれいで安定した記述もあるかもしれません。
(例によって投稿できないので開始括弧は全角です。変換してください。)

<a\s+(?:.*?)href\s*=\s*(?<quot>["']?)(?<urlfull>http://rank\.hogehoge\.jp(?<usiro>(/[^\s"']*?)?|([^/\s"']+?)))\k<quot>?(?:\s+[^>]*?)?>(?<text>.*?)</a>
(※VB.NETで"をダブらせるのを忘れないでください。)
念のため後ろ側をキャプチャする構成体もusiroとして用意してあります。


あ、上で注意しながら肝心な部分の処理が自分で抜けてました。
リンクタグ内で別タグを書いている場合に対応するため、
最後のtextは(?<text>.*?)ですよね。ということで上のパターン修正しました。
…こんな風に、私も色々ミスをやらかします。(すみません。)
■No29070に返信(とん。さんの記事)

> <a\s+(?:.*?)href\s*=\s*(?<quot>["']?)(?<urlfull>http://rank\.hogehoge\.jp(?<usiro>(/[^\s"']*?)?|([^/\s"']+?)))\k<quot>?(?:\s+[^>]*?)?>(?<text>.*?)</a>
> (※VB.NETで"をダブらせるのを忘れないでください。)


とん。さん、返信有難う御座います。


間違ってたらすみません。

"→""ですね?
(そうですよ。私が書いたのはパターンそのものですので、VBで使うならこれにひとつ追加してください。)
■No29074に返信(とん。さんの記事)
> (そうですよ。私が書いたのはパターンそのものですので、VBで使うならこれにひとつ追加してください。)


有難うございます。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板