LoginSignup
4
11

More than 3 years have passed since last update.

【VBA / IE】スクレイピングの基本操作のみ

Last updated at Posted at 2020-11-03

はじめに

備忘のため、VBAのスクレイピングについての基本操作を書いておきます。
初心者には難しい用語もあるかもしれませんが、やってみると以外に簡単です。

用語については、折々で「エクセルの神髄」というサイトのリンクを付けさせていただいています(あいまいな理解を解消してくれる良いサイトです)。
また、私の知識の多くは「VBAのIE制御入門」というサイトで学ばせていただいています。

なお、スクレイピングは、場合によっては違法と判断されるため、Webスクレイピングの注意事項一覧という記事などを参照しておくことをお勧めします。

<目次>
1. ブラウザを開くだけのコード
2. WEBページ中から必要な情報を抽出するコード
3. テキストボックスに文字入力して検索を実行するコード

1. ブラウザを開くだけのコード

1-1. 参照設定をしない場合

事前の設定(参照設定)をしなくとも、次のように書くことで、WEBブラウザを表示することができます。

1-1-1. ソースコード

Sub IEGet01()
    Dim objIE As Object 'オブジェクト型の変数を宣言
    Set objIE = CreateObject("InternetExplorer.Application") '外部ライブラリからインスタンスを作成
    objIE.navigate "https://www.amazon.co.jp/" 'navigateメソッドで指定したURLのページを開く
    objIE.Visible = True 'ブラウザを表示させる
    Set objIE = Nothing 'オブジェクトの解放(無くとも問題ないと思う)
End Sub

これは、CreateObject関数を使用して、コードの実行時に、外部ライブラリ(ActiveXオブジェクト)からインスタンス(objIE)を作成しています。
この方法は「実行時バインディング」と言われています。

なお、バインディングとかインスタンスとか、意味が分からなくても大丈夫です。
このコードであれば、何の前提作業も不要で、とりあえず動作します。

インスタンスとかオブジェクトなどの基礎知識を知りたい方は、こちらを参照してください。

1-1-2. 簡単な説明

1行目Dim objIE As Objectで、オブジェクト型の変数objIEを宣言します。変数なので別名でも構いません。

2行目Set objIE = CreateObject("InternetExplorer.Application")で、外部ライブラリ(ActiveXオブジェクト)からインスタンス(InternetExplorerオブジェクト)を生成しています。

3行目objIE.navigate "https://www.amazon.co.jp/"では、navigateメソッドを使用して指定したURLのページを開いています。

4行目objIE.Visible = Trueは、ブラウザを表示する状態に設定しています。
これがないと、「ブラウザは立ち上がっているものの画面には表示されない」という状態になります。

5行目Set objIE = Nothingでは、生成したインスタンス(オブジェクト)を解放(破棄)しています。
オブジェクトを解放しないと、プログラム終了後もメモリに残ってしまうためですが、これが無くとも構わないという見解も有力です(C言語でも同じ論争があります)。

1-2. 参照設定をする場合

一般的には、先に参照設定を行ってコードを記述します。

1-2-1. 参照設定の方法

VBAのメニュー「ツール」から「参照設定(R)」を選択します。
2020-11-03 115347.png
次に、参照設定の画面から、次の2つの項目を探してチェックを入れます。

Microsoft HTML Object Library
Microsoft Internet Controls
2020-11-03 104505.png
チェックを入れたらOKボタンを押して完了です。

1-2-2. ソースコード

Sub IEGet02()
    Dim objIE As InternetExplorer '変数を宣言
    Set objIE = New InternetExplorer 'インスタンスを作成
    objIE.navigate "https://www.amazon.co.jp/"
    objIE.Visible = True
End Sub

参照設定を行っているので、1行目で直接InternetExplorerオブジェクトを変数として宣言することができます。
事前にライブラリを読み込むために、用語としては「事前バインディング」といいます。

