Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2015年02月15日] [更新 2020年10月18日] Delphi サンプルプログラム集
949_ウェブブラウザを使用しない HTML のパース
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro + Indy 10 (Ver 10.5.7)
949_ParseHTML.zip [3.5 MB] 2020年10月18日17日版 (EXE 同梱)







ウェブブラウザを使用しない HTML の解析


ここまでの数ページで、ウェブブラウザのコントロールを使用して、ウェブページの操作や HTML コードの解析のテストをしてきました。
本ページのサンプルは、TWebBrowser 等のウェブブラウザのコントロールを使用しないで、つまり、対象のウェブページを表示しないで、オフラインで HTML コードの解析を行うサンプルです。
MSHTML のライブラリを使用しています。

HTML や XML, XHTML は DOM (Document Object Model) の実装です。そこで、HTML 等を操作することを DOM の操作、HTML を解析することを HTML のパースと言うことがあります。
パースを行うツール類、コンポーネント、ライブラリ等を「パーサー」と呼ぶこともあります。



[備考 1]
本ページのサンプルでは、Indy の TIdHTTP コンポーネントを使用してウェブページから HTML のコードを取得しています。TIdHTTP で対象のウェブページに接続できない場合、WinInet 類の関数を使用すると接続できることもあります。
また、HTML のパースには、正規表現等を利用して、テキストを直接解析する方法もあります。


[備考 2]
絶対 URL から相対 URL の変換等は、以下のサンプルに添付の plHtmlUtiltsEx.pas にもあります。
本ページのサンプルは、当サイトのウェブページの HTML コードだけでテストしていますが、以下の記事のサンプルは、いくつかのサイトのページでも動作確認しています。





01_HTML コードからリンクの URL を抽出


当サイトのトップページの HTML コードから、各ページへのリンクの URL を調べ、リストするサンプルです。

まず、TIdHTTP コンポーネントを使用して、ウェブページの HTML コードを取得します。この HTML のテキストを IHTMLDocument2 のドキュメントにするには、IHTMLDocument2 の write メソッドを使用しますが、write メソッドの引数は SafeArray 型です。そこで、要素が 1 つだけの SafeArray の配列を作成して、そこに取得した HTML コード (文字列) を代入しています。

後は、ウェブブラウザコントロールを使用したサンプルと同じ手法、手順が使えます。
タグが a の中の属性が href の値を取り出す処理をして、リストに追加します。同じページへのリンクが複数あるので、重複のチェックもしています。



図1
設計時画面
図2
実行結果

リスト1
HTML コードからリンクの URL を抽出
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
  IdHTTP, ExtCtrls, MSHTML;

type
  TForm1 = class(TForm)
    IdHTTP1: TIdHTTP;
    Panel1: TPanel;
    Edit1: TEdit;
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    function URLtoIHTMLDoc2(AStrURL: string; var ASiteHomeDir: string;
      IDoc2: IHTMLDocument2): Boolean;
    procedure CheckDuplicateItems(AList: TStringList);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses 
  ComObj, 
  ActiveX, 
  Generics.Collections;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := 'http://mrxray.on.coocan.jp/index.htm';
end;

//=============================================================================
//  HTML コードから、他のページへのリンク部分を抽出
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LSourceFile    : string;
  LHomeDirectory : string;
  LIHTMLDoc2     : IHTMLDocument2;
  LIndex         : Integer;
  LElementColl   : IHTMLElementCollection;
  LElement       : IHTMLElement;
  LListLinkURL   : TStringList;
  LValue         : string;
begin
  LSourceFile := Edit1.Text;

  // IHTMLDocument2 インターフェイスのオブジェクトを生成
  LIHTMLDoc2 := CreateComObject(Class_HTMLDOcument) as IHTMLDocument2;

  // URL からホームディレクトリ名と IHTMLDocument2 のドキュメントを取得
  if URLtoIHTMLDoc2(LSourceFile, LHomeDirectory, LIHTMLDoc2) then begin
    LListLinkURL := TStringList.Create;
    try
      //これ以降が実際のHTMLの解析
      LElementColl := LIHTMLDoc2.all;
      for LIndex := 0 to LElementColl.length - 1 do begin
        LElement := LElementColl.item(LIndex, '') as IHTMLElement;
        if LElement.tagName = 'A' then begin
          // タグが A の中のhref属性の値を取り出す
          LValue := LElement.getAttribute('href', 0);
          if Trim(LValue) <> '' then begin
            // 相対パスには先頭に about: という文字が付加されるらしい
            // これを置き換える
            LValue := StringReplace(LValue, 'about:/', LHomeDirectory, [rfIgnoreCase]);
            LValue := StringReplace(LValue, 'about:', LHomeDirectory, [rfIgnoreCase]);
            //リストに追加
            LListLinkURL.Add(LValue);
          end;
        end;
        Application.ProcessMessages;
      end;

      // 重複した URL を除外する
      CheckDuplicateItems(LListLinkURL);
      Memo1.Text := LListLinkURL.Text;
    finally
      FreeAndNil(LListLinkURL);
    end;
  end;
  LIHTMLDoc2 := nil;
