node.jsを使用した非同期Webマイニング

特定のリソースリストから情報を収集するという、Webマイニングの問題を解決した経験を共有したいと思います。 これは、独自の「検索エンジン」を作成しようとするものではないことに注意してください。これにはまったく異なるアプローチが使用されます。 Webマイニングの目標は、情報の一部を引き出すことです。 たとえば、リソースが「名刺」などの形式のマイクロフォーマットをサポートしている場合





実装について:正確にnode.jsなのはなぜですか? 確かに、特定のテクノロジーに制限はありませんでした。Java/ .NETを使用したC ++からPerl / Pythonまで、あらゆるものを使用できます。 node.jsを選んだ理由を説明します。



node.jsをインストールする



サーバー(FreeBSD、amd64)へのインストールはスムーズに進みました-「cd / usr / ports / www / node; make install」とnode.jsを使用する準備ができました。



Windowsプラットフォームの場合、最もアクセスしやすいインストールオプションはcygwinです。 純粋な.NETによるnode.jsの実装に遭遇しましたが、良い指示は見つかりませんでした。



Ubuntuの場合も、問題なく実行されます(たとえば、 適切な指示)



さらに素敵なマニュアルを読んでください。 マニュアルは本当に素晴らしく見えますが、基本的な要素のみをカバーしており、Webマイナーを他のほとんどのクラスと同様にイベントをトリガーできるようにしたい場合、このマニュアルはまったく説明されていませんでした。 しかし、それについては後で。



ページアンローダー



http.Clientの例を使用して、ドキュメント全体がロードされるまで待機し、URLを解析して目的のリクエストをコンパイルすると、次の「クラス」が見つかりました。

var webDownloader = function (sourceUrl) {<br>

events.EventEmitter.call( this );<br>

this .load = function (sourceUrl) {<br>

var src = url.parse(sourceUrl);<br>

var webClient = http.createClient(src.port==undefined?80:src.port,src.hostname);<br>

var get = src.pathname+(src.search==undefined? '' :src.search);<br>

sys.log( 'loading ' +src.href);<br>

var request = webClient.request( 'GET' , get ,<br>

{ 'host' : src.hostname});<br>

request.end();<br>

var miner = this ;<br>

request.on( 'response' , function (response) {<br>

// console.log('STATUS: ' + response.statusCode); <br>

// console.log('HEADERS: ' + JSON.stringify(response.headers)); <br>

response.setEncoding( 'utf8' );<br>

var body = '' ;<br>

response.on( 'data' , function (chunk) {<br>

body += chunk;<br>

});<br>

response.on( 'end' , function () {<br>

miner.emit( 'page' ,body, src);<br>

});<br>

});<br>

};<br>

}<br>

sys.inherits(webDownloader, events.EventEmitter);
<br>

<br>

* This source code was highlighted with Source Code Highlighter .









var webDownloader = function (sourceUrl) {<br>

events.EventEmitter.call( this );<br>

this .load = function (sourceUrl) {<br>

var src = url.parse(sourceUrl);<br>

var webClient = http.createClient(src.port==undefined?80:src.port,src.hostname);<br>

var get = src.pathname+(src.search==undefined? '' :src.search);<br>

sys.log( 'loading ' +src.href);<br>

var request = webClient.request( 'GET' , get ,<br>

{ 'host' : src.hostname});<br>

request.end();<br>

var miner = this ;<br>

request.on( 'response' , function (response) {<br>

// console.log('STATUS: ' + response.statusCode); <br>

// console.log('HEADERS: ' + JSON.stringify(response.headers)); <br>

response.setEncoding( 'utf8' );<br>

var body = '' ;<br>

response.on( 'data' , function (chunk) {<br>

body += chunk;<br>

});<br>

response.on( 'end' , function () {<br>

miner.emit( 'page' ,body, src);<br>

});<br>

});<br>

};<br>

}<br>

sys.inherits(webDownloader, events.EventEmitter);
<br>

<br>

* This source code was highlighted with Source Code Highlighter .









var webDownloader = function (sourceUrl) {<br>

events.EventEmitter.call( this );<br>

this .load = function (sourceUrl) {<br>

var src = url.parse(sourceUrl);<br>

var webClient = http.createClient(src.port==undefined?80:src.port,src.hostname);<br>

var get = src.pathname+(src.search==undefined? '' :src.search);<br>

sys.log( 'loading ' +src.href);<br>

var request = webClient.request( 'GET' , get ,<br>

{ 'host' : src.hostname});<br>

request.end();<br>

var miner = this ;<br>

request.on( 'response' , function (response) {<br>

// console.log('STATUS: ' + response.statusCode); <br>

// console.log('HEADERS: ' + JSON.stringify(response.headers)); <br>

response.setEncoding( 'utf8' );<br>

var body = '' ;<br>

response.on( 'data' , function (chunk) {<br>

body += chunk;<br>

});<br>

response.on( 'end' , function () {<br>

miner.emit( 'page' ,body, src);<br>

});<br>

});<br>

};<br>

}<br>

sys.inherits(webDownloader, events.EventEmitter);
<br>

<br>

* This source code was highlighted with Source Code Highlighter .












