今回はNode.jsでニュース記事をWebスクレイピングをやってみたので共有します。
ゴール
「ニュース一覧から本日投稿された記事のタイトルと内容をCSVファイルに出力する」
を実現するにあたり、以下の流れで行います。
ニュース一覧ページにアクセスする
↓
一覧ページに掲載されている記事に対して詳細ページのURLを取得する
↓
各記事に対して投稿日が本日のものである場合、記事詳細ページからタイトルと本文を取得する
↓
取得した記事情報をCSVに出力する
事前準備
今回は以下のライブラリを使用しました。
・ request-promise
リクエストを非同期で送信してくれるライブラリ
・ cheerio
取得したHTMLサーバー上でjQueryのようにHTML要素を加工できる。Seleniumのようなブラウザではなく、どちらかというとPhantomJSに似た類のもの
・ csv-writer
取得したデータをCSVに書き込むことができる
・ moment.js
JavaScriptのDate
オブジェクトをラップするオブジェクトを生成して、そのオブジェクトに日付処理を任せることが出来る。記事の投稿日を比較するために使用する
また、スクレイピングする際は事前にそのサイトの構造について調査する必要があります。
実装方法
まず各ライブラリを以下コマンドでインストールします。
npm install --save request request-promise cheerio csv-writer moment
次にjavascriptファイルを作成して、流れに沿ってプログラムを記述します。
const rp = require('request-promise');
const cheerio = require('cheerio');
const createCsvWriter = require('csv-writer').createObjectCsvWriter;
const moment = require('moment');
const csvWriter = createCsvWriter({
path : './file.csv',
header : ['date', 'title', 'context']
});
async function scrapePage() {
let records = []; // CSV出力対象の記事
let date;
let title;
let context;
// 記事一覧画面のURL
const url = 'http://www.example.com'; // 必要に応じて書き換える
// 記事一覧画面のHTMLを取得する
html = await rp(url);
$ = cheerio.load(html);
// 記事詳細URLを取得、なければ終了する
const postUrls = $('#news', 'a').attr('href'); // 必要に応じて書き換える
if(!postUrls) {
console.log('記事URLが見つかりませんでした');
return;
}
// 各記事詳細のHTMLを取得
postUrls.forEach(postUrl => {
html = await rp(postUrl);
$ = cheerio.load(html);
// 各投稿に対してHTMLから日付(YYYY/MM/DD)を取得
date = $('.date').text(); // 必要に応じて書き換える
// 投稿日が当日かどうかチェック
if(new Date(date).getDate == new Date().getDate) {
/* 取得した日付が当日のものであれば以下を取得
2. タイトル
3. 内容(複数の要素で構成されていれば結合)
*/
title = $('.title').text(); // 必要に応じて書き換える
context = $('.context').text(); // 必要に応じて書き換える
// CSV出力対象に追加
records.push({
'date' : date,
'title' : title,
'context' : context,
});
}
});
console.log(records);
// CSVに出力する
csvWriter.writeRecords(records).then(() => console.log('Done'));
};
scrapePage();
あとは以下コマンドでプログラムを実行するだけです。
node index.js
補足
上記のコードはこれでも動作はしますが安全でありません。例えば
html = await rp(postUrl);
ですがエラーハンドリングが行われていません。今後このような書き方は非推奨になるので
html = await rp(postUrl).catch(() => console.log('エラーが発生しました'));
といった感じに書き直すといいでしょう。