これにより、2行目のインスタンス生成でも、CreateObject関数を使用する必要はなく、単にNew [クラス名]という形で記述することができます。

以降は最初の例と同じですが、Set objIE = Nothingというオブジェクトの解放の部分は省略しています(こちらの見解に基づけば、書かなくても問題ないということになります)。

2. WEBページ中から必要な情報を抽出するコード

2-1. WEBページのHTMLの確認

例として、あまり実用性がないですが、yahooのトップページにある「サービス一覧」の部分のHTMLを取得する方法を考えてみます。
2020-11-03 130048.png
該当部分を右クリックして「検証(I)」を選択します。
2020-11-03 130541.png
するとWEBページのHTMLが表示されて、対象部分が青色になっていますので、このあたりが「サービス一覧」のHTML部分と言うことが分かります。
実際には、赤枠の部分が該当するHTMLになります。
2020-11-03 130953.png
このHTMLをVBAで取得してみます。
取得に際しては、1行目のid="ToolFooter"の部分を利用することにします。

2-2. HTMLを取得するコード

該当部分のHTMLを取得するコードを書くと次のとおりです。

Sub IEGet03()
    Dim objIE As InternetExplorer '変数を宣言
    Set objIE = New InternetExplorer 'インスタンスを作成
    objIE.navigate "https://www.yahoo.co.jp/"
    objIE.Visible = True

    'WEBページの読み込みを待つ
    Do While objIE.Busy = True Or objIE.readyState <> 4
        DoEvents
    Loop

    'ID名から要素を特定してHTMLを表示する
    Debug.Print objIE.document.getElementById("ToolFooter").outerHTML

End Sub

2-2-1. WEBページの読み込み待ち

WEBページの読み込みが完了してからでないと、HTMLの読み取り等の操作ができません。
そのため、次のコードで、ブラウザが読み込みを完了するのまで待機します。

Do While objIE.Busy = True Or objIE.readyState <> 4
    DoEvents
Loop

上記は定型で、どのサイトでも大体このような形で書いています。

Busyプロパティは、Boolean型であり、WEBページの読み込み中(遷移中、ダウンロード中)の場合にTrueを返します。
ReadyStateプロパティは、数値型(Long型)であり、次のように、0から4の定数が割り当てられています。

ステータス 数値 説明
READYSTATE_UNINITIALIZED 0 デフォルトの初期状態
READYSTATE_LOADING 1 オブジェクトの属性(プロパティ)読み込み中
READYSTATE_LOADED 2 オブジェクトの初期化の完了
READYSTATE_INTERACTIVE 3 オブジェクトは使用可能だが、全てのデータは利用はできない
READYSTATE_COMPLETE 4 全てのデータ受信完了

以上は、テキトウ翻訳なので、詳しくは「VBAのIE制御 / InternetExplorerオブジェクトのReadyStateプロパティ」を参照してください。

結論として、BusyプロパティがFalseになり、かつ、ReadyStateプロパティが4になるまでは「ページ読み込み中」と判断してループを繰り返し、両方の条件が成立したら「読み込み完了」と判断して、次の処理に進むということを行っているわけです。

2-2-2. IDから要素(ノード)を取得する方法

ページの読み込みが完了したら、必要なHTMLの取得処理に移ります。
IDを元にしてHTMLの要素(ノード)を取得するには、次のようにgetElementByIdメソッドを使用します。

objIE.document.getElementById("ToolFooter").outerHTML

頭の部分のobjIE.documentで、ページ全体の要素(ノード)を取得しています。
次に、このページ全体のノードから、getElementByIdメソッドを使用して絞り込みをかけてます。括弧内の"ToolFooter"はID名です。
最後のouterHTMLプロパティで、抽出したノードのHTMLを取得しています。

2-2-3. 要素(ノード)を取得するメソッドの一覧

要素(ノード)を取得するためのメソッドには次のようなものがあります。