ここで興味深いのは、クラスがイベントのソースとして登録される方法です。

  1. まず、コンストラクターでEventEmitterに登録します。events.EventEmitter.call(this);
  2. EventEmitterからクラスを「継承」する
  3. emitメソッドを使用してイベントを「発行」する




EventEmitterでの作業はまだ文書化されていないので、少しグーグルする必要がありました。



これで、ページ読み込みイベント全体をサブスクライブできます。

var loader = new webDownloader();<br>

loader.on('page',vcardSearch);








vCardデータを検索する



現在、ページからvCardデータを取得するあまり面白くない関数です。 正しい実装に多くの時間を費やしたくなかったので、「額」でした-適切なクラスを持つ要素を検索しました。



ここでは、ページの解析にApricotモジュールを使用することを除いて、特に興味深いものはありません(ただし、htmlparserを使用すれば十分ですが、Apricotの方がはるかに高速になります)。 最初に、必要な要素を検索するためのCSSセレクターを構築し、Apricotの検索機能を使用しようとしました(これは検索にSizzleを使用します)が、判明したように、すべての要素の再帰的な走査が高速になりました。



その結果、次のような機能が得られました。

var vcardSearch = function (body,src) {<br>

sys.log( 'scaning ' +src.href);;<br>

Apricot.parse(body, function (doc) {<br>

var vcardClasses = [<br>

// required <br>

'fn' ,<br>

'family-name' , 'given-name' , 'additional-name' , 'honorific-prefix' , 'honorific-suffix' ,<br>

'nickname' ,<br>

// optional <br>

'adr' , 'contact' ,<br>

'email' ,<br>

'post-office-box' , 'extended-address' , 'street-address' , 'locality' , 'region' , 'postal-code' , 'country-name' ,<br>

'bday' , 'email' , 'logo' , 'org' , 'photo' , 'tel' <br>

];<br>

var vcard = new vCard();<br>

var scanElement = function (el) {<br>

if (el==undefined) return ;<br>

<br>

if (el.className != undefined && el.className!= '' ) {<br>

var classes = el.className.split( ' ' );<br>

for ( var n in classes) {<br>

if (vcardClasses.indexOf(classes[n])>=0) {<br>

var value = el.text.trim().replace(/<\/?[^>]+(>|$)/g, '' );<br>

if (value != '' ) vcard.Values[classes[n]] = value;<br>

}<br>

}<br>

}<br>

for ( var i in el.childNodes) scanElement(el.childNodes[i]);<br>

}<br>

scanElement(doc. document .body);<br>

if (!vcard.isEmpty())<br>

sys.log( 'vCard = ' +vcard.toString());<br>

else <br>

sys.log( 'no vCard found on ' +src.href);<br>

});<br>

}
<br>

<br>

* This source code was highlighted with Source Code Highlighter .








var vcardSearch = function (body,src) {<br>

sys.log( 'scaning ' +src.href);;<br>

Apricot.parse(body, function (doc) {<br>

var vcardClasses = [<br>

// required <br>

'fn' ,<br>

'family-name' , 'given-name' , 'additional-name' , 'honorific-prefix' , 'honorific-suffix' ,<br>

'nickname' ,<br>

// optional <br>

'adr' , 'contact' ,<br>

'email' ,<br>

'post-office-box' , 'extended-address' , 'street-address' , 'locality' , 'region' , 'postal-code' , 'country-name' ,<br>

'bday' , 'email' , 'logo' , 'org' , 'photo' , 'tel' <br>

];<br>

var vcard = new vCard();<br>

var scanElement = function (el) {<br>

if (el==undefined) return ;<br>

<br>

if (el.className != undefined && el.className!= '' ) {<br>

var classes = el.className.split( ' ' );<br>

for ( var n in classes) {<br>

if (vcardClasses.indexOf(classes[n])>=0) {<br>

var value = el.text.trim().replace(/<\/?[^>]+(>|$)/g, '' );<br>

if (value != '' ) vcard.Values[classes[n]] = value;<br>

}<br>

}<br>

}<br>

for ( var i in el.childNodes) scanElement(el.childNodes[i]);<br>

}<br>

scanElement(doc. document .body);<br>

if (!vcard.isEmpty())<br>

sys.log( 'vCard = ' +vcard.toString());<br>

else <br>

sys.log( 'no vCard found on ' +src.href);<br>

});<br>

}
<br>

<br>

* This source code was highlighted with Source Code Highlighter .










まとめ



結果の使用は簡単です:



loader.load('http://www.google.com/profiles/olostan');<br>

loader.load('http://www.flickr.com/people/olostan/');<br>









私はそれが最終的な、少し真面目な製品としてではなく、概念実証として、そしてnode.jsを感じるために考案されたとすぐに言いたいです。



完全なコード (Googleドキュメントにアップロードされ、Googleアカウントが必要な場合があります)



PSこれは、サンドボックスでの私の投稿からの再投稿です。 これが受け入れられない場合は申し訳ありませんが、コメントを聞くのは面白いでしょう。 招待してくれたロマチェフに感謝します。 テーマ別ブログへの投稿にはカルマがありません。



All Articles