作成 2013年4月8日
クロスドメインへのアクセス手法
1.セキュリティ制約と不思議
AjaxのXMLHttpRequestを利用してクロスドメインへアクセスすると、ブラウザーのセキュリティ制約によって、アクセスそのものがエラーとなり、異なるドメインから取得したデータにアクセスすると、セキュリティー・エラーが発生する。1つの例で見ると、異なるドメインのコンテンツ(子コンテンツ)をiframeに読み込んだ場合、読み出し側のコンテンツ(親コンテンツ)のjavascriptは自身内のDOMにアクセスでき、iframe要素のsrc属性を取得できる。しかし、親コンテンツのjavascriptはiframe内に展開されている子コンテンツにアクセスすることはできない、また子コンテンツのjavascriptは同様に親コンテンツにアクセスできない。これがブラウザーのセキュリティ制約である。
その一方で不思議なことがある。script要素、img要素、iframe要素のsrc属性は、当初から、自己のサイトだけでなく、異なるドメインからその要素の実体をページに取り込むことができる仕様となっている。この機能を利用し、バナーを貼ってサイト誘導などに利用してきている。画像はブラウザーに表示するだけであるが、取り込んだスクリプトは、表示しているコンテンツのHTML-DOMにアクセスし、DOMの読み、書き、削除、追加が自由にできる。
この不思議に気付いた一部の人達は、クロスサイトスクリプティング手法をあみだした。また別の人達はクロスドメインへのアクセス手法をあみだした。
2.クロスドメインへのアクセス
マッシュアップと言う言葉が流行って以来、ブラウザー上に、GoogleMapを表示したい、他のWebサービスと連携しているサイトが増えている。
ここで取り上げるクロスドメイン・アクセスとは、サービス提供しているサーバーの情報を表示しているブラウザーから、異なるドメインのWebサービスにアクセスしてデータの送受信を行い、ブラウザーの表示内容を遷移させる一連の操作を示す。
これに対してGoogleMapのサービスで、与えたデータに基づき地図を表示する、このようにWebサービスに対して要求を出し表示のみで、データ受信を伴わないアクセスは”リンクを貼った”に近い振る舞いであるため、クロスドメイン・アクセスとは呼ばない。
ここで言いたいクロスドメイン・アクセスとは、ブラウザーが別ドメインにデータを要求し、受け取ったデータをブラウザー内に取り込み、能動的な表示、計算や登録などの処理を伴う、データ受信形態を示す。
3.クロスドメイン・アクセスの実装
クロスドメイン・アクセスでは、まずクロスドメインとなるWebサービスの存在が必須である。まず、希望するWebサービスはインターネットで検索し、接続方法はそのサイトに従う。公開されていないWebサービスは利用することができない。自社が管理しているサイト/契約関係があるサイトやイントラネットでは、その中で利用するためのWebインターフェイスに従うこと/なければWebインターフェイスを構築することで、クロスドメイン・アクセスによるシステム連携が実現できる。
3.1 テスト環境
80番ポートのHTTPサーバーと、コード1に示すnode.jsで作ったHTTPサーバー(testapp.js、8002番ポート)の2台サーバーを用意した。80番ポートのサーバーを元サーバーとし、もう一つのサーバーをクロスドメイン先とした。node.js(http://nodejs.jp/)のHTTPサーバーは大変シンプルで、GETメソッドによるリクエストを受信に対応するcgi処理を行う。mode="testJson1"のとき、変数var data={"name":"山田太郎","sex":"男","age":"20"};をscript要素の中身として埋め込み、mode="testJson2"のとき、コールバック関数xxx({"name":"山田太郎","sex":"男","age":"20"})で、JSONデータを埋め込む。
本編では、説明を補足するため、デモ並びに長いコードを掲載する。
コード1(testapp.js) コードのところをのダブルクリックし、コピーするとクリックボードに入ります。
var http = require('http');
var path = require('path');
var url = require('url');
http.createServer(function (req, res) {
var params = url.parse(req.url, true);
if(params.pathname=="/httpserv"){
if(params.query.mode=="testJson1"){ // /httpserv?mode=testJson1
console.log("testJson1");
res.setHeader("Pragma","no-cache");
res.setHeader("Expires","-1");
res.writeHead(200, {'Content-Type': 'text/javascript; charset="UTF-8"'});
var jdata='{"name":"山田太郎","sex":"男","age":"20"}';
var sdata="var data="+jdata;
res.end(sdata);
}
else if(params.query.mode=="testJson2"){ // /httpserv?mode=testJson2
console.log("testJson2");
res.setHeader("Pragma","no-cache");
res.setHeader("Expires","-1");
res.writeHead(200, {'Content-Type': 'text/javascript; charset="UTF-8"'});
var jdata='{"name":"山田太郎","sex":"男","age":"20"}';
var sdata=params.query.callback+"("+jdata+")";
res.end(sdata);
}
else if(params.query.mode=="testJson0"){ // /httpserv?mode=testJson0
console.log("testJson0");
res.setHeader("Pragma","no-cache");
res.setHeader("Expires","-1");
res.writeHead(200, {'Content-Type': 'text/javascript; charset="UTF-8"'});
var jdata='{"name":"山田太郎","sex":"男","age":"20"}';
res.end(jdata);
}
else if(params.query.mode=="getJson"){ // /httpserv?mode=getJson・・
console.log("getJson ");
var ps=params.query.url;
var options = url.parse(ps);
options.headers={'user-agent':'Mozilla/5.0(Windows NT 6.1) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17'};
//プロキシ経由にも対応
var proxy="yes";
if(proxy=="yes"){
options.hostname="proxy2.is.fujimic.com";
options.port="8080";
options.path=ps;
}
//HTTPクライアントによるデータ収集
var request = http.request(options, function(res) {});
var body="";
request.end();
request.on('response', function (response) {
response.setEncoding('utf8');
response.on('data', function (chunk) {
body += chunk;
});
response.on('end', function () {
body=params.query.callback+"("+body+")";
console.log("body="+body);
res.setHeader("Pragma","no-cache");
res.setHeader("Expires","-1");
res.writeHead(200, {'Content-Type': 'text/javascript; charset="UTF-8"'});
res.end(body);
});
});
}
}
}).listen("8002", "127.0.0.1");
3.2 最も簡単なscript要素を利用したクロスドメイン・アクセス
先述の通り、script要素のsrc属性は異なるドメインの設定ができる。script要素を動的に生成し、src属性にクロスドメインへのアクセス情報を記述する。
下記のコードは、script要素を動的に生成し、uriで示す要求をサーバーに行い、スクリプトのロードによって、<script type="text/javascript">var data={"name":"山田太郎","sex":"男","age":"20"};</script>が展開され、loadイベント後、変数dataを表示する。処理通り、{"name":"山田太郎","sex":"男","age":"20"}と言うシリアライズされたJSONデータが表示された。IE9、Chromeは動作したが、IE8では何の返答もなかった。IE8(Unicodeエスケープシーケンス変換が必要)、IE9、Chromeで動作を確認した。
コード2(test01.html)、デモ(test01.html)
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<script type="text/javascript">
uri="http://127.0.0.1:8002/httpserv?mode=testJson1";
var script= document.createElement('script');
script.charset = 'utf-8';
script.type="text/javascript";
script.src = uri;
document.getElementsByTagName('HEAD')[0].appendChild(script);
if (window.addEventListener){//IE以外
script.addEventListener("load", function(){ scriptLoaded(); }, false);
} else if (script.attachEvent){//IEの場合
script.attachEvent("onload", function(){ scriptLoaded(); }, false);
}
function scriptLoaded(){
DATA=JSON.stringify(data);
alert(DATA);
}
</script>
</head>
<body>
</body>
</html>
3.3 最も簡単なscript要素を利用したクロスドメイン・アクセス その2
前述では、JSON変数であったが、下記のコードは、コールバック関数で、JSONデータがscript要素の中に埋め込まれ、処理通り実行できた。この手法はクロスドメイン・アクセスの一般的方法で、JSONP(Json with Padding)と呼ばれている。
コード3(test02.html)、デモ(test02.html)
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<script type="text/javascript">
uri="http://127.0.0.1:8002/httpserv?mode=testJson2";
var script= document.createElement('script');
script.charset = 'utf-8';
script.type="text/javascript";
script.src = uri+"&callback=getJSON";
document.getElementsByTagName('HEAD')[0].appendChild(script);
function getJSON(json) {
DATA=JSON.stringify(json);
alert(DATA);
}
</script>
</head>
<body>
</body>
</html>
3.4 公開Webサービスを利用し実装を試みる
クロスドメイン・アクセス方式として、script要素を用いた手法を試みる。また、実装テストとして、無料公開されている占いAPI(http://jugemkey.jp/api/waf/)を利用する。占いAPIは、次のように年月日(YYYY/MM/DD)を入れたURLでアクセスすると、指定した日付の占いデータがJSON形式のデータで提供される。
http://api.jugemkey.jp/api/horoscope/free/YYYY/MM/DD
占いデータ
{"horoscope":{"2013/04/02":[
{"content":"イメージチェンジをしてみると良さそうです。ヘアスタイルを少しアレンジするだけで、華やかさが一気にアップして大好評かも。","item":"靴下","money":3,"total":3,"job":3,"color":"ブラウン","day":2,"love":2,"rank":9,"sign":"牡羊座"},
{"content":"人間関係がイマイチで、トラブルの多い一日です。どんなに褒められても、謙虚な姿勢でいることが開運のポイントに。","item":"ウーロン茶","money":3,"total":2,"job":2,"color":"レッド","day":"","love":2,"rank":10,"sign":"牡牛座"},
・
・
・
{"content":"いつもお世話になっている人に、感謝の気持ちを表すと吉。簡単なプレゼントを用意すると、さらに絆が深まりそうです☆","item":"ハンドバッグ","money":5,"total":4,"job":4,"color":"アイボリー","day":"","love":4,"rank":4,"sign":"水瓶座"},
{"content":"部屋や仕事場など、身の回りをキレイにすると吉♪整理整頓をすると、仕事の効率が上がります。フリータイムも快適に。","item":"乾電池","money":5,"total":4,"job":5,"color":"グレー","day":"","love":4,"rank":3,"sign":"魚座"}
]}}
この占いAPIにコード4で直接アクセスすると、ブラウザーでエラーが発生し、何も表示されない。
以上から、クロスドメイン・アクセスを成功させるためには、javascriptの埋め込み(<script>var data={“name”:” 山田太郎”, “sex”:” 男”, “age”:”20”}</script>)で対応するか、コールバック関数callback({“name”:” 山田太郎”, “sex”:” 男”, “age”:”20”})を用いるかのいずれかが適切である。
コード4(test03.html)、デモ(test03.html)
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<script type="text/javascript">
var url="http://api.jugemkey.jp/api/horoscope/free/";
var d=new Date();
YYYY=d.getFullYear();
MM=d.getMonth()+1;if(MM<10)MM="0"+MM;
DD=d.getDate();if(DD<10)DD="0"+DD;
url=url+YYYY+"/"+MM+"/"+DD;
var script= document.createElement('script');
script.charset = 'utf-8';
script.type="text/javascript";
script.src = url+"&callback=getJSON";
document.getElementsByTagName('HEAD')[0].appendChild(script);
function getJSON(json) {
DATA=JSON.stringify(json);
alert(DATA);
}
</script>
</head>
<body>
</body>
</html>
そこで、コード1のmode=="getJson"に示すように、HTTPサーバーにプロキシ機能を設け(HTTPサーバーにHTTPクライアントを組み込みプロキシとした)、占いAPIから取得したJSONデータをcallback関数に包み、コード5によって占いAPIのJSONデータを受け取ることができた。
コード5(test04.html)、デモ(test04.html)
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<script type="text/javascript">
var url="http://127.0.0.1:8002/httpserv?mode=getJson&url=http://api.jugemkey.jp/api/horoscope/free/";
var d=new Date();
YYYY=d.getFullYear();
MM=d.getMonth()+1;if(MM<10)MM="0"+MM;
DD=d.getDate();if(DD<10)DD="0"+DD;
url=url+YYYY+"/"+MM+"/"+DD;
var script= document.createElement('script');
script.charset = 'utf-8';
script.type="text/javascript";
script.src = url+"&callback=getJSON";
document.getElementsByTagName('HEAD')[0].appendChild(script);
function getJSON(json) {
DATA=JSON.stringify(json);
alert(DATA);
}
</script>
</head>
<body>
</body>
</html>
3.5 クロスドメイン・アクセスの結論
クロスドメイン・アクセスの結論は次の通りである。
・自社が管理している/契約で提携しているサイトでは、インターフェイスを調整し、JSONPによりアクセスする。
・ページから必要な情報を抽出・加工するプロキシを設けて、アクセスする。
HTML5ではiframe要素を用い、postMessageによるデータ通信で簡単で安全なクロスドメイン・アクセス方式を提供されている。クロスドメイン・アクセスは難しい技術ではなく、利用可能は技術となっている。特に、イントラのWebアプリケーションは、クロスドメイン・アクセスを利用することで、システム連携が容易になる。是非とも、お試しを!!!
3.6 参考サイト
クロスドメインで使う XMLHttpRequest と JSONP のお話
http://tadtak.jugem.jp/?eid=58
サンプルから5分で分かるJSONPの仕組み
http://www.syboos.jp/oss/doc/know-jsonp-by-sample.html
JSONP WebAPIを爆速で使いこなせるフレームワーク
http://techblog.yahoo.co.jp/programming/bakusoku-jsonp/
avaScriptで外部サイトをスクレイピング【Cross-Domain-Ajax】
http://logic.moo.jp/data/archives/759.html
postMessageとiframeでクロスドメインメッセージを送受信してみる
http://zafiel.wingall.com/archives/6631
4.Webサービスをサポートしていないサイト(Basic認証含む)へのアクセス
Webサービスをサポートしているサイトへのアクセスは、サービスを受けるために必要な複数の情報(アクセスキーなどを含む)をGETメソッド若しくはPOSTメソッドで送信する、その結果、対応するサービス情報が返信されてくる。
一方、Webサービスをサポートしていないサイト(通常のサイト)は、Basic認証、ログイン、メニュー選択などの操作を経て、必要な情報にアクセスする。Webサービスをサポートしていないサイトへは、上記で述べたクロスドメイン・アクセスは有効ではなく、別の手段でアクセスしなければならない。
今回利用するツールがPhantomJS (http://phantomjs.org/)で ある。PhantomJSはheadless Webkitと言われ、Webブラウザーなし(スクリーンなし)でJavaScriptの実行が可能なツールである。内部ではQtWebKitを使用しているため、単にJavaScriptを実行するだけではなく、DOMやCSSの取り扱いや、CanvasやSVGによる描画などにも対応している(無論HTML5にも対応している)。
PhantomJSなどのサイトにアクセスするツールによる手法はWebスクレイピングと呼ばれている。Webスクレイピング(scraping)とは、Webページから利用者が必要としている情報を選択的に収集する技術である。スクレイピング(scraping)とは削ることの意味で、Webページからレイアウトや色と言った付随的な情報を削り取り、必要な文字(数値)情報だけを取り出すことを示す。検索サイトはスクレイピングを行っている典型的な例である。
スクレイピング手法はいろいろな意味で有効であるが、著作権への配慮を含め、良識ある利用が肝要である。
4.1 PhantomJSの使い方
PhantomJSの使い方は簡単で、PhantomJSをインストールし、phantomjs.exeのパスの通るところで、スクリプトを実行するだけ。
スクリプトも簡単で、コード6に示すように、ページをオープンし、そのコールバック関数の中で、開いたページの画面キャプチャーを行い、ページ内のDOMを走査し、id、pwの入力、その画面キャプチャーを取り、phantomjsを終了する。簡単なコードで各ステップのスクレイピングができる。
コード6(PhantomJS版)(loginP.js)
var page = require("webpage").create();
//ログインページのオープン
page.open("http://127.0.0.1:8002/cxpw.html",function(status){
page.render("login1.jpg");
//id、pwのセットとログインボタンの押下
page.evaluate(function() {
document.getElementById('id').value = 'osaka';
document.getElementById('pw').value = 'osaka';
document.getElementById('button').click();
});
page.render("login2.jpg");
phantom.exit();
});
4.2 node.jsからPhantomJSをアクセス
PhantomJSがサイトにアクセスし、headless Webkit内にそのサイトのレンダリングを行い、画面キャプチャー、HTML-DOMへのアクセスを上記の通り示した。node.jsにはPhantomJSをコントロールするライブラリがあり、これを利用するといろいろな連携が可能となる。ここでは、コード6をnode.jsのphantom.jsライブラリで書き直し、動作を確認したところ、コード6と同様の結果が得られた。
コード7(node.js+phantomJS版)(loginN.js)
var phantom = require("phantom");
phantom.create(function(ph) {
ph.createPage(function(page) {
//ログインページのオープン
page.open("http://127.0.0.1:8002/cxpw.html", function(status) {
page.render("login1.jpg");
//id、pwのセットとログインボタンの押下
page.evaluate(function() {
document.getElementById('id').value = 'osaka';
document.getElementById('pw').value = 'osaka';
document.getElementById('button').click();
});
page.render("login2.jpg");
ph.exit();
});
});
});
4.3 PhantomJSによるステップ実行
PhantomJSも非同期で作動するため、コード6で示したように、処理の進行はコールバックの連鎖によって行われ、どんどんコールバックのネストが深くなる(プログラムの可読性が悪くなる)。そこでonLoadイベントを用い、順次処理を実現し、可読性の高いコードが得られた。
コード8(PhantomJSによるログイン)(loginP2.js)
var page = require("webpage").create();
var testindex = 0, loadInProgress = false;
page.settings.userAgent = 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:13.0) Gecko/20100101 Firefox/13.0';
page.onConsoleMessage = function(msg) {
console.log(msg);
};
page.onLoadStarted = function() {
loadInProgress = true;
console.log("load started");
};
page.onLoadFinished = function() {
loadInProgress = false;
console.log("load finished");
};
var steps = [
//ログインページのロード(ステップ1)
function() {
page.open("http://127.0.0.1:8002/cxpw.html");
},
//ログイン情報のセット、ログインボタンのクリック(ステップ2)
function() {
page.evaluate(function() {
document.getElementById('id').value = 'osaka';
document.getElementById('pw').value = 'osaka';
document.getElementById('button').click();
});
},
//ログイン後のページの内容表示(ステップ3)
function() {
page.evaluate(function() {
console.log(document.querySelectorAll('body')[0].innerHTML);
});
}
];
interval = setInterval(function() {
if (!loadInProgress && typeof steps[testindex] == "function") {
console.log("step " + (testindex + 1));
steps[testindex]();
testindex++;
}
if (typeof steps[testindex] != "function") {
console.log("test complete!");
phantom.exit();
}
}, 500);
4.4 node.js+phantomJSによるActiveMailアクセス(その1)
コード8を応用しActiveMailにアクセスするphantomjsモジュールを作成、これをnode.js版に手直しして、ActiveMailの受信箱にアクセスできた。
処理の流れは、"https://gm.fujimic.com/"にアクセスしBasic認証を経て、ActiveMailにログインする画面を表示、ユーザーID、パスワードの入力、ログインボタンの押下を経て、ActiveMailにアクセスする。その後、受信箱をクリックし、受信箱を表示し、その中のメール情報を取得し、ログアウトする。受信箱のクリックをする動作はしていないが、受信箱をクリックしたとき作動する関数"amtop.changeContents('rmail')"をキックしている。"page.evaluate"内は、WebKitに展開しているページのDOMが操作できるため、このような手段が可能となる。
認証後のログイン画面
ユーザーID、パスワードの流し込み
ActiveMailメイン画面
受信箱の表示
ログアウト画面
“http://127.0.0.1/crossdomain/testMail.html”の画面
コード9(node.js版)(ActiveMail.js)
/*
Node.js × PhantomJS で何でもサクサクスクレイピングするよ!
http://d.hatena.ne.jp/hecomi/20130108/1357653054
ActiveMailへの接続
basic認証、ログイン、受信箱を開き、タイトルを表示する
PhantomJSはWebブラウザなしでJavaScriptの実行が可能なツールです。内部ではQtWebKitを使用しているため、単にJavaScriptを実行するだけではなく、DOMやCSSの取り扱いや、CanvasやSVGによる描画などにも対応しています。
*/
var phantom = require("phantom");
// -----------------------------------------------------------
getMailData("xxxxxxxx","xxxxxxxx","xxxxxxxx","xxxxxxxx",null);
// -----------------------------------------------------------
//exports.getMailData = function(bid,bpw,userid,pw,func){//getMailDataのモジュール化
function getMailData(bid,bpw,userid,pw,func){//bid、bpwはBasic認証情報
var testindex=0, loadInProgress = false;
var title_list="";
//phantom.create("--proxy=http://proxy2.is.fujimic.com:8080", function(ph){
phantom.create(function(ph) {
ph.createPage(function(page) {
page.set("onLoadStarted", function() {
console.log("load Started");
loadInProgress = true;
});
page.set("onLoadFinished", function() {
console.log("load Finished");
loadInProgress = false;
});
//PhantomJSで順次実行する関数
var funcs=[
//ログイン画面の表示とBasic認証
function() {
console.log("ActiveMailサイトへのアクセス(Basic認証)");
//Basic認証情報のセット
page.set("settings.userName", bid);
page.set("settings.password", bpw);
//ユーザーエージェントのセット
page.set("settings.userAgent", "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:13.0) Gecko/20100101 Firefox/13.0");
//画面サイズのセット
page.set("viewportSize", {width: 1024, height: 768});
//サイトのオープン
page.open("https://gm.fujimic.com/", function(status) {
console.log("status="+status);
if (status !== "success") {
testindex=funcs.length;
console.log("opened Page? ", status+" "+testindex);
ph.exit();
return;
}
});
},
//ログイン情報のセット
function() {
console.log("ログイン画面で、ユーザーID、PWの入力");
page.render("site1.jpg");
//console.log(userid+":"+pw);
page.evaluate(function(Userid,Pw) {
var arr=document.getElementsByTagName("input");
for (var i=0;i<arr.length;i++){
if(arr.item(i).getAttribute("type")=="text")arr.item(i).value=Userid;
else if(arr.item(i).getAttribute("type")=="password")arr.item(i).value=Pw;
}
},null,userid, pw);
},
//ログインボタンの押下
function() {
page.render("site3.jpg");
console.log("ログイン画面でのサブミット");
page.evaluate(function() {
var arr=document.getElementsByTagName("form");
for (var i=0;i<arr.length;i++){
if(arr.item(i).getAttribute("name")=="login"){
arr.item(i).submit();
break;
}
}
});
},
//ログイン後の画面表示と受信箱の押下
function() {
page.render("site4.jpg");
console.log("メール画面の表示と受信箱へのアクセス");
//メール受信
page.evaluate(function() {
amtop.changeContents('rmail');//受信箱のクリック
});
},
//受信箱の表示と受信データの取得
function() {
page.render("site5.jpg");
console.log("受信箱表示とメールタイトルの取得");
// iframe 内の HTML を取得
page.evaluate(function() {
var st='{"maildata":[';
var c=document.getElementById("contentIframe").contentWindow;
var arr=c.document.getElementsByTagName("tr");
for (var i=0;i<arr.length;i++){
var nd=arr.item(i);
if(nd.getAttribute("class")){
if(nd.getAttribute("class").indexOf("row")>0){
c=nd.childNodes;
if(c&&c.length==9){
st+='{"title":"'+c.item(5).innerHTML+'","from":"'+c.item(6).innerHTML+'","date":"'+c.item(7).innerHTML+'"},';
}
}
}
}
st=st.substring(0,st.length-1)+"]}";
return st;
}, function(html) {
title_list = html;
});
},
//ログアウトボタンの押下
function() {
console.log("ログアウトボタン押下");
// page.render("site6.jpg");
page.evaluate(function() {
var nd=document.getElementById("header_button_logout");
nd.click();
});
},
//ログアウト画面の表示
function() {
console.log("ログアウト後の画面");
page.render("site7.jpg");
page.evaluate(function() {
return document.getElementsByTagName("html")[0].outerHTML;
}, function(html) {
//console.log(html);
ph.exit();
if(func)func();
else dispList(title_list);
});
}
];
interval = setInterval(function() {
if (!loadInProgress && typeof funcs[testindex] == "function") {
console.log("step " + (testindex + 1));
funcs[testindex]();
testindex++;
}
if (typeof funcs[testindex] != "function") {
console.log("test complete! "+testindex);
clearInterval(interval);
return;
}
}, 2500);
function dispList(data){
console.log("titles=\n"+data);
}
});
});
}
4.5 node.js+phantomJSによるActiveMailアクセス(その2)
コード1にActiveMail用のアクセスモジュールを組込み、"http://127.0.0.1/testMail.html"からBasic認証情報、ログイン情報を入力し、"http://127.0.0.1:8002/httpser?mail&・・・"を経てActiveMailにアクセスし、受信箱のメール情報(JSON形式)の取得、ログアウト処理を行い、4.4と同様の結果を得た。
このデモにはエラー処理が実装されていないため、正しい入力を行わないと、結果が得られないことに注意を。
コード(サーバー(testapp.js))、HTML(testMail.html))
4.6 インストールソフト
(1) node.js(http://nodejs.jp/)
node-v0.8.23-x86.msiをインストール(この時点で、最新版v0.10.3でphantom.jsが作動しなかったため)。
http、url、fs、opts、phantomなどのnode-moduleを組み込む。
(2) PhantomJS( http://phantomjs.org/)
phantomjs-1.9.0-windows.zipを解凍、解凍フォルダにpath設定。
4.7 参考サイト
Screen Scraping with Node.js
http://net.tutsplus.com/tutorials/javascript-ajax/web-scraping-with-node-js/
Node.js × PhantomJS で何でもサクサクスクレイピングするよ!
http://d.hatena.ne.jp/hecomi/20130108/1357653054
PhantomJS でログインが必要なページでも自由自在にスクレイピング
http://d.hatena.ne.jp/hecomi/20121229/1356785834
How to submit a form using PhantomJS
http://stackoverflow.com/questions/9246438/how-to-submit-a-form-using-phantomjs
5.結論
・サービスするサーバー側との調整が容易にできるイントラ・アプリでは、クロスドメインによるシステム連携が打って付けで、データ形式は断然JSONが便利である。
・GoogleMapなどのWebサービスは、データの提供だけでなく、データを操作するメソッド並びにUIを提供している。これを実現している技術がscript要素へのデータ並びに操作関数の埋め込みによるものである。提供されるJavascriptの関数は難読化処理がなされているため、簡単に解析できず、信頼サイトでない限り、気持ち悪いものである。無料のWebサービスを利用する場合、信頼できるサイトであるか、サービスがどのくらい継続されるなど検討が必要である。
・データを提供するだけのWebサービスを利用する場合、最終的にはプロキシサーバーをかませないと、データが得られない若しくは処理し易いデータが得られないことがある。データ形式の確認と、容易にデータが取り込めるよう、プロキシサーバー上での対応が必要となる。
・HTML5のpostMessageは安全にクロスドメインと連携できる手段である。
・クロスドメイン・アクセス法として、iframe要素若しくはscript要素を利用して実装されている。iframe要素は、データの授受を行うことができるが、script要素では、値の授受に留まらず、スクリプトの受け渡しもできる。GoogleMapのように、データとそのデータを操作、処理するスクリプトをクロスドメイン環境で受け渡すなど、script要素によるクロスドメイン・アクセスは、汎用化でき、多彩なWebアプリをもたらす。
・Webサービスのインターフェイスを持たない通常のWebサイトでは、PhantomJSなどのスクレイピング手法は有用である。
・node.jsは大変面白い仕掛けである。プログラム作成では、非同期であることに要注意。
作成(大坂哲司)