think or die :
1970年代生まれの
人たちのための
エッセー集
>ITを考える
>同月のブログ「愛と苦悩の日記」へ
デスクネッツに自動データ登録
Notesからdesknet's電子掲示板に自動ポスト
2006/03/08

唐突ではあるがNotes/DominoのLotusScriptなど、Visual Basic系の開発環境で任意のWebサイトにデータをGET/POSTする方法をまとめておく。利用するCOMオブジェクトは「MSXML2.XMLHTTP」「ADODB.Stream」の2つ。ADODB.Streamが必要な理由は、Webサーバから受け取ったコンテンツの文字コードを正しく識別するためだ。以下の例はExcelのVisual Basic for Applicationsで一応動作検証をしている。

まず単純にGETメソッドで特定のWebページのコンテンツを読込む処理から。一例としてYahoo!JAPANのトップページを読込む。やや面倒なのは文字コードを正しく処理するためにADODB.Streamオブジェクトをいったんバイナリモードにしてページ内容を読み込んでから、テキストモードに変換して読み出すという点だ。

テキストモードに変換した後に読み出すときは、Webページの文字コードに合わせてADODB.StreamオブジェクトのCharsetプロパティに「Shift_JIS」「UTF-8」「euc-jp」などの文字コードを現す文字列を設定してからReadTextメソッドで読み出すこと。

MSXML2.XMLHTTPオブジェクトのOpenメソッドで、3番目の引数は同期モードか非同期モードかの区別を表す。False(偽)にしておくと同期モードになるため、Webページが完全にダウンロードされるまで次の行には進まない。Visual Basicプログラムなら同期処理の方が便利だろう。

    Dim oHttp, oStream As Variant

    Set oHttp = CreateObject("MSXML2.XMLHTTP")
    Set oStream = CreateObject("ADODB.Stream")

    oHttp.Open "GET", "http://www.yahoo.co.jp/", False
    oHttp.Send
    
    oStream.Open
    oStream.Type = 1               ' いったんバイナリモードにする
    oStream.Write oHttp.responseBody
    
    oStream.Position = 0
    oStream.Type = 2               ' テキストモードに変換する
    oStream.Charset = "euc-jp"     ' Webサイトに合った文字コードを設定

    Open "temp.html" For Output As 1
    Print #1, oStream.ReadText
    Close #1

次にPOSTメソッドでWebサイト上の入力フォームにデータを送信する処理。Yahoo!やGoogleはプログラムなどによって自動で検索データを送信することを許可していないようなので、@niftyの検索機能でテストしてみた。それでも検索結果のテキストファイルは形が崩れているが、一応正しく検索されていることがわかる。

    Dim oHttp, oStream As Variant
    Dim vData As Variant

    Set oHttp = CreateObject("MSXML2.XMLHTTP")
    Set oStream = CreateObject("ADODB.Stream")

    oHttp.Open "POST", "http://search.nifty.com/cgi-bin/search.cgi", False
    oHttp.setRequestHeader "Content-type", "application/x-www-form-urlencoded"
    vData = "select=2&Text=AAA"
    
    oHttp.Send vData

送信するフォームデータはVariantとして宣言し、GETメソッドの場合と同じ形式でQuery String(問い合わせ文字列:パラメータと値を半角等号で連結、そのペアを半角&記号で連結するという形式)を代入する。それをMSXML2.XMLHTTPオブジェクトのSendメソッドの引数にすればいいだけだ。「oStream.Open」以下は上記のコードと全く同じなので省略する。