end;

//-----------------------------------------------------------------------------
//  HTML コードから IHTMLDocument2 のドキュメントを取得
//  ここでは実際のウェブページから HTML コードを取得
//
//  AStrURL      : 対象のページのアドレス
//  ASiteHomeDir : 対象のページのアドレスの[プロトコル+ホスト名]の値
//                 この値がホームディレクトリ
//  IDoc2        : IHTMLDocument2 オブジェクト (呼び出し側で生成しておく)
//-----------------------------------------------------------------------------
function TForm1.URLtoIHTMLDoc2(AStrURL: string; var ASiteHomeDir: string;
  IDoc2: IHTMLDocument2): Boolean;
var
  LHtmlCode : TStringList;
  LOleArray : Variant;
begin
  Result  := True;
  ASiteHomeDir := '';
  IdHTTP1.HandleRedirects := True;

  //T IdHTTP でL SourceFile のページの HTML コードを TStringList に格納
  LHtmlCode := TStringList.Create;
  try
    try
      LHtmlCode.Text := IdHTTP1.Get(AStrURL);
      // プロトコルとホスト名を取得しておく。この値がホームディレクトリ
      ASiteHomeDir := IdHTTP1.URL.Protocol + '://' + IdHTTP1.URL.Host + '/';
    except
      Result := False;
    end;
    IdHTTP1.Disconnect;
    if not Result then exit;


    // IHTMLDocument2 を編集可能にして
    // 念のため readState が loading の間待つ
    IDoc2.designMode := 'on';
    while IDoc2.readyState = 'loading' do begin
      Application.ProcessMessages;
    end;

    // バリアント配列に HTML コードをセット
    LOleArray := VarArrayCreate([0, 0], VarVariant);
    try
      VarArrayLock(LOleArray);
      LOleArray[0] := LHtmlCode.Text;

      // IHTMLDocument2 オブジェクト にHTML コードを書き込む
      IDoc2.Write(PSafeArray(System.TVarData(LOleArray).VArray));
      IDoc2.designMode := 'off';

      // 念のため readState が loading の間待つ
      while IDoc2.readyState = 'loading' do begin
        Application.ProcessMessages;
      end;
    finally
      VarArrayUnLock(LOleArray);
      VarClear(LOleArray);
    end;
  finally
    FreeAndNil(LHtmlCode);
  end;
end;

//-----------------------------------------------------------------------------
//  リスト内の重複した文字列を削除するルーチン
//  uses に Generics.Collections が必要
//-----------------------------------------------------------------------------
procedure TForm1.CheckDuplicateItems(AList: TStringList);
var
  LListDic  : TDictionary<String,TObject>;
  LListTemp : TStringList;
  LIndex    : Integer;
begin
  // 重複した URL を除外してTMemo に追加していく
  LListDic  := TDictionary<String, TObject>.Create;
  LListTemp := TStringList.Create;
  try
    LListTemp.Text := AList.Text;
    AList.Clear;
    for LIndex := 0 to LListTemp.Count - 1 do begin
      if LListDic.ContainsKey(LListTemp[LIndex]) = False then begin
        LListDic.Add(LListTemp[LIndex], nil);
        AList.Add(LListTemp[LIndex]);
      end;
    end;
  finally
    FreeAndNil(LListDic);
    FreeAndNil(LListTemp);
  end;
end;

end.




02_htmlfile オブジェクトによる HTML の解析例


