[BlueLeaf1336]> PROBLEMS> リンクタグ作成 HrefBuilder>

リンクタグ作成 HrefBuilder > 基本処理作成

historyTOP

2005/09/04:作成
2005/09/04:更新
2005/09/05:更新
2005/09/06:更新
2005/09/07:更新

2005/09/04TOP

(前回とのつながりがあんまりないですが)ページデータをダウンロードした時に、ちゃんとサーバーの戻すプロパティを確認することにします。つまり、ステータスコードと、テキストデータかどうか、文字セットです。ただし、文字セットについては確実に存在するとは限らないので微妙ですが。

その上で、タイトルとできれば説明を取得することにします。

データの抜き出しについては、BlueLeaf1336-PROBLEMS-2004_0048 > MizuhoGetter > HTMLソースの解析(3)に置いてあるソースに無断で含んでいる、今はなきページ(http://hp.vector.co.jp/authors/VA009712/take/delphi/msregexp.html)で公開されていたバグ修正版のユニットファイルを使って、VBScriptのActiveXを使った正規表現でやることにします。

以下は、参考になりそうなページです。

とりあえず、Peggyを使って正規表現の練習をすることにします。ただし、改行は落としておくことにします。

正規表現試行錯誤TOP

抽出対象Peggy
ページタイトル(タグ含む)<TITLE>[^<]*</TITLE>
説明(タグ含む)<META[ ]+NAME="description"[ ]+CONTENT=[^>]*>

自分のためのメモ。

1つ目は「<TITLE>で始まる。</TITLE>で終わる。」を実現したい。そのために開始タグと終了タグの間を「"<"以外のあらゆる文字を見過ごす」としてスキップする。

VBScriptで試行錯誤TOP

//  TRegExpを使用するため
uses
    VBScript_RegExp_55_TLB;

procedure TMainForm.BitBtn1Click(Sender: TObject);
var
    FRegBuf: TRegExp;
    Data: string;
    Matches, Match: OleVariant;
begin
    //  作る
    FRegBuf := TRegExp.Create(nil);
    //  1つ目だけを探す
    FRegBuf.Global := false;
    //  大文字・小文字無視
    FRegBuf.IgnoreCase := true;
    //  改行を落とす
    Data := Memo1.Text;
    Data := StringReplace(Data, #13, '', [rfReplaceAll]);
    Data := StringReplace(Data, #10, '', [rfReplaceAll]);
    //  タイトルタグの抜き取りのためのパターン
    FRegBuf.Pattern := '<TITLE>[^<]*</TITLE>';
    //  検索実行(と検索結果コレクション受け取り)
    Matches := FRegBuf.Execute(Data);
    //  発見した件数を調べて
    if (Matches.Count > 0) then
    begin
        //  取り出す
        Match := Matches.Item[0];
        //  表示する
        ShowMessage(Match.Value);
    end;
    //  捨てる
    FreeAndNil(FRegBuf);
end;

正規表現試行錯誤2TOP

このままだと、タグも含んでしまうので何とかタグを落としておきたいものです。もちろんここまでくれば落とすのは簡単でしょうが、せっかく正規表現を使っているので何とかやってみます。

このあたりをよく読んでみると、「個別のサブマッチ文字列」なんてものがあるようです。そしてこれはどう考えても、Pattern プロパティの「(pattern)...引数 pattern に指定した文字と一致します。一致する文字列が見つかったら、記憶されます。」に対応していると。

抽出対象Peggy
ページタイトル<TITLE>([^<]*)</TITLE>

以前との違いは、タイトルの文字列が入っている部分を「括弧」で囲んだということだけです。ただし取り出し方が異なります。例として

<TITLE>これはサンプルです</TITLE>

というタグを含むページがあったとします。このページの全ての文字列を抜き出したとして、パターンには上記の括弧付きのタイトルタグ用表現を使用して、Executeメソッドを実行し、Matches(: OleVariant)で結果を受けとることにします。

Matches := FRegBuf.Execute(ページ文字列);

すると、「Matches.Count = 1」になるはずで、「Matches.Item[0]」に検索結果が収められています。この検索結果を、Match(: OleVariant)で受けます。

Match := Matches.Item[0];

長くなってきましたが、ここで「Match.Value」には何が入っているのか? このプロパティで取り出せるのはパターンに一致した全体ということになります。つまり、

<TITLE>これはサンプルです</TITLE>

です。これが、最初に示したコードの限界ですが、今は更に一段進んで、サブマッチ文字列を取得できるように、パターンに「括弧」をつけています。括弧は「<TITLE>」と「</TITLE>」の隙間に挟まれた文字列全体を囲んでいるので、この例の場合だと、

これはサンプルです

となり、まさにタイトルの文字列を取得できることになります。この文字列へのアクセス方法は次のようになります。(あまり必要ではないのですが、ダラダラとプロパティをつなげるよりも、後で見たときにどのようなコレクションやオブジェクトを使っているつもりなのかをはっきりさせたいために)「Match.SubMatches」を SubMatches(: OleVariant)で受けとります。

SubMatches := Match.SubMatches;

そして、念のために「SubMatches.Count > 0」を確認した後で、

SubMatches.Item[0]

にアクセスすると、「これはサンプルです」だけを取り出すことができます。注意しないといけないのは、ここまでのように「SubMatches.Item[0] を SubMatch オブジェクトで受けて SubMatch.Value としてアクセス」するとエラーになるということです。

SubMatches.Item[0] は単なる文字列です。

結局のところ、最初に示したコードよりも、次のように書くほうが後が楽ということになります。※先のコードもこのコードも共に、妄想だけで書いているのでそのままコンパイルできるかどうかは知りません。後で、少なくともコンパイルは通るプロジェクト一式を載せます。

procedure TMainForm.BitBtn1Click(Sender: TObject);
var
    FRegBuf: TRegExp;
    Data: string;
    Matches, Match: OleVariant;
begin
    FRegBuf := TRegExp.Create(nil);
    FRegBuf.Global := false;
    FRegBuf.IgnoreCase := true;
    //  改行を落とす
    Data := Memo1.Text;
    Data := StringReplace(Data, #13, '', [rfReplaceAll]);
    Data := StringReplace(Data, #10, '', [rfReplaceAll]);
    FRegBuf.Pattern := '<TITLE>([^<]*)</TITLE>';
    Matches := FRegBuf.Execute(Data);
    //  見つかったら
    if (Matches.Count > 0) then
    begin
        Match := Matches.Item[0];
        //  さらに
        SubMatches := Match.SubMatches;
        if (SubMatches.Count > 0) then
        begin
            ShowMessage(SubMatches.Item[0]);
        end;
    end;
    FreeAndNil(FRegBuf);
end;

同様に、説明のほうも考えてみると、次のようなものになりそうな気がします。

抽出対象Peggy
説明<META[\s]*NAME[\s]*=[\s]*"?[\s]*description[\s]*"?[\s]*CONTENT[\s]*=[\s]*"?[^>"]*"?[\s]*>

さすがにこっちは説明を書いておきます。後で自分が見て意味不明になることが確実だからですが。

<META[\s]*NAME[\s]*=[\s]*"?[\s]*description[\s]*"?[\s]*CONTENT[\s]*=[\s]*"?[^>"]*"?[\s]*>

「<META」で始まって、半角スペースやタブなど(何個でも)

<META[\s]*NAME[\s]*=[\s]*"?[\s]*description[\s]*"?[\s]*CONTENT[\s]*=[\s]*"?[^>"]*"?[\s]*>

「NAME=description」もしくは「NAME="description"」。正確には「"」は片方だけでもよいが、文法上おかしい。また「=」の前後には空白があってもよい。

<META[\s]*NAME[\s]*=[\s]*"?[\s]*description[\s]*"?[\s]*CONTENT[\s]*=[\s]*"?[^>"]*"?[\s]*>

「CONTENT=」

<META[\s]*NAME[\s]*=[\s]*"?[\s]*description[\s]*"?[\s]*CONTENT[\s]*=[\s]*"?[^>"]*"?[\s]*>

