はじめに
備忘のため、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)」を選択します。
次に、参照設定の画面から、次の2つの項目を探してチェックを入れます。
Microsoft HTML Object Library
Microsoft Internet Controls
チェックを入れたら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を取得する方法を考えてみます。
該当部分を右クリックして「検証(I)」を選択します。
するとWEBページのHTMLが表示されて、対象部分が青色になっていますので、このあたりが「サービス一覧」のHTML部分と言うことが分かります。
実際には、赤枠の部分が該当するHTMLになります。
この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のトップページにある検索フォームに文字を入力して検索を実行するコードです。
検索フォームは、次のような感じです。
3-1. HTMLの確認
検索フォームのHTMLは次のように、formタグで括られた範囲となります。
このうち、赤枠部分がテキストボックス
で、青枠部分が検索ボタン
となります。
テキストボックスは、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
で引数のデータ型を指定しています。
もう一つの宣言も基本的に同様の構造ですが、PtrSafe
やLongStr
の意味などは、APIについて(Win32API)を参照してみてください。
なお、余談ですが、Ptr
はポインタの意を表しています。
最後に出てきましたが、「Sleep関数は、VBAでスクレイピングをする場合には必須」と言っていい関数です。
毎回使うので、いつでもコピペできるように(?)しておいてください。
さいごに
昨日、Pythonのスクレイピングについての記事を書きましたので、VBAについても残しておきました。
スクレイピングは、基本的な方法だけではなかなか思ったようにいかない場合も多く、試行錯誤の繰り返しが多いです。
そういう中で、解決策を見いだしながらコードを書くことで、結構な勉強にもなります。
後日、余裕があるときに、iframe内の要素の操作
、別ウィンドウで開かれたページの制御方法
など個人的に苦労したことの解決方法についても書いていこうと思います。