山本隆さんのブログで htmlfile というオブジェクトの記事を見つけました。
これを使用して、Delphi で簡単なテストをしてみました。バリアント型で扱うので、write メソッドの引数に SafeArray を使用しなくても、文字列が使えるようです。IHTMLDocument2 インターフェイス関係のメソッドやプロパティが利用可能と思われます。


図3
FirstChild.outerHTML の 取得結果
図4
Title プロパティの取得結果
図5
body.outerText の取得結果

リスト2
htmlfile オブジェクトによる HTML の解析例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses 
  MSHTML,
  ComObj;

{$R *.dfm}

//=============================================================================
//  htmlfile という OLE オブジェクトのテスト
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  HTMLDoc : OleVariant;
  LText   : string;
begin
  HTMLDoc := CreateOleObject('htmlfile');

  LText := '<html><head><title>タイトル</title></head><body>ボディ</body></html>';
  HTMLDoc.write(LText);

  LText := HTMLDoc.FirstChild.outerHTML;
  // LText := HTMLDoc.body.parentElement.outerHTML;でも同じ
  ShowMessage(LText);

  LText := HTMLDoc.Title;
  ShowMessage(LText);

  LText := HTMLDoc.body.outerText;
  ShowMessage(LText);

  HTMLDoc := Unassigned;
end;

end.




03_相対 URL から絶対 URL を取得


本ページの最初のサンプルで、リンク先のページのアドレスを取得しています。
それは、サイトのトップページに対する操作でした。したがって、全てのリンクはサイトのホームディレクトリからの相対リンクとなり、正しく取得することができました。

しかし、実際のウェブページは必ずしもそのサイトのトップページを表示するわけではありません。該当するサイトのトップページでないページの HTML コード内の相対リンクは、絶対パスとして取得できなければ画像等のダウンロードはできません。
相対パスを絶対パスに変換する関数として、ExpandFileName があります。これはローカルなディスク上のファイルに対しては有効です。ExpandFileName 関数は、カレントディレクトリに対する相対パスを絶対パスに変換する関数です。

ウェブページ内のリンクの相対パスを絶対バスに相当する値を求めるには、カレントディレクトリに相当する値、つまり、基準となるディレクトリか必要です。
そこで、ウェブページの URL から、そのサイトのホームディレクトリを取得して、これを基準ディレクトリとします。この基準ディレクトリを元にして、相対 URL を絶対 URL に変換する関数を作成してみました。本サンプルはそのテストです。



図6
実行結果の例

リスト3
相対 URL から絶対 URL を取得する関数とそのテスト
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Edit1: TEdit;
    Edit2: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    function ToAbsoluteURLPath(ATargetURL, ARelativeURL: string): string;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses 
  IdURI,
  IOUtils,
  StrUtils;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := 'http://mrxray.on.coocan.jp/Delphi/plSamples/949_ParseHTML.htm';
  Edit2.Text := '../AAA.txt';
end;

//=============================================================================
//  下の関数のテスト
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LLinkFullPath : String;
begin
  Memo1.Lines.Clear;
  LLinkFullPath := ToAbsoluteURLPath(Edit1.Text, Edit2.text);
  Memo1.Lines.Add(LLinkFullPath);
end;

//-----------------------------------------------------------------------------
//  HTML コード内のリンクの相対 URL を絶対 URL に変換する関数
//
//  ATargetURL   : ウェブページのアドレス
//                 ディレクトリ名の場合は、末尾にデリミタ (/) が必要
//                 / がなければファイル名と見なす
//  ARelativeURL : 絶対パスを取得したい相対パス名 (絶対パスでもよい)
//  戻り値         相対パスを絶対パスに変換した値
//                 サイト外のアドレスの時は、元の RelativeURL のまま
//                 ページ内のリンクもそのまま (先頭が #)
//-----------------------------------------------------------------------------
function TForm1.ToAbsoluteURLPath(ATargetURL, ARelativeURL: string): string;
var
  LBaseDir       : string;
  LProtocol      : string;
  LRelativeURL   : string;
  LIdURL         : TIdURI;
  LHomeDirectory : string;
