勉強のために日本語訳します。
はじめに
本文では、ハッカーニュースサイトの、上位ランキングのリンクと、題名、URL、点数、コメント数など他のデータを取得するために、ホームページを走査します。これは、node.js を使ってウェブページからデータを取得するための、いくつかある方法の内の1つです。主に、Matthew Mueller 氏が作成した cheerio と呼ばれるモジュールを使います。cheerio はサーバーサイドで使うように設計された jQuery のサブセットです。
cheerio は軽量で、高速、融通が利き、jQuery に慣れていれば、使い易くできています。また、簡単な HTTP クライアントとして、Mikael Rogers 氏が作成した request モジュールを使います。
必要事項
node.js、jQuery、VPS や SSH の接続方法など基本的な Linux の管理・設定ができることが前提です。もし、node.js を使うことが初めてで、まだインストールしていなければ、お使いのOSへのインストール手順について、上記の Articles & Tutorials の章を参照してください。
プログラム
NPM を使って必要なモジュールをインストールします。次のコマンドを実行します。
npm install request cheerio
これは、現在の作業ディレクトリにのみ、モジュールをインストールします。全体にインストールする場合は、 npm install -g request cheerio を実行します。
ファイル scrape.js を作成し、次の行を追加します。
var request = require('request');
var cheerio = require('cheerio');
これは、必要になるモジュールをすべて読込みます。
まずは request でハッカーニュースサイトのホームページを読込み、ページの HTML コードを表示しましょう。もし、エラーが無ければ、HTTP ステータスは 200 になります。
ファイルに次の行を追加します。
request('https://news.ycombinator.com', function (error, response, html) {
if (!error && response.statusCode == 200) {
console.log(html);
}
});
node scrape.js でスクリプトを実行してみてください。端末画面に HTML コードが表示されるはずです。
目的のデータをどうやって抽出するかを調べるために、HTML コードで要素がどのように構成されているかを知る必要があります。良い方法は、Google Chrome に組み込まれているウェブデベロッパーツールを使って、目的の要素を調査することです。ウェブページで右クリックし「要素を調査する」を選びます。
次の例では、Chrome でハッカーニュースサイトを開いて、右クリックし、上位ランク記事の題名を調査しています。
図1.
ウェブデベロッパーコンソールで確認すると、上位ランクの30個すべてのリンクと題名を解釈するためには、 クラス名 comhead が付いた「span」要素を探し出し、jQuery API 関数の prev() を使って、その1つ前の「a」要素を選べば良いことが分かります。
プログラムを次のように変更して、動作を確認します。
request('https://news.ycombinator.com', function (error, response, html) {
if (!error && response.statusCode == 200) {
var $ = cheerio.load(html);
$('span.comhead').each(function(i, element){
var a = $(this).prev();
console.log(a.text());
});
}
});
プログラムを実行すると、期待したように、30個の題名の一覧が得られました。次に、残りの他のデータを解釈するように修正します。
request('https://news.ycombinator.com', function (error, response, html) {
if (!error && response.statusCode == 200) {
var $ = cheerio.load(html);
$('span.comhead').each(function(i, element){
var a = $(this).prev();
var rank = a.parent().parent().text();
var title = a.text();
var url = a.attr('href');
var subtext = a.parent().parent().next().children('.subtext').children();
var points = $(subtext).eq(0).text();
var username = $(subtext).eq(1).text();
var comments = $(subtext).eq(2).text();
// Our parsed meta data object
var metadata = {
rank: parseInt(rank),
title: title,
url: url,
points: parseInt(points),
username: username,
comments: parseInt(comments)
};
console.log(metadata);
});
}
});
追加したプログラムの説明
1つ前要素の選択
var a = $(this).prev();
a要素より2つ上位の要素を解釈して、ランクを取得する
var rank = a.parent().parent().text();
リンクの題名を解釈する
var title = a.text();
a要素から href パラメータを解釈する
var url = a.attr('href');
HTML の表の次行から、subtext 子要素を取得する
var subtext = a.parent().parent().next().children('.subtext').children();
子要素から実際のデータを取得する
var points = $(subtext).eq(0).text();
var username = $(subtext).eq(1).text();
var comments = $(subtext).eq(2).text();
修正したスクリプトを実行すると、オブジェクトの配列が次のように出力されます。
[ { rank: 1,
title: 'The Meteoric Rise of DigitalOcean ',
url: 'http://news.netcraft.com/archives/2013/06/13/the-meteoric-rise-of-digitalocean.html',
points: 240,
username: 'beigeotter',
comments: 163 },
{ rank: 2,
title: 'Introducing Private Networking',
url: 'https://www.digitalocean.com/blog_posts/introducing-private-networking',
points: 172,
username: 'Goranek',
comments: 75 },
...
以上です。加えて、詳細な解析を行うために、抽出したデータを、MongoDB や Redis のようなデータベースに保存できます。scrape.js ファイルのプログラム全体を以下に示します。
var request = require('request');
var cheerio = require('cheerio');
request('https://news.ycombinator.com', function (error, response, html) {
if (!error && response.statusCode == 200) {
var $ = cheerio.load(html);
var parsedResults = [];
$('span.comhead').each(function(i, element){
// Select the previous element
var a = $(this).prev();
// Get the rank by parsing the element two levels above the "a" element
var rank = a.parent().parent().text();
// Parse the link title
var title = a.text();
// Parse the href attribute from the "a" element
var url = a.attr('href');
// Get the subtext children from the next row in the HTML table.
var subtext = a.parent().parent().next().children('.subtext').children();
// Extract the relevant data from the children
var points = $(subtext).eq(0).text();
var username = $(subtext).eq(1).text();
var comments = $(subtext).eq(2).text();
// Our parsed meta data object
var metadata = {
rank: parseInt(rank),
title: title,
url: url,
points: parseInt(points),
username: username,
comments: parseInt(comments)
};
// Push meta-data into parsedResults array
parsedResults.push(metadata);
});
// Log our finished parse results in the terminal
console.log(parsedResults);
}
});