[BlueLeaf1336]> PROBLEMS> MizuhoGetter>

MizuhoGetter > HTMLソースの取得(3)

historyTOP

2004/11/03:作成
2004/11/04:更新

2004/11/03TOP

すごく横着ですが、過去の当選番号データの入っている全て(のハズ)のデータをダウンロードする処理だけ作成しました。

何が横着か、というと、宝くじコーナー・ロト当せん番号案内のページから、ロト/ミニロトの当選番号データページへのリンクが全て取れると仮定していること、また、リンクはこのトップページと同一のディレクトリに対するリンクだと仮定していること、全てのリンクは「href="」から「"」の間に改行なしに挟みこまれていると仮定していること、それ以外のリンクが含まれていないと仮定していること、などです。ナンバーズの方も同様に、宝くじコーナー・ナンバーズ当せん番号案内をスタートとして、(同一関数で処理してますので)完全に同じ前提条件が成り立つとしています。

それから、取得途中でのキャンセルとか一切無理です。エラーが起きたりしたらどうなるかも知りません。

でもまあ、うまくいっているような感じです。次は、データの取出しに進みます。

20041103MizuhoGetter01.png実行中

20041103MizuhoGetter02.png取得したファイル達

20041103MizuhoGetter.zip(6,173bytes)※ソースコードと実行ファイル

2004/11/04TOP

さて、データの解析です。とはいっても、ダウンロードしたファイルには7種類あります。(赤太字は連番や日付)

おまけに全種類ともフォーマットが異なるようです。しかも(もちろんですが)全部タグのついたHTMLでかかれています。どうやって解析するのが楽なのかわからないんですが、やはりココはタグを一旦飛ばしてしまって考えるのがよさそうな気が少しだけします。それから空白行が結構挟まれてるのでそのあたりもズバッと除去してしまったり、なぜか全角で書かれている数字も半角に直したいところです。気のせいでした。ちゃんと半角のようです。

そういうわけで、最初にタグを飛ばすことにします。そこで、方法ですが、最近見つけたページにこんなのがありました。

コレを使って、一撃必殺タグ飛ばしをやろうと思います。って、正規表現ってそういうのできると思い込んでるんですが...

その前に、ダウンロードしたファイルを今のところ一つのフォルダに全て叩き込んでるんですが(全てのファイルは異なる名称だという前提です)、コレを列挙してそれぞれのファイルに対してタグ飛ばしをやることにします。どう考えてもダウンロード後の保存の前にやるべきな気もしますが、ここはやはりオリジナルを置いておこうというビビりモードでやることにします。

ところで、フォルダ内のファイルの列挙については、あらゆる場所にサンプルが転がってるので、ここでは(またもや)コールバック関数を使った、使いまわしが効く(と信じていて少なくとも3回は使いまわしたことのある)形でファイルの列挙をやってみます。

ファイルの列挙 - コールバック関数の型TOP

まずは、ファイルやフォルダが見つかった時に、フォルダ検索関数から呼び出されるコールバック関数です。結構引数がありますが、Path(読取専用)は、見つかったファイルやフォルダのフルパス、IsFile(読取専用)は、ファイルの場合に true、フォルダの場合に false が入った状態で関数が呼ばれます。Level(読取専用)は、呼び出した時点を基準にどれだけフォルダを掘り進んだか、DigDir(要書き換え)は、もしそのパスがフォルダの場合に、そのフォルダの中をさらに検索するかを、コールバック関数内で書き換えます。掘り進む場合はtrueを指定します。最後に、コールバック関数の返却値がtrueの場合、検索をそこで中断します。

全然関係無いですが、of object は、クラスメソッドをコールバック関数にする際に指定します。たとえば、フォームのメンバ関数、TMemoとかに検索結果を表示する場合は、ぜひともフォームのメンバ関数にしたいところですが、そういうものをコールバックさせることができるようになります。

