今回はVBAプログラミングのお話です。
今まで紹介してきたIE操作系の処理で、「指定したURLを開いてテキストなどのデータを取得する」というものがありました。
今回はこの処理を違う形で実装して、処理を高速化してみます。
用途について
まず今回の処理がどういう場面で使われるかと言うと、例えばAmazonやヤフオク、楽天といった1ページの画面内に色々な商品のリンクが存在するページにおいて、
「それぞれのリンク先の商品データの内容を全て取得する」
という処理を組むとします。
その場合、通常であればそれぞれのリンクからURLを取得して、
「ie.Navigate2 “http://www.~~”」
という風に1ページずつ画面を開いてからデータの読み込みを開始しますよね。
数件や数十件であればこの方法でも全く問題ありません。全て処理しても掛かる時間は数分程度でしょうし。
しかしこれが数百件、数千件ともなると、場合によっては数十分~数時間単位の作業になります。更にその作業を毎日行うとした場合は・・・結構な時間になりますよね。
既にプログラムで高速化できているとは言え、欲を出して「もう少し時間を短縮できないかな・・・」と考えてしまう所だと思います。
開く先のページによっても変わると思いますが、『ただデータを取得する』というスクレイピングツールにおいて処理の妨げになるのは『商品画像や外部ファイル(スクリプト等)の読み込み』です。
必要であれば画像のURLさえあればダウンロードできるため※1、わざわざブラウザ側で読み込む必要は無いのですが、ブラウザソフトでサイトにアクセスした際は自動的にこれらのファイルの読み込みを行ってしまいます。
※1画像のダウンロードについては以下の記事を参照
【商品ページからボタン一つで情報を取り込むVBAプログラムの作り方】
ファイルの読み込みに掛かる時間がそのままページの読み込み完了までの時間の増加に繋がりますので、件数が多ければ多いほど全体の処理時間が低下します。
ただ、この邪魔な画像については実は設定を変更するだけで簡単に読み込みを停止することができます。
上記の様にIEのインターネットオプションを開き、「画像を表示しない」という設定にしていれば画像ファイルが読み込まれる事無く処理がスムーズになりますが、それでもスクリプトファイル何かは読み込まれますし普通にブラウジングしたい場合は一々設定を戻さなければいけません。
これもまた面倒です。
という事で、プログラム側で「htmlソースだけ」を読み込む様な処理を用意します。
VBAコード
コードについてはざっとこんな感じ。
Public Function getHtmlDoc(url As String) As Object Dim objHtml As Object 'サイトデータの格納先 Dim objDoc As Object 'ドキュメントの格納先 'XMLHTTPオブジェクトを生成 Set objHtml = CreateObject("MSXML2.XMLHTTP") On Error Resume Next Call objHtml.Open("GET", url, False) Call objHtml.send 'ロード完了まで待つ Do While objHtml.readyState <> 4 Sleep 100 DoEvents Loop 'DOM操作を行える様にする Set objDoc = CreateObject("htmlfile") Call objDoc.write(objHtml.responseText) Set getHtmlDoc = objDoc End Function
途中にある「readyState」で読み込みの完了を待つ処理については、もしかしたら不要かもしれません。
エラーを防ぐ意味で念のためチェックしていますが、その直前にある「send」処理を実行した時点で読み込みまで行っている様なので、無くてもエラーが出ない様であれば省いて大丈夫だと思います。
そして重要なのが後半の2行です。
objHtml.sendが終わった段階でドキュメントのデータは取得できていますが、このままではDOM操作を行うことができません。
「CreateObject(“htmlfile”)」でHTMLDocumentのオブジェクトを作成し、そこに取得してきた「responseText」の内容を書き込むことで、この『objDoc』は『ie.Document』を扱っているのとほぼ同じ状態になります。
上記処理はそのままコピペで動作しますが、環境によっては「CreateObject(“MSXML2.XMLHTTP”)」の部分の修正が必要になる様です。
注意点など
上記処理で返ってきたオブジェクトは、ただのウェブサイトのソースのコピーです。
従って、inputタグの値にデータをセットしようとしたり、リンクやボタンをクリックしても何も起こりません。
あくまでも”情報を読み取るだけ”という用途に限定した場合の処理になるので、勘違いしない様にして下さい。
当然ですが、ユーザーの操作によって動的に情報が変化するサイトなんかも正しく情報を取得できません。スクリプトが動かないので、何らかのアクションによって変化するべき部分がそもそも変化しないためです。
取れるのは”サイトを開いた時点”で得られる情報だけとなります。
用途によって使い分けが必要となると思いますが、使いこなせれば便利だと思いますので色々試してみて下さい。
実行速度の比較
最後に、じゃあ実際どれだけ処理時間が変わるのかというのを見てみたいと思います。
用意するのはこちらの二種類の処理。
Sub test1() Dim objIE As InternetExplorer Dim objDoc As Object Dim startTime As Long startTime = GetTickCount Set objIE = CreateObject("InternetExplorer.Application") objIE.Navigate2 "https://www.yahoo.co.jp/" waitBrowseLoading objIE Debug.Print objIE.document.title Debug.Print "掛かった時間:" & (GetTickCount - startTime) / 1000 & "秒" End Sub Sub test2() Dim objDoc As Object Dim startTime As Long startTime = GetTickCount Set objDoc = getHtmlDoc("https://www.yahoo.co.jp/") Debug.Print objDoc.title Debug.Print "掛かった時間:" & (GetTickCount - startTime) / 1000 & "秒" End Sub
test1の方は従来通りIEウィンドウの起動からページ遷移を行うという処理。
test2は今回用意したドキュメントのみを取得するという処理。
最終的にイミディエイトウィンドウに指定したサイトのタイトルを出力するまでに掛かった時間を計測しています。
ちなみにtest1側のIE読み込み処理に使っている「waitBrowseLoading」という処理についてはこちらの記事を参照して下さい。
⇒ショッピーズへの出品を自動化するVBAプログラムを組んでみる①
今回は対象のサイトをヤフージャパンとし、それぞれ処理の実行前にIEのキャッシュクリアを行っています。
そして実行結果がこちら。
【test1】
Yahoo! JAPAN
掛かった時間:2.527秒
【test2】
Yahoo! JAPAN
掛かった時間:0.577秒
おぉ。
実は計ってみたのは今回が初めてなのですが・・・随分と顕著な結果になりましたね。約5倍近い差が出ました。
サイトにアクセスする以前に、IEの起動自体に時間が掛かってしまっているのも理由の一つですね。
test2の処理はブラウザキャッシュの影響を受けないので、キャッシュクリアは単純にtest1側にとって不利だったかもしれません。
今回はイラストやアイコンが多めなサイトとしてヤフージャパンを対象にしましたが、それぞれの素材が軽量化されていると思われるヤフーでもこの結果です。
画像だらけでサイズをあまり意識していないショッピングサイトなどですと、通信環境にもよりますが更にこの倍の時間は掛かるのでは無いでしょうか。
今回の処理はそういうサイトで特に力を発揮しそうですね。
コメント
れいんさま
こんにちは。いつも拝見させていただいております。このサイトがvbaを学習する私にとっての指南書になっており、大変感謝しています。
現在、amazonのwebページを読み込み、中に書いてある情報を抽出するプログラミングを作っております。
こちらのページに書いてある、Navigateメソッドを使わないで、該当ページのhtmlだけを読み込み、処理速度が著しく速くなったことについて、ものすごくメリットを感じています。ありがとうございます。
ところで、ひとつ問題が発生してしまったのですが、以前はamazonの生のURL(とても長い)をgetHtmlDocの引数として使っていたのですが、現在は、ASIN番号を使った短いURL(https://www.amazon.co.jp/dp/”ASIN番号”)を引数として使っています。
以前(長いURL)は問題なくページを読み込むことに成功していたのですが、現在(短いURL)はページを読み込んだり、読み込まなかったり動作が不安定になってしまいました。
何か、ヒントがあればご指摘いただけないでしょうか。お忙しい中だと思いますが、何卒よろしくお願いいたします。
たびたび、失礼します。
URLの長短で成功しないというのは結果論であり、本質は他の所にあることが分かりました。まだ成功していませんが、お手数おかけして申し訳ございませんでした。
ご連絡が遅くなり申し訳ありません!
原因は別の部分だったのですね。
根本的な解決にはなりませんが、返ってきた変数の中にドキュメント取得成功可否に関する値もあります。
その値を見て成功するまでループする処理を入れる様にしても、安定性は向上させられるのでは無いかと思います。
れいんさま
お返事ありがとうございます。どうやらここ数日間繰り返して実験した結果、成功する場合と成功しない場合が現れたのですが、成功しない場合の特徴としては、何回もAmazonの商品ページにアクセスして、読みに行ったあとになるようでした。逆に、成功する場合はある程度アクセスする時間間隔をあけてから、読みに行った場合に成功することが分かりました。たぶん、Amazonのサイトの方で、短時間に何回もサイトにアクセスしないための対策が講じられているのかもしれません。。。
あーそれはあるかもしれません・・・というか思い出しました。あります。
以前数千件の商品データをAmazonから取得する作業を行った際、時間短縮のためにこのXMLドキュメントを取得する処理を用いたのですが、その際数件の取得を行った後にエラーが連続して取得できないという状況に陥りました。
その後は仕方なくIEで一つ一つページを開いてという処理に変えたのですが、負荷を軽減するために対策されているのでしょうね。
他のサイトでも同様の現象が発生し、「MSXML2.XMLHTTP」でリクエストを飛ばして取得するという方法が悪いのかと思い色々と試したりもしたのですが、結局解決できずの状態です。
Amazonのデータ取得については速度や安定性を考えてもAPIを使うのが一番だと思います。
以下のシリーズでその辺の方法にも触れていますので、ぜひ参考にしてみて下さい。
rabbitfoot.xyz/vba-programming-amazonapi1/
れいんさま
お返事ありがとうございます。
やはりそうでしたか。。いろいろ調べてみたところ、DDOS攻撃対策とか呼ばれているようですね。別に攻撃するつもりでアクセスはしていないのですが。。
AmazonAPIは大口出品者しか利用できないサービスですよね?ちょっと考え直してみます!
このたびはありがとうございました。また今後ともこのサイトを拝見させていただきますので、何卒よろしくお願いいたします。
いえ、APIはアカウント登録さえすれば誰でも利用できますよ。
現に私は大口も小口の契約もしていませんし。
ただ当ブログではアカウントの取得方法については解説しておりませんので、お手数ですが登録方法については他のサイト様でご確認下さい。
Amazonアソシエイト(アフィリエイトプログラム)への登録が必要なので、もしかしたら自分用のサーバなりサイトを持っていないとダメだったかもしれませんが・・・
はい、わかりました。大口出品者というのは私の勘違いでした。自分のサイトとかは持っていないので難しいかもしれませんが、方法を調べて試してみますね!
このたびはご親切にご回答していただきありがとうございました(*^_^*)