begin
  Result       := '';
  LBaseDir     := ATargetURL;
  LRelativeURL := ARelativeURL;
  if LBaseDir = '' then exit;
  if LRelativeURL = '' then exit;

  // ホームディレクトリの URL を取得しておく
  LIdURL := TIdURI.Create(ATargetURL);
  try
    LProtocol      := LIdURL.Protocol + ':';
    LHomeDirectory := LIdURL.Protocol + '://' + LIdURL.Host;
  finally
    FreeAndNil(LIdURL);
  end;


  // ホームディレクトリの URL が含まれていればすでに絶対バスと見なす
  if ContainsText(LRelativeURL, LHomeDirectory) then begin
    Result := LRelativeURL;
  end else begin
    // 取得したリンク先の相対パスの先頭は about: または about:/ となるらしい
    // ページ内のリンクは about:blank となるらしい
    // これを削除しておく
    LRelativeURL := StringReplace(LRelativeURL, 'about:blank', '', [rfIgnoreCase]);
    LRelativeURL := StringReplace(LRelativeURL, 'about:', '', [rfIgnoreCase]);

    //先頭が[//]の時はプロトコルに対する相対パス
    if (LeftStr(LRelativeURL, 2) = '//') then begin
      Result := LProtocol + LRelativeURL;
    end else
    // 先頭が [/] の時はサイトルート相対パス
    if (LeftStr(LRelativeURL, 1) = '/') then begin
      Insert(LHomeDirectory, LRelativeURL, 1);
      Result := LRelativeURL;
    end else
    // [./] は同じディレクトリ内を明示的に表す時の記述
    if (LeftStr(LRelativeURL, 2) = './') then begin
      Delete(LRelativeURL, 1, 2);
      Result := LRelativeURL;
    end else begin
      //[../]がない場合は同じディレクトリまたは他のサイトへのリンク
      if not ContainsText(LRelativeURL, '../') then begin
        // [://] という文字列があれば他のサイトと見なす
        if ContainsText(LRelativeURL, '://') then begin
          Result := LRelativeURL;
        end else begin
          if LeftStr(LRelativeURL, 1) = '#' then begin
            Result := LRelativeURL;
          end else begin
            Result := TPath.GetDirectoryName(LBaseDir) + '/' + LRelativeURL;
          end;
        end;
      end else begin
        // [../] の数だけ上の階層のディレクトリ名を取得していく
        while True do begin
          LBaseDir := TPath.GetDirectoryName(LBaseDir);
          if LeftStr(LRelativeURL, 3) <> '../' then break;
          Delete(LRelativeURL, 1, 3);
        end;
        LBaseDir := LBaseDir + '/';
        if ContainsText(LBaseDir, LHomeDirectory) then begin
          Result := LBaseDir + LRelativeURL;
        end;
      end;
    end;
  end;
end;

end.




04_HTML コード内のリンク先やファイル名等の絶対パスを取得


上のサンプルで、相対 URL を絶対 URL に変換する関数を作成しました。
実際の HTML コードでテストするサンプルです。本サンプルは、最初のサンプルと同じく、実際にウェブページから取得した HTML コードを使用しています。また、最初のサンプルでは、別のウェブページへのリンクだけを取得しましたが、本サンプルは、画像、スタイルシート、JavaScript ファイル等のリンク先も取得するようにしてみました。


図7
設計時画面
図8
相対パスのままの状態
  • ヘッダ部の相対リンクは、HTML コード内の記述のまま
  • <BODY> タグ内の相対リンクには about: の文字列が付いている
図9
絶対 URL のパスに変換した結果

リスト4
HTML コード内のリンク先やファイル名等の絶対パスを取得
ここまでのテストで作成した関数類のコードは省略
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
  IdHTTP, ExtCtrls, MSHTML;

type
  TForm1 = class(TForm)
    IdHTTP1: TIdHTTP;
    Panel1: TPanel;
    Edit1: TEdit;
    Button1: TButton;
    Memo1: TMemo;
    CheckBox1: TCheckBox;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    function URLtoIHTMLDoc2(AStrURL: string; var ASiteHomeDir: string;
      IDoc2: IHTMLDocument2): Boolean;
    function ToAbsoluteURLPath(ATargetURL, ARelativeURL: string): string;
    procedure CheckDuplicateItems(AList: TStringList);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ComObj, ActiveX, IdURI, IOUtils, StrUtils, Generics.Collections;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := 'http://mrxray.on.coocan.jp/Delphi/plSamples/003_StopWatch.htm';
end;