//------------------------------------------------------------------------------
//  ファイル/フォルダ発見時のコールバック関数
type
    TVisitorProc = function(const Path: string;     //  見つけたパス
                            const IsFile: Boolean;  //  ファイルかどうか
                            const Level: integer;   //  掘り進んだレベル
                            var DigDir: Boolean)    //  フォルダの場合掘るかどうか
                            : Boolean of object;    //  検索中断時 true

ファイルの列挙 - フォルダ巡回関数TOP

そして、メインのフォルダ巡回関数です。DirPathは、検索したいフォルダのパス、Levelは何でもよいのですが、0などを指定します。指定フォルダ直下のファイル/フォルダが発見された時に、コールバック関数のLevel引数に0が渡され、指定フォルダ内のサブフォルダ内のファイル/フォルダが発見されると、それなりにLevelがインクリメントされて渡されます。早い話Levelの開始値です。最後に、Procには、フォームのメンバに作成した(後述します)TVisitorProc型のメソッドを渡します。

//------------------------------------------------------------------------------
//  巡回関数
procedure VisitDirectory(const DirPath: string; const Level: integer; Proc: TVisitorProc);
var
    Rec: TSearchRec;
    Path: string;
    DigDir, Cancel: Boolean;
begin
    //  \ 付け
    Path := IncludeTrailingPathDelimiter(DirPath);
    //  初回検索
    if FindFirst(Path  + '*.*', faAnyFile, Rec) = 0 then
    begin
        //  見つかった
        try
            repeat
                //  フォルダ発見
                if (Rec.Attr and faDirectory > 0) and (Rec.Name <> '.') and (Rec.Name <> '..') then
                begin
                    //  通知
                    DigDir := true;
                    Cancel := Proc(Path + Rec.Name, false, Level, DigDir);
                    //  キャンセルの場合
                    if Cancel then break;
                    //  さらに掘る場合
                    if DigDir then VisitDirectory(Path + Rec.Name, Level + 1, Proc);
                end
                //  ファイル発見
                else
                if (Rec.Name <> '.') and (Rec.Name <> '..') then
                begin
                    //  通知
                    Cancel := Proc(Path + Rec.Name, true, Level, DigDir);
                    //  キャンセルの場合
                    if Cancel then break;
                end;
            until
                (FindNext(Rec) <> 0)
            ;
        //  解放
        finally
            FindClose(Rec);
        end;
    end;
end;

ファイルの列挙 - サンプルコールバック関数TOP

そして、こんな感じで、コールバック関数を宣言して、

type
  TForm1 = class(TForm)
  private
    function VisitAction(const Path: string; const IsFile: Boolean;
                         const Level: integer; var DigDir: Boolean): Boolean;
  public
  end;

実際の処理を記述します。ココでは、ファイルが見つかった場合のみ、その深さに応じてインデントをつけてMemo1にフルパスを表示するようにしています。また、キャンセルはできず、フォルダもずばずばと掘り進むような感じにしています。

//------------------------------------------------------------------------------
//  ファイルやフォルダを見つけたときにどうするか
function TForm1.VisitAction(const Path: string; const IsFile: Boolean;
                            const Level: integer; var DigDir: Boolean): Boolean;
begin
    //  キャンセルしない
    Result := false;
    //  ファイルの場合
    if IsFile then
    begin
        Memo1.Lines.Add(StringOfChar(' ', Level) + Path);
    end
    //  フォルダの場合
    else
        //  掘り進む
        DigDir := true;
    ;
end;

という前準備を終わらせてから、次のように呼び出します。この設定で実行すると、C:\WINNT\system32\ に存在する全てのファイルが列挙され(ると信じてい)ます。

//  ---------------------------------------------------------------------------
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
    VisitDirectory('C:\WINNT\system32\', 0, VisitAction);
end;

要は、「フォルダを掘り進む」という定型処理と「実際に見つかったファイル/フォルダへのアクション」を分離したい、というそれだけです。ふと思って、今やってみたところ、最初の呼び出しに指定したフォルダそのものは、コールバック関数に対して渡されません。どっちがいいんでしょうか?

またもや、寄り道に大量行数を費やしたため次のページへ続きます。

EOFTOP