送信するデータの中に半角英数字以外の文字が含まれている場合は、当然URLエンコードする必要がある。たとえば「高橋源一郎」という文字列は「%E9%AB%98%E6%A9%8B%E6%BA%90%E4%B8%80%E9%83%8E」というふうにURLエンコードする必要がある。LotusScriptであれば@関数に@URLEncodeというそのものずばりの関数が用意されているので1行でURLエンコードが完了してしまう。非常に便利である。

    vData = Evaluate("@URLEncode(""Shift_JIS"";""" & vData & """)")
    oHttp.Send vData(0)

LotusScriptのEvaluate関数を使った結果は配列変数になることを忘れないようにしたい。@URLEncode関数は1個しか値を返さないので配列のインデックスが0の値が戻り値である。したがって「oHttp.Send vData(0)」となる。「oHttp.Send vData」では正しくWebページが返ってこない。

VBScriptやVisual Basic for Applicationsの場合には残念ながらURLエンコード用の関数が用意されていない(Active Server PagesならServerオブジェクトのメソッドとして用意されている)。ただベクターなどからフリーウェアで変換ルーチンをダウンロードできるようなので、そちらをお試し頂きたい。

さて、このようにVisual Basic系の言語で任意のWebページに自由にデータを登録することで何をやりたいのかと言えば、他システムとデータをやりとりするインターフェースをもたない社内のWebアプリケーションに、自動でデータを登録することである。

勘の良い方はもうお分かりのとおり、デスクネッツ・スタンダード版にバッチ処理でデータを登録できないかと考えていたのだ。Notes/DominoならLotusScriptを使えば自動で文書を登録するくらい簡単にできてしまうのだが、デスクネッツ・スタンダード版には(そしておそらくEnterprise版も)そのようなAPIが存在しないので、直接Webフォームにデータを送り込むしかない。

Visual Basic系の開発環境で任意のWebフォームにデータを送信・登録できるようになると、社内の任意のWebアプリケーションにデータを登録できる。ただし言うまでもないことだが、下手にPOSTでデータを送信するとそのWebアプリケーションに負荷をかけるばかりか、とんでもない不正データを誤って大量に登録する原因ともなる。飽くまで慎重に実施して頂きたい。

さて、標的となるWebアプリケーションはデスクネッツ・スタンダード版である。その中でも電子会議室を使って、自動的にスレッドを立てる処理を書いてみたい。デスクネッツは最初の画面でユーザ名とパスワードを入力してログインする処理が必要だが、MSXML2.XMLHTTPオブジェクトの同一のインスタンスを一貫して使えば、ログインも簡単に出来てしまう。

デスクネッツにはGETメソッドでログインできる。「uid」に自分のユーザID(注意:ログインIDではなく、デスクネッツ内部で付番されているユニークな番号のこと。デスクネッツの「ユーザ名簿」で自分の画面を開くと、URLの中に「id」というパラメータがあるはずだ。これが自分のユーザIDである)、「_word」にパスワード、「cmd」には固定で「certify」という文字列をわたす。つまり下記のようなURLでログインできてしまう。

http://サーバ名/dnet.exe?uid=14&_word=PASSWORD&cmd=certify

これはユーザIDが「14」でパスワードが「PASSWORD」の例である。したがってプログラムにすると下記の2行でデスクネッツへのログイン完了だ。

oHttp.Open "GET", "http://サーバ名/dnet.exe?" & _
uid=14&_word=PASSWORD&cmd=certify", False
oHttp.Send

続けて特定の電子会議室に新規投稿する画面に直接ジャンプする場合もGETメソッドで対応できる。今度は「dnet.exe」ではなく「xforum.exe」というCGIプログラムを呼び出す。「id」というパラメータに電子会議室のID(これは対象の電子会議室を開くとURLの中にやはり「id」というパラメータがあるはずだ。これがその電子会議室のIDである)、「page」というパラメータには固定の文字列「formartentry」(おそらくForm for article entryの略)をわたす。つまり下記のようなURLで新規投稿画面が開く。

http://サーバ名/xforum.exe?id=25&page=forumartentry

ログイン画面でログインした後、25番の電子会議室の新規投稿画面に飛ぶという一連の処理は下記のようなプログラムになる。

oHttp.Open "GET", "http://サーバ名/dnet.exe?" & _
"uid=14&_word=PASSWORD&cmd=certify", False
oHttp.Send
oHttp.Open "GET", "http://サーバ名/xforum.exe?" & _
"id=25&page=forumartentry", False
oHttp.Send

あまりに簡単すぎる。次にこの電子会議室にそのまま投稿データを送信して、スレッドを一つ立ててみよう。呼び出すCGIプログラムは同じく「xforum.exe」だが、今度はGETメソッドではなくPOSTメソッドで送信する。しかし送信データの組み立て方はGETメソッドとまったく同じである。

「cmd」パラメータには固定の文字列「forumcmdartentry」(おそらくForum command for article entryの略)、「id」には電子会議室のID、「title」には投稿の題名、「contr」には投稿者名、匿名投稿にしたい場合は「anonymouse」に「1」をセットする(デスクネッツの開発者たちはanonymousという正しいつづりを知らなかったようだ)。そして「memo」に投稿内容の本文をわたす。

「title」「contr」「memo」の部分には必ず漢字が入ってくるはずなのでURLエンコード処理が必須になる。また「memo」の部分には改行が必ず入ってくるはずなので、1行ずつURLエンコードした上で、各行の末尾に「%0D%0A」というURLエンコードされた改行コードを付加する処理が必要だ。

LotusScriptでVariant型のデータに、パラメータ名とURLエンコードされた値をつなげていく関数を作ってみると下記のようになる。

Function AppendToSendData(vData As Variant, sName As String, _
vValue As Variant)  As Variant
 
 Dim vTmp As Variant
 Dim I As Integer
 
 If Datatype(vValue) = 8 Then
  vTmp = Evaluate("@URLEncode(""Shift_JIS"";""" & vValue & """)")
  vData = vData & sName & "=" & vTmp(0)
 Else
  vData = vData & sName & "="
  For I = 0 To Ubound(vValue)
   vTmp = Evaluate("@URLEncode(""Shift_JIS"";""" & vValue(I) & """)")
   vData = vData & vTmp(0) & "%0D%0A"
  Next
 End If
 
 If Right(vData, 6) = "%0D%0A" Then vData = Left(vData, Len(vData) - 6)
 
 vData = vData & "&"
 
 AppendToSendData = vData
 
End Function

この関数を呼び出しながら、投稿データを組み立てていけばよい。いったんデータ送信用のVariant変数vDataをクリアしてから送信データを組み立てていく。「memo」というパラメータにわたす投稿の本文部分は文字列の配列変数にしてみた。本文の各行を配列の各インデックスに代入して上記に定義したAppendToSendData関数にわたすと、URLエンコードしつつ「%0D%0A」で改行コードも付加してくれるというわけだ。

    Dim sMemo() As String

 vData = ""
 
 Call AppendToSendData(vData, "cmd", "forumcmdartentry")
 Call AppendToSendData(vData, "id", "25")
 Call AppendToSendData(vData, "title", "テストの自動投稿です")
 Call AppendToSendData(vData, "contr", "自動投稿者")

 Redim sMemo(2) As String
 sMemo(0) = "ちゃんと改行されるか、"
 sMemo(1) = "検証中 http://www.yahoo.co.jp/"
 Call AppendToSendData(vData, "memo", sMemo)
 
 vData = vData & "s_ok"
 
 oHttp.Open "POST", "http://サーバ名/xforum.exe", False
 oHttp.Send vData

ここでなかなか気づかないのが最後の「s_ok」というパラメータである。このパラメータには値をわたさず、パラメータ名だけを送信する。このパラメータがないとデスクネッツのCGIプログラムは送信されたデータをPOSTデータと見なしてくれない。送信データの組み立てが終われば、今度は1番目の引数を「POST」にしてOpenメソッドを呼び出せばいい。POSTメソッドなので2番目の引数のURLに余計なパラメータはいらない。シンプルに「http://サーバ名/xforum.exe」だけで問題ない。最後のFalseは例によって同期処理という意味で、Webページの処理が終わるまでプログラムは待ち状態になる。

CGIプログラム側で厳しいセキュリティチェックをしていない限り、Webアプリケーションに対しては同じような方法でログインし、データを送信・登録することができる。どんなパラメータにどのような値をわたせばよいのかは、WebアプリケーションのHTMLのソースを注意深く分析すれば大抵のことは分かると考えていい。