メソッド 内容 取得形式
getElementById() ID名から取得 単独
getElementsByClassName() class名から取得 複数(コレクション)
getElementsByName() name名から取得 複数(コレクション)
getElementsByTagName() タグ名から取得 複数(コレクション)
getAttribute() 属性値を取得 単独

class名、name名、タグ名から要素を取得する場合は、配列形式(コレクション形式)で取得されます。
そのため、1つの要素(ノード)を特定するには、次のように、末尾に要素番号(0)を記載する必要があります。

objIE.document.getElementsByClassName("_3wZKJtfV8AYYaTP-zoyO9O ult__mods")(0).outerHTML

属性値については、次のように取得したいノードを特定してからgetAttributeメソッドを使用します。
これによりクラス名が返されます。

objIE.document.getElementById("ToolFooter").getAttribute("class")

分かる方には分かると思いますが、基本的にJavaScriptのメソッドと同じです。

3. テキストボックスに文字入力して検索を実行するコード

次は、yahooのトップページにある検索フォームに文字を入力して検索を実行するコードです。
検索フォームは、次のような感じです。
2020-11-03 164525.png

3-1. HTMLの確認

検索フォームのHTMLは次のように、formタグで括られた範囲となります。
2020-11-03 164933.png
このうち、赤枠部分がテキストボックスで、青枠部分が検索ボタンとなります。

テキストボックスは、name属性name="p"が一意なので、これで要素を取得できます。

検索ボタンは、class名class="_63Ie6douiF2dG_ihlFTen rapid-noclick-resp"が一意なので、これを利用して要素を取得します。
なお、このクラスは半角スペースで区切られて2つのclass名を持っているので、ここでは、前半の_63Ie6douiF2dG_ihlFTenをclass名として使用します。

3-2. ソースコード(上手くいかない例)

一般的には、次のようなコードでテキストボックスに文字を入力して検索を実行します。
ただ、yahooのサイトでは、これではうまくいきません(解決方法は後述します)。

Sub IEGet04()
    Dim objIE As InternetExplorer '変数を宣言
    Set objIE = New InternetExplorer 'インスタンスを作成
    objIE.navigate "https://www.yahoo.co.jp/"
    objIE.Visible = True

    'WEBページの読み込みを待つ
    Do While objIE.Busy = True Or objIE.readyState <> 4
        DoEvents
    Loop

    objIE.document.getElementsByName("p")(0).Value = "テスト" '検索文字を入力
    objIE.document.getElementsByClassName("_63Ie6douiF2dG_ihlFTen")(0).Click '検索ボタンをクリック
End Sub

3-2-1. テキストボックスに文字を入力する

テキストボックスに文字を入力するコードは次の部分です。

objIE.document.getElementsByName("p")(0).Value = "テスト"

取得したテキストボックスのノードについて、Valueプロパティを使用して、"テスト"という文字列を代入しています。

3-2-2. 検索ボタンをクリックする

検索ボタンをクリックするコードは次の部分です。

objIE.document.getElementsByClassName("_63Ie6douiF2dG_ihlFTen")(0).Click

取得した検索ボタンのノードについて、Clickメソッドを使用して、検索ボタンをクリックする処理を行っています。

3-2-3. 検索が実行されないことについて

コードを実行してみると、テキスト入力もできて、検索ボタンも押しているのに、検索結果が表示されないという状態になります(私の環境では)。
挙動を確認してみると、キーボードから入力された文字列にしか検索が実行されないようです。

3-3. ソースコード(上手くいく例)

テキストボックスへの入力部分に難があるようなので、今度はSendKeysメソッドでキーボード入力をして検索を実行するようにしました。
次のとおりです。

'Win32APIからSleep関数を使用するための宣言
#If VBA7 Then
    Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
#Else
    Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#End If

