初めて質問させていただきます。
VBAにて、Webサイトからオンメモリで特定のタグ部分を抜き出すプログラムを作成しております。方法としては本サイトの質問項[HTML取得について],[WebbrowserコントロールなしでのDHTMLの操作]等々に書かれていた内容を基に組みました。
しかし実行においてステップインを用いての処理であれば正常に終わるもの、全部を一度に行った場合(F5)は変数aaaの値が反映されずnothingとなり以降がエラーとなっています。
ストリーミングを行う時間が必要なのであろうと解釈しているのですが、forループやWait関数を用いても改善が見られないためどの様な対応を行えばよいか質問させていただいております。
少しVBから趣旨がずれているかも知れませんが、処理手順としてもっとスマートなやり方等もあればご指導よろしくお願いします。
'VBAにてWebサイト(HTTPver:XHTML1.0 Transitional)からMSXML2.XMLHTTP を用いてソースを入手
'---
Set http = CreateObject("MSXML2.XMLHTTP")
If (Err.Number <> 0) Then
Set http = CreateObject("MSXML.XMLHTTPRequest")
End If
With http
.Open "GET", url, False
.send
End With
'---
'メモリ上からソースを読み込み、あるタグ<"PRE">中の値をdebug出力
'---
Dim doc As MSHTML.HTMLDocument
Set doc = New MSHTML.HTMLDocument
Dim stm As IPersistStreamInit
Set stm = doc
stm.Load http.responseStream
Set doc = stm
Set aaa = doc.getElementsByTagName("PRE").Item(0) '.innerText
buf = aaa.innerText
Debug.Print buf 'doc.getElementsByTagName("PRE").Item(0).innerText
---
> ストリーミングを行う時間が必要なのであろうと解釈しているのですが、forループやWait関数を用いても改善が見られないためどの様な対応を行えばよいか質問させていただいております。
紹介されてる過去ログで
> MSHTMLパーサによってHTMLが解析されるには、若干の時間が必要なのです。
>
> WebBrowserコントロールを使って処理する時にも、DocumentCompleteイベントが発生するまで、解析は完了しませんよね。それと一緒です。
> Loadメソッドで読み込んだ直後は、WebBrowserでいえば、まだNavigateCompleteイベントが発生した段階に過ぎないというわけです。
と魔界の仮面弁士さんは書かれていますが。
WebbrowserコントロールなしでのDHTMLの操作
http://madia.world.coocan.jp/cgi-bin/VBBBS/wwwlng.cgi?print+200401/04010013.txt
熊谷隆史さん
書き込みありがとうございます。
はい、隆史さんがご指摘の通り、魔界の仮面弁士さんの記事にてそのことが書かれています。
よって解析完了迄の時間を待つ、若しくは終了条件を設定すればプログラムは正常に動くと思われます。
しかし、力足らずでreponsestreamの返す値に対しての終了条件を見つけることが出来なかったため、(http://tomizawa-web.hp.infoseek.co.jp/property/responseStream.htm)
苦肉の策としてプログラム後半
stm.Load http.responseStream
Set doc = stm
Set aaa = doc.getElementsByTagName("PRE").Item(0) '.innerText
の前後に適宜forループやWait関数で数秒処理を遅延させました。
問題はそれでもストリームは終わらないのか、再度何か処理が必要なのか、aaaへ結果が入力されないという部分です。
事情説明と判らない箇所が荒くなってしまい申し訳ありません。
ストリームの際の時間稼ぎ若しくは終了を認識する方法をご指導いただければ幸いです。
(書き損じておりましたがOSはWindowsXPを使用しています)
koba-chanさん、即レスどうも。
難しく考え過ぎじゃないですか。
DOM(MSHTML.HTMLDocument)の
readyStateプロパティが"complete"を
返すまでループするってことです。
(過去ログの様にイベント処理してもいいですけど)
> stm.Load http.responseStream
> Set doc = stm
Do
DoEvents
Loop While doc.readyState <> "complete"
> Set aaa = doc.getElementsByTagName("PRE").Item(0) '.innerText
熊谷隆史さん
お返事ありがとうございます、隆史さんの回答も素早いですね。
>DOM(MSHTML.HTMLDocument)の
>readyStateプロパティが"complete"を
>返すまでループするってことです。
> (過去ログの様にイベント処理してもいいですけど)
readyState:http://tomizawa-web.hp.infoseek.co.jp/property/readyState.htm
readyStateで受信側の状態が取得出来るのですね、DOMで使用出来ることを失念しておりました。
先ほどdebugで確認をした所、docの中にきちんと状態表示がされているのを確認し、プログラムも正常に動作するようになりました。
ありがとうございました。
koba-chanさん、ご報告どうも。
後から気付いたのですが。
> stm.Load http.responseStream
Set doc = stm ← ここは要らないのでは。
Do
DoEvents
Loop While doc.readyState <> "complete"
> Set aaa = doc.getElementsByTagName("PRE").Item(0) '.innerText
ついでに、
XMLHTTPはキャッシュを拾うので
ServerXMLHTTPの使用も検討された方が
いいかと。
> Do
> DoEvents
> Loop While doc.readyState <> "complete"
このループ中にインターネットアクセスが
発生しないと思われているなら間違いです。
(HTML構造にもよりますが)
> 数秒処理を遅延させました。
Download Controlを設定しないから
かも知れませんね。
過去ログだと、この辺。
http://madia.world.coocan.jp/cgi-bin/VBBBS/wwwlng.cgi?print+200603/06030080.txt
http://madia.world.coocan.jp/cgi-bin/VBBBS/wwwlng.cgi?print+200705/07050038.txt
手順としては、
IOleClientSiteの実装(クラスモジュール)が
必要になります。
そのインスタンス(IOleClientSite型にキャストしてから)を
DOMに対してIOleObject::SetClientSiteで
関連付けさせてから
読み込むと言った感じです。
こちらのサイトで
shiraさんから、教わりました。
http://www2.moug.net/bbs/exvba/
では。
隆史さん
追加情報の書き込みありがとうございます。
実装して状況報告をしてから返事を書かせていただこうと思ったのですが、
時間が掛かりそうなので先にご挨拶をば。
どのような時にどのような関数を使うという感覚がまだ無いため、これからも精進していきます。
同じプログラムを一度に複数回行う予定なので、隆史さんのやり方や紹介していただいた内容を理解して少しでも早く動くよう実装してみますね。
ありがとうございました。
# もうご覧になってないかも知れませんが。
難しいことをなさろうとしているのですから
それに見合う労力とか努力が必要なので
あまり、世話を焼き過ぎるのもどうかと思いますが、
さすがに難し過ぎるのでもう少しだけヒントを
載せておきます。
えーと、VBで定番(?)のタイプライブラリを
お使いのようですが、本来は自分で作成した方が
勉強にもなるし、好ましいでしょうね。
(私も自前で作成しました)
Windows SDKに付属するmidl.exe(*.idl→*.tlb)で
コンパイルするのですが
環境変数にパスを通すのが面倒なので
「Visual C++ 2008 Express Edition」を導入すると
楽チンです。
そのタイプライブラリにて
IOleClientSiteインターフェースだけを定義します。
(DispCallFuncを使う場合)
それを参照設定して、Implementsで実装する訳です。
更に、DISPID_AMBIENT_DLCONTROL(-5512)という
Dispatch IDを持ち、DLCTL_XXXXXの値を返す
プロパティプロシージャも必要です。
(クラスモジュール)
http://msdn2.microsoft.com/en-us/library/aa770041(VS.85).aspx
http://msdn2.microsoft.com/en-us/library/aa741313(VS.85).aspx
ただ、VBAにはVB6での
プロシージャ属性→プロシージャIDなる設定項目が
存在しません。
なので、モジュールのエクスポート、インポートに
よるAttribute文の有効化が必要になります。
後は
> そのインスタンス(IOleClientSite型にキャストしてから)を
> DOMに対してIOleObject::SetClientSiteで
> 関連付けさせてから
で、それから今お使いの
DOMからIPersistStreamInitを取得して
Loadメソッドで読むと。
WebBrowserのケースとは異なり、
今回のDOMでは
ある簡単なことをしなくてはいけません。
(クラスモジュールにて)
そうしないと現状のままでは単純に
実行時エラーが発生するだけになります。
また、クラスモジュールはそのままに
> Do
> DoEvents
> Loop While doc.readyState <> "complete"
の後にある処理を追加しても、大丈夫だそうです。
> > Do
> > DoEvents
> > Loop While doc.readyState <> "complete"
>
> このループ中にインターネットアクセスが
> 発生しないと思われているなら間違いです。
> (HTML構造にもよりますが)
これは画像やActivXです。
後は、
ご自分で試行錯誤されてください。