ExcelVBAでWebサイトのソースを取り込んでみよう
これまでスクレイピングについての解説はあまり推奨したくなかったため避けるようにしていました。しかし、今後解説したい内容を考える上で避けては通れないため、今回記事にすることにしました。
まえがき
みなさんは毎日いろいろなサイトを巡回し、様々な情報を得ていると思います。
しかし、サイトによってはその情報が深いところにあり、またその内容を記録しておきたいとき、手作業での情報収集は大変な手間になってしまいます。
そこで、効率的にWebで情報を収集するための手法が、スクリプトを用いたスクレイピングです。
今回はそのスクレイピングについて解説します。
スクレイピングの是非
スクレイピングは、WEB動作アプリケーションを作る上で基本的な技術であって、それ自体に問題があるわけではありません。むしろ、その利用目的や方法に問題が存在する場合が多いのです。例えば情報収集のため何回もアクセスした結果WEBサイトに高負荷がかかり、サーバーの機能が停止してしまい偽計業務妨害に問われたり、収集した情報が著作物であった場合に著作物の二次利用により権利の侵害が問われる事態となりやすいのです。
まあ実際は検索エンジンとかも、ものすごい勢いでサーチしてるんだけどさ。
この辺は問題になっていた時期もありました。 逆に言うと、大手のサーチエンジンといえど例外ではないということです。難しい問題を孕んでいるのです。安易にググレカスとか言っちゃいけませんね。
そのため、WEBサイトによっては、明示的にクロール、スクレイピングを禁止しているところもあります。あるいは許容されるとしても、条件を守らなければならない場合があります。僕も基本的にはWEBサイトのアクセスやデータ利用については、WEBサイト管理者側の想定した用途及び方法で利用すべきだと考えています。
しかし、WEBサイトへのアクセスを含めた手法の構築はWEBプログラミングにおいて基本的なところですし、本記事ではAPIの活用といった高度な手段はもう少し後に取り上げる予定ですし、単一ページのhtmlパース程度の通常およそ想定される程度の負荷及び解析の内容を記事にして解説しようと考えています。
そもそも初学者が作成するスクレイピングについては(うっかり鬼のような高負荷無限ループを作ってしまった場合を除き)通常のブラウジングと同程度かそれ以下の負荷になるであろうと考えています。
そこに配慮し、今回はスクレイピングの基本を作成する上で、以下の三つの点について備えることにしました。
- 高負荷なループを作らない → Sleepの実装/ループ内に組込み
- 何度もソースを獲得しない → ソースの出力ができるようにする
- できるだけ情報を得たいページをピンポイントで扱う → リンクをたどって収集する系のスクリプト(クロール)にしない
この三つの要件が備わっていれば、おおよそ通常の想定内の負荷に収まってくれるのではないかと思います。
初心者にはちょっと敷居が高いかもしれませんが、最初が肝心です。
そんなわけで、上記の点について注意しつつスクリプトを作ってみましょう。
スリープを定義しよう
いきなり面倒な話から始まります。
簡単な方法をいくつか考えようと思ったのですが、Windowsの場合、WIN32APIのkernel32のAPI(Application Programming Interface)関数のSleepを使うのが手っ取り早いようです。
ちなみにAPIって何って人向けに。APIというのはアプリケーションコンポーネント間でデータのやり取りを行う機能です。ちなみにドメイン間でWeb上のデータのやりとりに使用されるものもAPIといいます。今回はそれを使用します。
さっきまではAPIの説明は後々って言ってたんだけどね、ごめんね。
この段階では詳しく理解する必要はありません。簡単にいうと
ファイナルファンタジーの黒魔法にあるスリプルを使えるようにするよ。
くらいのニュアンスで大丈夫です。
Private Declareという宣言でDLLの呼び出しを行います。難しいことはさておき、以下のように記述すれば多分動きます。うまく動かない場合はPtrSafeを削除してみてください。
'API(Application Programming Interface)によるSleep(1/1000秒の停止)を定義
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
これでkernel32のライブラリにあるSleepという機能を使用できるようになります。
例えば
Sleep(1000)
と書けば1000ms(1秒間)の時を停止することができます。
え? こんな簡単でいいの?
と思うかもしれませんが、API関数の良いところは、APIを呼び出すだけで、その中で使われている関数を自由に使えることにあります。
空気を吸って吐くことのように!HBの鉛筆をベキッ!とへし折ることと同じようにッできて当然と思うことですじゃ!大切なのは「認識」することですじゃ!
というわけで早速プロシージャを作ってみましょう。
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
sub theworld()
Sleep(1000)
end sub
このtheworldを実行すると、処理が1秒間停止します。完璧ですね!
試しに実行してみましょう
・・・
うん、1秒止まった・・・よな?(実感なし)
一秒停止できるからと言って、その停止した時間に自由に動けるとは言ってないからね・・・
webページを取得しよう
それでは早速WEBサイトのデータ取得を行ってみます。先に説明したtheworldも組み込みますので、続けて書いていきましょう。
Sub creObj()
WEBページを取得するにはいくつか方法があるのですが、今回はHTMLオブジェクトに対象ページを格納することで利用したいと思います。これにより、アクセス頻度が下がりますし、インテリセンスも使えるのでミスタイプも減るでしょう。
オブジェクトの説明は後に回すとして、オブジェクトを利用するためにもひと手間必要になります。
VisualBasic(マクロ画面のウインドウ)の「ツール」から「参照設定」を選び、「Microsoft Internet Controls」「Microsoft HTML Object Library」にチェックを入れましょう。
そしたらInternetExplorerとHTMLDocumentのオブジェクトを宣言します。
Dim objIE As New InternetExplorer
Dim objHtml As HTMLDocument
前もっての準備はこれだけです。
InternetExplorerオブジェクトの設定
オブジェクトを宣言出来たら、オブジェクトのプロパティを設定していきましょう。
オブジェクトはオブジェクトの種類ごとにいくつかのプロパティ(値)とファンクション(機能)を持っています。
オブジェクトというのは、共通の特徴を持つ物体をまとめて表す概念になります。イメージで言うと下表のような感じです。
この表では3つのプロパティと2つのファンクションで表現していますが、実際には材質、縫い目、重さ、弾性などたくさんのプロパティを持たせたりもします。
同じオブジェクトの種類でも、それぞれのプロパティは異なります。その時に必要なものを宣言すればよいでしょう。
テニスをするのであればテニスボールを宣言して呼び出してあげればよいのです。
ここで、扱いたいのが軟式のテニスボールであった場合、あとから色を白にすれば軟式テニスボールになりますね(ならんけど)。そうすると、ほかのプロパティは最初から一致しているので定義の手間がはぶけるのです。バスケットボールのサイズも5号球にしたかったなら、あとからより小さく書き換えるだけで良いのです。
このように、予め必要なプロパティと初期値が設定されたものがあると、プログラミングが容易になります。大規模なプロジェクトになればなるほど便利になります。
オブジェクトについては、オブジェクト指向プログラミングの解説をもしかしたら将来記事にするときに詳しく解説します。
さて、話を戻しましょう。InternetExplorerオブジェクトにはプロパティもファンクションもたくさんあるのですが、オブジェクトを宣言した時点で初期値が設定されますので、使う部分だけ変更すれば十分です。
ウェブサイトを閲覧しようとしたとき、IEを開いたらまずウインドウを開いて、URLを打ちこみますね。それと同じように今回は、表示・非表示とアクセスする先のURLのみ変更してみます。
テストの設定先はヤフージャパンにしてみました。サーチエンジンのトップページはそもそも相当なアクセスに耐える設計でしょうし、サーチエンジン自体が他所のサイトをスクレイピングすることで成り立っていることを考えると、相互互恵関係にあるべきであると言っても過言ではありません。情報量も多く、トピックなどが随時更新されるため、スクレイピングの練習にはもってこいというわけです。
With objIE
.Visible = True '非表示でやるならFalse
.navigate "https://www.yahoo.co.jp/"
End With
次にトップページが表示されるまで待機させます。早速最初に作ったtheworldの出番です。
'画面表示待機
Do While objIE.Busy = True Or objIE.readyState <> READYSTATE_COMPLETE
theworldLoop
theworld
Loopの最後に念のためトドメの連続theworldを発動しています。原作再現ですね(何が)
htmlオブジェクトの取得
さて、実はInternetExplorerオブジェクトの中にはhtmlオブジェクトが格納されています。今度はそれを引き出してみましょう。
'html解析
Set objHtml = objIE.document
outhtm = objHtml.all.tags("html")(0).outerHTML
引き出したhtmlオブジェクトから、HTMLそのものを出力します。このデータを今度はエクセルのシートに一行ずつ書き出したいと思います。こうすれば、出力されたソースを確認するのも簡単ですし、行数の確認もやりやすいと思います。
htmlineという配列を宣言し、改行コードLFで区切ればできあがりです。
Dim htmline As Variant
Sheets("html").Select
htmline = Split(outhtm, vbLf)
htmline()のデータを書き出していきます。配列に対してはCells(i, 1).OFFSET(1,0).Value派の人もいますが、どちらでも良いです。
コメントアウトしていますが、 Cells(i + 1, 1).Interior.Color = RGB(0, 0, 255)というのは謎の改行連打がソースに存在した場合に、後半のコードを見落としてしまうので背景色を付けるようにアレンジしたコードです。
For i = 0 To UBound(htmline) - LBound(htmline) - 1
Cells(i + 1, 1).Value = htmline(i)
' Cells(i + 1, 1).Interior.Color = RGB(0, 0, 255)
Next
最後にオブジェクトの破棄を行います。プロセスに多量のオブジェクトが増えてしまうのを防ぐためです。この辺もお約束として押さえておいたほうが良いですね。
'オブジェクト破棄
objIE.Quit
Set objIE = Nothing
End Sub
次回は、取得したコードを解析して必要な情報を整理するコードに進んでいきますよ。
コード書くのは20分程度だったのに、blogの記事のほうが圧倒的に時間がかかりました。それでも少しでも参考になれば幸いです。