Sub IEGet05()
    Dim objIE As InternetExplorer '変数を宣言
    Set objIE = New InternetExplorer 'インスタンスを作成
    objIE.navigate "https://www.yahoo.co.jp/"
    objIE.Visible = True

    'WEBページの読み込みを待つ
    Do While objIE.Busy = True Or objIE.readyState <> 4
        DoEvents
    Loop

    SendKeys "テスト" 'SendKeysメソッドでキーボード入力を行う
    Sleep 1000 '1000ミリ秒待機する
    objIE.document.getElementsByClassName("_63Ie6douiF2dG_ihlFTen")(0).Click '検索ボタンをクリック
End Sub

3-3-1. テキストボックスにキーボードで文字を入力する

テキストボックスにキーボードで文字を入力するコードは次の部分です。

SendKeys "テスト" 'SendKeysメソッドでキーボード入力を行う

yahooのトップページを開くと、デフォルトで、カーソルが検索ボックスに当てられています。
ここで、SendKeysメソッドを使用して、"テスト"という文字列をキーボード入力しています。

もし、カーソルが目的の箇所とは別のところにある場合は、カーソルを動かす処理が必要になります(ここでは触れません)。

3-3-2. 処理を一定時間待機させる

上記のキーボード入力の処理が終わってすぐに検索ボタンを押すと、WEBサイト側で処理が追いつかず正常に検索されないので、Sleep関数を使用して1秒間(1000ミリ秒間)待機をさせています。次のコードです。

Sleep 1000

上記の記述で、1秒間(1000ミリ秒間)待機とすることができますが、このSleep関数はVBAの関数ではなくてWindows自体が持っている機能となります。
そのため、Windowsの機能を使用するためのAPI(Win32API)をVBAで使用できるように呼び出す必要があります(次項)。

3-3-3. Win32APIの関数の呼び出し

Win32APIの関数を呼び出すための宣言は次の部分です。

#If VBA7 Then
    Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
#Else
    Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#End If

この宣言については、基本はどのサイトも同様の記述をしているので、コピペで十分です。

とはいえ、一応、簡単に構造を説明しておきます。
最初に、VBAのバージョンにより宣言の構造が変わるため、If文でVBA7であるか否かを判定しています。
この条件分岐で、2パターンの宣言がありますが、ここでは簡単なElse以下のDeclare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)の方を見てみます。

まず冒頭にあるのが、Declareステートメントです。
これは、「ダイナミックリンクライブラリ(DLL) 内の外部プロシージャへの参照」を宣言するものです。
いい加減に言うと、外部にあるWindowsの機能の使用を宣言するというような意味です。

続くSub Sleepの部分でSleep関数(プロシージャ)を使用することを指定して、更に、この関数は「kernel32」というライブラリにあることをLib "kernel32"で示しています。

最後の(ByVal dwMilliseconds As Long)では、引数のデータ型を指定しています。
ByValは引数が「値渡し」であることを示し、dwMillisecondsは変数なので何でも良い(mとかでも良い)、そしてAs Longで引数のデータ型を指定しています。

もう一つの宣言も基本的に同様の構造ですが、PtrSafeLongStrの意味などは、APIについて(Win32API)を参照してみてください。
なお、余談ですが、Ptrはポインタの意を表しています。

最後に出てきましたが、「Sleep関数は、VBAでスクレイピングをする場合には必須」と言っていい関数です。
毎回使うので、いつでもコピペできるように(?)しておいてください。

さいごに

昨日、Pythonのスクレイピングについての記事を書きましたので、VBAについても残しておきました。

スクレイピングは、基本的な方法だけではなかなか思ったようにいかない場合も多く、試行錯誤の繰り返しが多いです。
そういう中で、解決策を見いだしながらコードを書くことで、結構な勉強にもなります。

後日、余裕があるときに、iframe内の要素の操作別ウィンドウで開かれたページの制御方法など個人的に苦労したことの解決方法についても書いていこうと思います。

4
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
11