//=============================================================================
//  HTMLコードから
//    他のページへのリンク部分
//    使用しているスタイルシート、JavaScript ファイル等の場所
//  を取得して表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LSourceFile    : string;
  LHomeDirectory : string;
  LIHTMLDoc2     : IHTMLDocument2;
  i              : Integer;
  LElementColl   : IHTMLElementCollection;
  LElement       : IHTMLElement;
  LListLinkURL   : TStringList;
  LValue         : string;
begin
  Memo1.Lines.Clear;
  LSourceFile := Edit1.Text;
  Screen.Cursor := crHourGlass;

  // IHTMLDocument2 インターフェイスのオブジェクトを生成
  LIHTMLDoc2 := CreateComObject(Class_HTMLDOcument) as IHTMLDocument2;

  // URL からホームディレクトリ名と IHTMLDocument2 のドキュメントを取得
  if URLtoIHTMLDoc2(LSourceFile, LHomeDirectory, LIHTMLDoc2) then begin

    LListLinkURL := TStringList.Create;
    try
      // これ以降が実際の HTML の解析
      LElementColl := LIHTMLDoc2.all;

      for i := 0 to LElementColl.length - 1 do begin
        LElement := LElementColl.item(i, '') as IHTMLElement;

        LValue := '';
        // 指定タグ名の指定属性の値を取り出す
        if LElement.tagName = 'FRAME' then begin
          LValue := LElement.getAttribute('src', 0);
        end else
        if LElement.tagName = 'IFRAME' then begin
          LValue := LElement.getAttribute('src', 0);
        end else
        if LElement.tagName = 'BODY' then begin
          LValue := LElement.getAttribute('BACKGROUND', 0);
        end else
        if LElement.tagName = 'LINK' then begin
          LValue := LElement.getAttribute('href', 0);
        end else
        if LElement.tagName = 'SCRIPT' then begin
          LValue := LElement.getAttribute('src', 0);
        end else
        if LElement.tagName = 'A' then begin
          LValue := LElement.getAttribute('href', 0);
        end else
        if LElement.tagName = 'IMG' then begin
          LValue := LElement.getAttribute('src', 0);
        end;

        if LValue <> '' then begin
          // 相対 URL を絶対 URL に変換
          if CheckBox1.Checked then begin
            LValue := ToAbsoluteURLPath(LSourceFile, LValue);
          end;

          // ページ内のリンクと.cgi ファイルは除外
          if LeftStr(LValue, 1) <> '#' then begin
            if not ContainsText(LValue, '.cgi') then begin
              // リストに追加
              LListLinkURL.Add(LValue);
            end;
          end;
        end;
        Application.ProcessMessages;
      end;

      if LListLinkURL.Count = 0 then begin
        Memo1.Text := 'リンク情報なし';
      end else begin
        // 重複している URL を取得したリストから削除
        CheckDuplicateItems(LListLinkURL);
        Memo1.Text := LListLinkURL.Text;
      end;
    finally
      FreeAndNil(LListLinkURL);
    end;
  end;

  LIHTMLDoc2 := nil;
  Screen.Cursor := crDefault;
end;




05_TWebBrowser で表示中のウェブページのリンク先を取得


当サイトに、ウェブページの画像を全てダウンロードするサンプルがあります。
そのサンプルでは特に絶対パスに変換するコードは実行していません。IE オブジェクトであるウェブブラウザから IHTMLDocument2 オブジェクトを取得すると、getAttribute 関数が自動的に絶対 URL に変換して取得するようです。そのことを確認するテストです。

実行結果をみると、<BODY> 部のリンクはフルパスになっていますが、ヘッダ部のリンクは変換されていません。MSDN の記事によると、getAttribute の第 2 引数を 4 にすると、フルパスにしてくれるようなので、4 にして実行してみました。しかし、一部のリンクが変換されていません。
結果的に、HTML コード内の全てのリンクを絶対 URL, 絶対パスに変換するには、自前で処理した方がよさそうです。



図10
設計時画面
図11
getAttribute の引数値が 0 の場合
  • ヘッダ部のリンクが変換されていない
  • ページ内へのリンクも絶対 URL に変換され0る
図12
getAttribute の引数値が 4 の場合
  • 変換されていないリンクがある
図13
絶対 URL に変換する関数を使用
  • 全てのリンクが絶対 URL に変換されている

