適材適所

パソコン作業の自動化・効率化のための情報を発信するブログ(VBA,PowerShellなど)

VBAでウェブスクレイピング_IE操作編_その2

HTMLとは

今回もハンズオン形式でIEを使ったスクレイピングのやり方を書いていきます。 前回は、HTMLの話をしていたのでした。HTMLはウェブページの設計図のようなものであること、ブラウザはそれを頼りに、ページを表示していること。

VBAのコードに戻る前に、もう少しHTMLの理解に時間を裂きましょう。

理解のために、簡単なHTMLを見てみます。

<html>
    <body>
    Hello World
    </body>
</html>

これをメモ帳などで拡張子を「.html」として、保存するとブラウザで開くことができます。
f:id:shinmai_papa:20190729224918p:plain

HTMLの正体はただのテキストなんですね。

HTMLのすごいところは、タグというものを文書の中に埋め込むことで、その文書に構造を与えている点です。

この「構造」と、それを表す「タグ」を理解することがHTMLを理解するになります。
これを押さえておけば、スクレイピングはそんなに難しいことではありません。

タグとは

タグは基本的に、<○○>~</○○>という形をとります。○○がタグ名、内容が~になります。また、タグには属性を与えることができます。
例えば、ウェブページにリンクを貼りたいときは、aタグというタグを使います。リンク先のアドレスはhrefという属性で定義します。
こんな感じで定義します。

<a href="sample.html">リンク先</a>

HTMLにアクセスするときは、このタグと属性の情報を手掛かりに行います。

IEを使ったスクレイピングでも、このタグと属性を使います。

IEを使ったスクレイピングにおいて、必要となる動きは、

  • 情報を集める
  • リンクをクリックする
  • インプットボックスに入力する

が半分以上な気がします。

それぞれについて説明してみます。

情報を集める

情報を集める。これがまさにスクレイピングを行う目的ではないでしょうか。
HTMLの中にある情報を取得する際に知っておくべき属性が二つあります。それは、

  • id属性
  • class属性

の二つです。それぞれの使い方のイメージはこんな感じです。

<h1 id="title"><a href="https://www.tekizai.net/">適材適所</a></h1>
<a class="entry-title-link" href="https://www.tekizai.net/entry/2019/07/25/080000">VBAでウェブスクレイピングをマスターする_IE操作編①</a>
<a class="entry-title-link" href="https://www.tekizai.net/entry/2019/07/24/080000">VBAを使ってIBM Notesでメールを送信する_ファイルを添付する</a>

このブログのトップ画面かソースから抜粋です。

1行目のh1タグとは、そのページの一番大きな見出しです。h1タグにtitleという、意味を与えています。

2、3行目のclassは、aタグにentryという意味を与えています。

機能は似ており、タグに意味を与えます。id="title"は同じページ上で存在してはいけないのに対し、class=""entry-title-link"はいくつ存在してもいいという違いがあります。

主な使われ方はウェブページのデザインなどです。例えば、id=titleの部分のフォントサイズを大きくして・・・といった使われ方です。デザインは一般的にCSS(カスケーディングスタイルシート)というもので定義されます(興味のある方はググってみてください)。

スクレイピングで情報を集めるときは、この二つのタグが重要な意味を持ちます。

ここで一旦、VBAのコードに戻ってみましょう。

こんなコードを前回書いたのでした。

Sub sample()
    Dim ie As InternetExplorer
    
    Set ie = New InternetExplorer
    
    ie.Visible = True
    
    ie.Navigate "https://www.tekizai.net/"
    
    Do While (ie.Busy Or ie.ReadyState <> READYSTATE_COMPLETE)
        DoEvents
    Loop

    Dim htmlDoc As HTMLDocument
    
    Set htmlDoc = ie.Document
    
    Set ie = Nothing
End Sub

前提として、「Microsoft Internet Controls」と「Microsoft HTML Object Library」の参照設定が必要なコードです。

何をするコードかというと、インターネットエクスプローラーを開始し、可視化し、このブログにアクセスし、HTMLを取得するというコードでした。

そうそう、HTMLを取得してみたところまでやってみたのでした。

Set htmlDoc = ie.DocumentでHTMLが取得でき、このHTMLの中身を解析して取得したいタグや属性を探し出します。

解析と聞くと難しく聞こえますが、やってみると案外簡単にできる・・・場合が多いです。ウェブページの作られ方次第ですが・・・。

実際のHTMLを見てみる

IEの機能で、実際のHTMLを見ることが出来ます。

右クリックすると出てくるメニューの中に「ソースの表示」がありますので、それをクリックします。

f:id:shinmai_papa:20190729225204p:plain

ソースを見てみると、色々なことがごちゃごちゃ書かれており、訳がわかりません。でもめげないでください。

今回の目的は、ブログのエントリ一覧を取得してみることです。

とにかくとっかかりが必要です。試しに、ブログのエントリの文字列を検索してみましょう。

f:id:shinmai_papa:20190729225248p:plain

あ、ありました。

もうちょっと検索してみると、どうやらブログの記事タイトルは、aタグ+entry-title-linkというclass名で囲まれてるっぽい、という予想がつきます。

試してみましょう。

HTMLからclass属性を取得する

特定のclass属性を取得したい場合は、HTMLDocumentオブジェクトのgetElementsByClassNameメソッドを呼び出します。 引数は、取得したいclass名です。今回の場合は「entry-title-link」です。