「"」と「"」で挟まれているもの。ただし「"」がない場合は、終了タグまで。説明できん。

HTTPステータス・コードTOP

ところで、以前作成した時は「WinInet.dll」を使用していたので、このページ(Welcome to MSDN Library)のタイトルはちゃんと取得できていたのですが、「WinSock」でソケット作って...の方でやると、サーバーが「302」を返してきやがります。「リクエストされたリソースは、一時的に異なるURIに属する」らしいですが、うーん。

これ見ると、「200以外が失敗」なんて考えちゃ駄目だとわかります。それに、まだ、サーバーの戻してきた「ファイルの種類」や「文字コード」を全く考慮してません。あー。先長そう。

HrefBuilder20050904.zip(27,018bytes) ソースコードです。

2005/09/04 その2TOP

サーバーのレスポンスから「Content-Type」と「charset」を抜き出します。どうも、この2つはセットになっているらしく、「charset」がないこともあるような。とりあえず「Content-Type」を取り出す場合は、改行コードを残したままで

抽出対象Peggy
Content-TypeContent-Type:[\s]*([^;|\n])*(;|\n)

でいけそうです。注意するのは、SubMatch(前述)の Item[1] は無視すべきだということです。ついでに、charset があればそれも取り出せるように考えてみます。

抽出対象Peggy
Content-Type と charsetContent-Type:[\s]*(([^;|\n])*)[;|\n][\s]*(charset[\s]*=[\s]*(([^\n])*)\n)?

括弧(パターン)表現が増えたので、SubMatchのどれに意味があるのかを調べてみます。また、末尾が改行であることを頼んでいます。

止めました。一括で抜こうとするよりも個別にやる方が素直だし、SubMatchの何番目みたいな異様なことをしなくてもよくなるので、次のようにすることにします。また、サーバーのレスポンスだけを見たいのですが、本文が戻ってきてるとマジメなソースのメタタグにひっかっかってしまうようです。