リスト5
TWebBrowser で表示中のウェブページのリンク先を取得
ここまでのテストで作成した関数類のコードは省略
unit UnitIE;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, MSHTML, OleCtrls, SHDocVw;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Edit1: TEdit;
    Button1: TButton;
    Memo1: TMemo;
    WebBrowser1: TWebBrowser;
    Splitter1: TSplitter;
    CheckBox1: TCheckBox;
    CheckBox2: TCheckBox;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    procedure CheckDuplicateItems(AList: TStringList);
    function ToAbsoluteURLPath(ATargetURL, ARelativeURL: string): string;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  IOUtils,
  IdURI,
  StrUtils,
  Generics.Collections;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := 'http://mrxray.on.coocan.jp/Delphi/plSamples/003_StopWatch.htm';
end;

//=============================================================================
//  ウェブページをオンラインで表示中に、そのページのHTMLコードから
//    他のページへのリンク部分
//    使用しているスタイルシート、JavaScriptファイル等の場所
//  を取得して表示
//
//  (1) getAttributeの第2引数の値が0の時と4の場合のテスト
//  (2) 相対 URL を絶対 URL に変換した場合のテスト
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LSourceFile  : string;
  LIHTMLDoc2   : IHTMLDocument2;
  i            : Integer;
  LElementColl : IHTMLElementCollection;
  LElement     : IHTMLElement;
  LListLinkURL : TStringList;
  LValue       : string;
  LIFlags      : Integer;
begin
  Memo1.Lines.Clear;
  LSourceFile := Edit1.Text;

  if CheckBox1.Checked then begin
    LIFlags := 4;
  end else begin
    LIFlags := 0;
  end;

  // ウェブページを TWebBrowse で表示
  WebBrowser1.Navigate(LSourceFile);

  // 生成完了まで待つ
  while WebBrowser1.ReadyState < READYSTATE_INTERACTIVE do begin
    Application.ProcessMessages;
    Sleep(1);
  end;

  // IHTMLDocument2 オブジェクトを取得
  if Assigned(WebBrowser1.Document) then begin
    LIHTMLDoc2 := WebBrowser1.Document as IHTMLDocument2;

    // 念のためreadStateが complete になるまで待つ
    // ブラウザでない場合は = 'loading' でないと永久ループになる
    while LIHTMLDoc2.readyState <> 'complete' do begin
      Application.ProcessMessages;
      Sleep(1);
    end;

    LListLinkURL := TStringList.Create;
    try
      // これ以降が実際の HTML の解析
      LElementColl := LIHTMLDoc2.all;

      for i := 0 to LElementColl.length - 1 do begin
        LElement := LElementColl.item(i, '') as IHTMLElement;

        LValue := '';
        // 指定タグ名の指定属性の値を取り出す
        if LElement.tagName = 'FRAME' then begin
          LValue    := LElement.getAttribute('src', LIFlags);
        end else
        if LElement.tagName = 'IFRAME' then begin
          LValue := LElement.getAttribute('src', LIFlags);
        end else
        if LElement.tagName = 'BODY' then begin
          LValue := LElement.getAttribute('background', LIFlags);
        end else
        if LElement.tagName = 'LINK' then begin
          LValue := LElement.getAttribute('href', LIFlags);
        end else
        if LElement.tagName = 'SCRIPT' then begin
          LValue := LElement.getAttribute('src', LIFlags);
        end else
        if LElement.tagName = 'A' then begin
          LValue := LElement.getAttribute('href', LIFlags);
        end else
        if LElement.tagName = 'IMG' then begin
          LValue := LElement.getAttribute('src', LIFlags);
        end;

        if LValue <> '' then begin
          // 相対 URL を絶対 URL に変換
          if CheckBox2.Checked then begin
            LValue := ToAbsoluteURLPath(LSourceFile, LValue);
          end;

          // ページ内のリンクと .cgi ファイルは除外
          if not (LeftStr(LValue, 1) = '#') then begin
            if not ContainsText(LValue, '.cgi') then begin
              // リストに追加
              LListLinkURL.Add(LValue);
            end;
          end;
        end;
        Application.ProcessMessages;
      end;

      if LListLinkURL.Count = 0 then begin
        Memo1.Text := 'リンク情報なし';
      end else begin
        // 重複している URL を取得したリストから削除
        CheckDuplicateItems(LListLinkURL);
        Memo1.Text := LListLinkURL.Text;
      end;
    finally
      FreeAndNil(LListLinkURL);
    end;
  end;

  LIHTMLDoc2 := nil;
end;