Sub sample()
    Dim ie As InternetExplorer
    
    Set ie = New InternetExplorer
    
    ie.Visible = True
    
    ie.Navigate "https://www.tekizai.net/"
    
    Do While (ie.Busy Or ie.ReadyState <> READYSTATE_COMPLETE)
        DoEvents
    Loop
    
    Dim htmlDoc As HTMLDocument
    
    Set htmlDoc = ie.Document
    '追加
    Debug.Print htmlDoc.getElementsByClassName("entry-title-link")
    
    Set ie = Nothing
End Sub

これを実行してみると・・・
f:id:shinmai_papa:20190729225330p:plain

エラーですね・・・。

なんででしょう。

getElementsByClassNameメソッド

新たに追加した「getElementsByClassName」が怪しいですね。

この言葉をバラバラにすると、get Elements By Class Name。

get Elements By Class Name

Elementsとなっています。

これ、実は要素を複数取得するものなのです。

そのため、Debug.Printする際に、要素数を指定する必要があります。

要素は0から始まります。とにかくコードを修正してみましょう。

Sub sample()
    Dim ie As InternetExplorer
    
    Set ie = New InternetExplorer
    
    ie.Visible = True
    
    ie.Navigate "https://www.tekizai.net/"
    
    Do While (ie.Busy Or ie.ReadyState <> READYSTATE_COMPLETE)
        DoEvents
    Loop
    
    Dim htmlDoc As HTMLDocument
    
    Set htmlDoc = ie.Document
    '修正
    Debug.Print htmlDoc.getElementsByClassName("entry-title-link")(0)
    
    Set ie = Nothing
End Sub

これを実行してみると、イミディエイトウィンドウに

https://www.tekizai.net/entry/2019/07/25/080000

と表示されました(実行するタイミングにより異なるURLが出力されます)。

なんか違う。

これじゃない感がすごいです。

欲しいのは、URLじゃなくて、記事タイトルなんです。

もうちょっと「getElementsByClassName」について調べてみます。

困ったときは、VBEのオブジェクトブラウザーです。VBEの画面でF2を押すと出てきます。検索窓にいれて、検索してみます。

f:id:shinmai_papa:20190729225508p:plain

おぉ、どうやらgetElementsByClassNameの戻り値はIHTMLElementCollectionみたいですね。

うん、なんだ、このIHTMLElementCollectionとは。

これも調べてみます。
f:id:shinmai_papa:20190729225531p:plain

MSHTMLの中にあるClassみたいですね。

まだわかりません。もうちょっと詳しく見てみます。

f:id:shinmai_papa:20190729225601p:plain

緑のアイコンの左上に緑の○があるメンバー、これがメソッドの呼び出しを省略したときに呼ばれるメソッドです。

オブジェクトブラウザによると、getElementsByClassNameメソッドを呼び出したときによばれていたのは「item」というプロパティみたいですね。

ではitem(0)はどんなデータ型なのか、下記のコードで見てみましょう。

Sub sample()
    Dim ie As InternetExplorer: Set ie = New InternetExplorer
    
    ie.Visible = True
    
    ie.Navigate "https://www.tekizai.net/"
    
    Do While (ie.Busy Or ie.ReadyState <> READYSTATE_COMPLETE)
        DoEvents
    Loop
    
    Dim htmlDoc As HTMLDocument: Set htmlDoc = ie.Document
    '修正
    Debug.Print TypeName(htmlDoc.getElementsByClassName("entry-title-link").Item(0))
    
    Set ie = Nothing
End Sub

実行すると、HTMLAnchorElementと出てきました。

HTMLAnchorElementについて、オブジェクトブラウザーで検索してみるとデフォルトプロパティは「href」つまりリンク先のURLのようです。

f:id:shinmai_papa:20190729225637p:plain

既定のプロパティはhrefなので、https://www.tekizai.net/entry/2019/07/25/080000が返ってきたのですね。

ここで注意が必要なのは、今回取得したキー「entry-title-link」はたまたまHTMLAnchorElementでしたが、どんな要素が返ってくるかケースバイケースで変わります。

タイトルを取得する

ではタイトルを取得したい場合はどのようにすればよいでしょうか。

f:id:shinmai_papa:20190729225714p:plain

改めてHTMLを見てみるとタイトルはタグに囲まれていますね。○○という形です。

タグに囲まれた○○を取得したいのです。

その場合、HtmlAnchorElementのinnerTextというメソッドで取得することができます。

これはよくあるパターンです。

では実際に取得してみましょう。

Sub sample()
    Dim ie As InternetExplorer: Set ie = New InternetExplorer
    
    ie.Visible = True
    
    ie.Navigate "https://www.tekizai.net/"
    
    Do While (ie.Busy Or ie.ReadyState <> READYSTATE_COMPLETE)
        DoEvents
    Loop
    
    Dim htmlDoc As HTMLDocument: Set htmlDoc = ie.Document
    
    Dim htmlAnc As HTMLAnchorElement
    
    Set htmlAnc = htmlDoc.getElementsByClassName("entry-title-link").Item(0)
    
    Debug.Print htmlAnc.innerText
    
    Set ie = Nothing
End Sub
VBAでウェブスクレイピングをマスターする_IE操作編_その1

欲しいものが取得できました。やっと一つのタイトルを取得できました。

うーん、だいぶ長くなってしまったので、いったんここまでにして、次回、すべてのページを遷移してすべての記事タイトルを取得してみたいと思います。

お読みいただき、ありがとうございました。

www.tekizai.net