どんどんおかしなことになりますが、ダウンロード後に、ヘッダとボディに分割するようにします。もしくはヘッダはヘッダでダウンロードするとか?

抽出対象Peggy
Content-TypeContent-Type:[\s]*([^;|\n]*)[;|\n]?
charsetcharset[\s]*=[\s]*([^\n]*)[\n]?

なんかもう、説明に意味がなくなってきたのでだいたいで進めることにします。

HrefBuilder20050904_2.zip(27,429bytes) ソースコードです。

やはりかなりおかしい。WinInet.dll使用版では何の問題もないのに、WinSockでダウンロードすると「404」が戻ってきたり、と、ひょっとしてコマンドが間違ってるんじゃないか?と思い、調べてみました。

今までは、「GET /index.html HTTP/1.0」としており、それでちゃんと取得できるページも存在するんですが、念のため「GET http://www.hogehoge.co.jp/index.html HTTP/1.0」に直してみました。すると、取れるようになったページが出てきました。たとえば、とほほのWWW入門とかです。最初のページのみ指定した場合は、タイトルが「Reflesh...」となってしまい、何か別のものをとってきてしまってたんですが、URLそのものを渡すようにしてやると「とほほのWWW入門」と取れるようになりました。でも、モーバイルインフォサーチ実験とはとかWelcome to MSDN Libraryとかはまだ取れません。何でだろう。

2005/09/05TOP

とてもわかりやすいページを見つけました。

今までは

GET http://.../index.html HTTP/1.0

もしくは

GET /index.html HTTP/1.0

にしてたんですが、サーバーによっては「403 Forbidden」を返したり「200 OK」を返したり。片方でしかちゃんと応えてくれないサーバーがいる上に、上なら応える、あるいは下なら応える、といった嫌がらせとしか思えない感じに打ちひしがれていましたが、このページを参考に、GETコマンドを

GET /index.html HTTP/1.0
Host: www.host2.com:80

形式に変えたところすっぱり改善されました。でもまだWelcome to MSDN Libraryの返す「302 Object moved」に対応できてません。「Location:」を追いかけても再び「302」。どうせぇと?

また、あるページのサーバーのレスポンスを見ても、本文のMETAタグを見ても、そのページの文字コードがわからないときってどうすればよいのか判断しかねています。いまは明示的に書いてある場合(EUCしかチェックしてませんが)は、それなりに。明示的に書いてない場合は「JConvert.pas :: 全自動コード変換(SJIS)」を無条件に通しています。でも何とかしたいです。

HrefBuilder20050905.zip(27,176bytes) ソースコードです。デバッグ用のダイアログがバンバン表示されるバージョン(?)です。

2005/09/06TOP

そろそろ次のページに移動したいところですが、(先に進んでないので...)書くこともないのでこのまま継続します。で、やっと処理の流れも落ち着いてきて、「302」にも対応したのはよいのですが、クダンのマイクロソフトのページをダウンロードしようとすると、それは違うだろうあきらかになんだ嫌がらせか、と納得しそうなへんてこページを指示されて、そっちをダウンロードしてしまう。しかも、ステータス200の成功コードが戻ってくる。

当然ブラウザやWininet.dllで問題ないにも関わらず。何でだろう。

HrefBuilder20050906.zip(27,675bytes) ソースコードです。

2005/09/07TOP

相変わらずマイクロソフトのページ(MSDNライブラリだけ?)が取れなくて困っているんですが、GETコマンドに何か足りないんじゃないのか、と思ってのんべんだらりと調べていたところ、次のページを見つけました。

あっ、と思って自分のサイトを見直してみると、BlueLeaf1336-PROGRAM-2003_0017 WEBサーバを作ろう-002 こんなことをやってました(もちろん作れませんでしたけど)。ここの下の方で、自分で使ってるブラウザがどんなリクエストをWEBサーバに投げているのかを確認しています。

そういえば、Wininet.dll を使うときも、User-Agentを指定したことを思い出してきました。コレじゃあないかと。マイクロソフトの特定のページがつれない返事を戻してくる原因は。

なんかもう嫌になるぐらい近くにありました。いつも参考にさせてもらってるだけでなく、まさにテストに使ってるサイト(とほほのWWW入門)に非常に詳しく書いてました。もーなんかもー。

これが原因であってくれ。横着GETコマンドが原因でありますように。

原因でした。

GET /xxx.html HTTP/1.0
HOST: www.xxx.com:80
User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0) <= THIS!!
<改行>

そういえば、User-Agentを偽装しないとマイクソロソフトのサイトは見れないとかなんとかいう話を、どっかで見たことあるようなないような...解決。やっと本来やりたいところにすすめられます。

HrefBuilder20050907.zip(217,423bytes)(27,714bytes) ソースコードと実行ファイルです。

このページはここまで。

EOFTOP