JavaScript
Node.js
jQuery
scraping

node.jsでjQueryを使ってスクレイピングする


要約



  • Node.jsでスクレイピングする


  • jQueryを利用する


  • Promise,async/awaitで同期処理にする。なお同期処理に悪戦苦闘した。難しい・・・


動機


  • 自然言語の機械学習用データ集めのため、スクレイピングする必要があった。

  • Pythonでばかりコーディングするのも芸がない。DOMを操作するんだから、jQueryを使ってやろう。

  • Node.jsは前から興味があったけど、初めて触る。JSのお勉強がてらやってみるか。(※ワタクシのJavaScript知識は10年以上前で止まっていましたので。プロミス?なにそれ、消費者金融?って状態でした)


やりたいこと


  • とある、Webサイトの記事のURLを取得したい。

  • Webサイトには一覧ページがあり、1ページに50件程度の記事一覧がある。ページはパラメータで変えられる(https://hoge?page=2のような形で)

  • 記事一覧の各ページから、記事本文のURLと投稿日、記事タイトル等を取得する


やったこと


必要なライブラリのインストール

$ node -v # => v8.11.1

$ npm init
$ npm install jquery --save
$ npm install jsdom --asve

jqueryは趣旨のとおり。jsdomはjqueryをブラウザなしで利用するのに必要だった。


コーディング

ポイントは


  • https.getは非同期で動作するので、(同期させたいなら)Promiseオブジェクトを返す関数でラップしてやる(get_entry_titles)

  • GETで取得したHTMLをそのままjqueryへ突っ込むと windowがない といってエラーになるで、jsdomを利用する

  • 同期したいところをasync/await を使う。

const https = require("https"); //GETメソッドでリクエストするために

const util = require("util"); //format関数を利用するために
const jsdom = require("jsdom"); //jqueryをつかうために
const jquery = require("jquery"); //趣旨のとおり、DOM走査するために

// 記事一覧のページ
const URL = "https://hoge?page=%s";

// スクレイピングする処理(プロミスオブジェクトを返す)
// 最近はfunctionで定義しないで、無名関数を変数に代入にするのがモダンなの?
function get_entry_titles(page){
// プロミスをnewする
return new Promise((resolve) => {
// httpsリクエスト
const req = https.get(util.format(URL,page), (res) =>{
// このあたりは公式のドキュメント参考に
var html = "";
res.setEncoding('utf8');
res.on('data',(chunk) => html += chunk);
res.on('end',() => {

// windowがないとjQueryが動かないそうで、以下の様にする模様
const dom = new jsdom.JSDOM(html);
const $ = (jquery)(dom.window);

//スクレイプしたい処理をjQueryで記述
$(".foo").each((index,element) =>{
// スクレイプ結果を出力
console.log(util.format(
"%s\t%s\t%s"),
page, //ページNo.
$(element).find(".bar").attr("href"), //リンクURL
$(element).find(".hoge").text()); //タイトル
});
//ここで処理完了なのでresolve。これにたどり着くのに悪戦苦闘した
resolve();
});
});
req.end();
});
}

// メインのループ処理(ページ数文繰り返し) async関数にする
async function main(){
// 1〜9ページ繰り返す
for (var page=1; page < 10 ; page++){
// awaitを付けて、1ページづづ同期する。じゃないとページ数だけが先にインクリメントされてしまう。
await get_entry_titles(page);
}
}

// 処理エントリ
main();


最後に

うむ、JavaScript、もう一度勉強しないと。10年の遅れをキャッチアップせねば。

なんとか動作はしたが、もっといいやり方とかがあるかもしれない。