Node.js で簡単なクローラーを作成してWeb サイトの情報を得る その2

[前回の記事](https://qiita.com/yamachan360/items/3ee48de81394db9d3063) への反応は薄かったのですが、自分メモを兼ねて続きを書きますね。

前回に作成したのは、ページャー機能のあるWebサイトを対象に、リストされたコンテンツの一覧を作成する Web クローラーでした。対象サイトは [日本IBM 開発者向けサイト: Code Patterns](https://developer.ibm.com/jp/patterns/) で、100個以上のパターン情報を含んだ配列の [JSONデータ](https://github.com/yamachan/simple-web-crawlers/blob/master/list-ibmjp-patterns.json) を生成することができました。

このサンプルを拡張して、他のサイトのクロールを実行してみましょう。どこを改造すれば対応できるのか、どこを流用できるのか、ツールの仕組みと拡張方法について理解するのに、役立つとおもいます。

# 英語サイトをクロールしてみる

[Code Patterns 日本語サイト](https://developer.ibm.com/jp/patterns/) には、翻訳元となる [英語サイト](https://developer.ibm.com/patterns/) があるのですが、こちらもクロールしたい、と相談がありました。

いや、英語版なだけなら URL 変更するだけで良くない？と思ったのですが… アクセスしてみると何故かサイト構造や、ページの組み立て方が違うのですよ。それもかなり！ですので、元のツールを改造して、[別ツール](https://github.com/yamachan/simple-web-crawlers/blob/master/list-ibm-patterns.js) として実装してみました。

## とりあえず js ファイルを用意

[list-ibmjs-patterns.js](https://github.com/yamachan/simple-web-crawlers/blob/master/list-ibmjp-patterns.js) ファイルをコピーして [list-ibm-patterns.js](https://github.com/yamachan/simple-web-crawlers/blob/master/list-ibm-patterns.js) というファイルを作成します。とりあえず先頭部分を英語サイトにあわせ書き換えておきましょう。

```javascript:list-ibm-patterns.js
if (process.argv.length !== 2) {
	console.log('list-ibm-patterns.js');  // メッセージを英語サイト用に変更した
	console.log('  Simple crawler tool to list patterns from IBM Code patterns site.');
	console.log('  2018/11/24 by github.com/yamachan');
	return;
}

const nedb_file_name = 'list-ibm-patterns.nedb';  // nedb ファイル名も忘れず変更する
```

## まずはページャー機能の確認

前回と同様に [対象サイトのURL分析](https://qiita.com/yamachan360/items/3ee48de81394db9d3063#%E5%AF%BE%E8%B1%A1%E3%82%B5%E3%82%A4%E3%83%88%E3%81%AEurl%E5%88%86%E6%9E%90) を実施してみましょう。

まずページャーで2ページ目のURLですが
https://developer.ibm.com/patterns/page/2/
です。次に、これを1ページ目に適用すると、以下のトップにリダイレクトされます。
https://developer.ibm.com/patterns/
ここまでは日本語サイトと同じですね。

しかし英語サイトで9999ページ目の表示を確認すると、ページは404エラーになってしまいます。あとでメインループを修正する際に、終了の検知に少し工夫が必要になりそうです。
https://developer.ibm.com/patterns/page/9999/
まあ基本は同じですから、このまま進めます。まずは対象 URL を得るための関数を英語サイトにあわせて修正しておきましょう。

```javascript:list-ibm-patterns.js
function getListURL(_page) {  // 日本版から /jp を外しただけです
	if (_page == 1) {
		return 'https://developer.ibm.com/patterns/';
	} else {
		return 'https://developer.ibm.com/patterns/page/' + _page + '/';
	}
}
```

## 対象サイトのコンテンツ分析

次は[対象サイトのコンテンツ分析](https://qiita.com/yamachan360/items/3ee48de81394db9d3063#%E5%AF%BE%E8%B1%A1%E3%82%B5%E3%82%A4%E3%83%88%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%84%E5%88%86%E6%9E%90) です。ページに並んだカード形式のコンテンツリストが対象ですから、Webブラウザの開発者ツールで、それをqueryできる条件を探します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/ddfa9f38-4ffc-09c3-90a0-f352a614846f.png)
リスト対象に ibm--card というクラスが付与されているのがわかります。日本版よりハイフンが多いですね… その親となる要素には cpt-content というクラスが付与されているようです。

というわけで、開発者ツールのコンソールに切り替え、これらの条件が絞り込みに利用できるか確認しましょう。

```javascript:開発者コンソール
document.querySelectorAll(".cpt-content .ibm--card")
```

ちゃんとページ内のパターン8個 (日本版は15個でした) がリストされることが確認できました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/a9708f56-cbdc-c389-35fa-8cd6fec5dc39.png)
早速、js コードのほうに、この値(query用の文字列)を反映しておきましょう。

```javascript:list-ibm-patterns.js
const list_item_query = '.cpt-content .ibm--card';
```

## メインロジックを更新してみよう

さて今度はメインロジックです。

日本語版は「Webページにアクセスし、取得した要素数が0でなかったらループ継続」という条件で、処理をループさせていました。しかし英語版では「要素がないページは404エラーを返す」ので、ループの条件にちょっと手を加えないといけません。

今回は「英語版でも日本語版と同じようにループさせる」つまり「404エラーが返ってきたらエラー終了ではなく、要素数が0だったとして扱う」ようにコードを修正してみましょう。以下のような実装になります。

```javascript:list-ibm-patterns.js
(async () => {
	let page = 1;
	let number_of_items = 0;
	let count_of_items = 0;
	do {
		console.log('LOOP: page = ' + page);
		let url = getListURL(page);
		let ret = client.fetchSync(url);
		if (ret.error || !ret.response || ret.response.statusCode !== 200) {
			console.log('ERROR:' + url);
			number_of_items = 0;  // エラー時に終了させず要素数を0にセットする
		} else { // 以降の処理は元のまま
			let items = ret.$(list_item_query);
			number_of_items = items.length;
			processListItems(ret, items, page);
			count_of_items += number_of_items;
			console.log('number_of_items = ' + number_of_items);
			page++;
		}
	} while (number_of_items > 0);
	console.log('count_of_items = ' + count_of_items);
})();
```

`return` していた部分を `number_of_items = 0` に書き換えただけで、他のコードはほとんどそのまま、なのがわかりますか？終了条件によるコード変更以外は、そのまま利用できていることを確認してみてください。

なおエラー文を残してあるので、正常に終了してもエラーが1回表示されてしまいます。まあ、細かいところは気にしないでいきましょうw
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/644396fc-814c-cb12-d65a-b917ee29b1f7.png)


## リスト対象の分析

さてメインループを更新した後は [リスト対象の分析](https://qiita.com/yamachan360/items/3ee48de81394db9d3063#%E3%83%AA%E3%82%B9%E3%83%88%E5%AF%BE%E8%B1%A1%E3%81%AE%E5%88%86%E6%9E%90) にとりかかります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/aac1e5ef-27af-9c33-36a7-745d272f58d2.png)
この構成を手掛かりに、必要なデータそれぞれの要素を入手し、[cheerio](https://www.npmjs.com/package/cheerio) や [cheerio-httpcli](https://www.npmjs.com/package/cheerio-httpcli) のメソッドで操作して値を入手していきます。itemToObject関数を、対象の構成にあわせて更新しましょう。

```javascript:list-ibm-patterns.js
function itemToObject(_ret, _item) {
	return {
		title: _ret.$('h3', _item).text().trim(),
		url: _ret.$('a.ibm--card__block_link', _item).url(),
		date: _ret.$('.ibm--card__date', _item).text().trim(),
		tech: _ret.$('span.bx--tag--category', _item).map(function(){
			return _ret.$(this).text().trim()
		}).get().sort()
	};
}
```

同じカード形式なのですが、日本版と表示内容はわりと違っているのでした。概要文やGitHubへのURLが無いかわりに、更新日が表示されています。

と、生成されるデータ(Object)の属性が変わっているので、checkUpdateObject関数もそれにあわせて修正しておきましょう。

```javascript:list-ibm-patterns.js
function checkUpdateObject(_obj, _doc) {
	return JSON.stringify(_obj) !== JSON.stringify({
		title: _doc.title,
		url: _doc.url,
		date: _doc.date,  // ここ追加
		tech: _doc.tech
	});
}
```

## これで完成です

これでツールの更新は完了です。以下のように実行して英語サイトの情報をクロールしましょう。

```
node list-ibm-patterns.js
```

## ページ削除への対応

対象サイトでページ削除があった場合に、その対応について質問がありました。今回のツールは動作が軽いこともあり、手抜きしてページ削除への対応ロジックが無かったりします。次回あたりで削除ロジックも説明したいとおもいます。

というわけで、\*.nedb ファイルをいったん削除し、ツールを実行してリストを再作成することで対応してください。

## 利用上の注意

nedb の特徴として、更新処理の直後は新旧両方の情報が \*.nedb ファイルに残っていることがあります。これは再読み込みで解消しますので、UPDATE が表示されたらツールをもう一度実行するのが確実です。json 形式のファイルを入手したい場合、以下のように実行すると安全です。

```
node list-ibm-patterns.js
node list-ibm-patterns.js
node nedb2json list-ibm-patterns.nedb > list-ibm-patterns.json
```

実際に生成した JSON 形式のデータは [コチラ](https://github.com/yamachan/simple-web-crawlers/blob/master/list-ibm-patterns.json) に公開しています。

# というわけで

同じサイトの英語版と日本語版かと思ってたら、かなり違いがあるので驚きました。とはいえ、ページャー機能を使ったWebサイトであるのは同じですし、カード形式の表示も似ているので、それほど苦労せずにツールを対応させることができました。

前回の日本語サイト用のコードである [list-ibmjp-patterns.js](https://github.com/yamachan/simple-web-crawlers/blob/master/list-ibmjp-patterns.js) と、今回の英語サイト用のコードである [list-ibm-patterns.js](https://github.com/yamachan/simple-web-crawlers/blob/master/list-ibm-patterns.js) を見比べてみると、流用できるコードや構成、そして対象に応じて変更すべきトコロ、ざっくりと見えてくるのではないでしょうか。

実はもうひとつ別なサイトのクロールをお願いされていたりするので、余裕がありましたらそちらも投稿としてまとめてみたいとおもいます。少なくとも、未来の自分が読んで参考にしそうな気がするのでw

それではまた！