node.jsでスクレイピングしてみる

node.jsでスクレイピングしてみる

初めまして。4月入社の山崎です。これが初投稿になります。 今回はnode.jsでスクレイピングをします。

経緯

とあるサイトをリニューアルしていて、旧サイトの静的ページからテキストなどのコンテンツを持ってきて新サイトのHTMLタグの中に貼り付けていくという作業はよくあると思います。

僕もとあるサイトのリニューアル時にそういった作業をやっていたのですが、旧サイトのよくある質問の質問と回答のページが分かれており、回答ページに行ってテキストをとってきてHTMLに貼り付けていました。 ただ、その質問も全部で100個くらいあり、1つ1つコピペするのは面倒くさいので、スクレイピングで特定のURLに一括アクセスし、テキストを引っ張ってきて、新サイトのHTML構造に埋め込み、HTMLを自動生成しようと思ったのがきっかけです。

僕はシェルスクリプトはほぼ書けず普段JSを使うことが多いため、node.jsを使うことにしました。

事前準備

まず、今回使用するライブラリをインストールします。今回の目的はあくまでHTMLを生成するためだけのもので、特にプロジェクトで使うわけではないので、グローバルインストールします。(nodeやnpmなどは事前にインストールされている前提)

$ npm install -g scraperjs
次に、scrape.jsを作成します。
$ touch scrape.js
作成したscrape.jsの先頭で先ほどインストールしたscraperjsを読み込みます。
const scraperjs = require('scraperjs');
requireのパスはそのファイルから一番近いnode_modulesディレクトリのパスを読みます。ですので、これだけではモジュールが見つからないと怒られます。 試しにnodeコマンドでnodeコンソールに入り
$ node
> global.module.paths

とうってみるとrequireがどこからのパスを優先的に読み時むのかが確認できます。そしてグローバルにインストールされたモジュールがどこにインストールされているかというのは

$ npm root -g

コマンドで確認できます。 そして、グローバルのnode_modulesディレクトリを参照できるようにするには環境変数$NODE_PATHにグローバルのnode_modulesのパスを追記する必要があります。 .bash_profileに以下を追記してください。

NODE_PATH=`npm root -g`
export NODE_PATH

シェルを再起動し、

$ echo $NODE_PATH

と打ったらグローバルのnode_modulesのパスが環境変数$NODE_PATHに入っているのがわかります。 ここまで終わったらrequireのエラーが解消して、モジュールが使えるようになったので、いよいよスクレイピング開始です。

HTMLを用意

実在するサイトから引っ張ってくるのもなんかあれなので、自分のローカル環境で適当にHTMLを作ってみました。 まずは、適当なディレクトリにfaqというディレクトリを作成し、スクレイピング対象となるHTMLを用意します。

<!-- 01.html -->
<html lang="ja">
<head>
<title>よくある質問1</title>
<meta charset="utf-8">
</head>
<body>
<h1>お名前は?</h1>
<p>山崎です。</p>
</body>
</html>
<!-- 02.html -->
<html lang="ja">
<head>
<title>よくある質問2</title>
<meta charset="utf-8">
</head>
<body>
<h1>出身は?</h1>
<p>静岡</p>
</body>
</html>
<!-- 03.html -->
<html lang="ja">
<head>
<title>よくある質問3</title>
<meta charset="utf-8">
</head>
<body>
<h1>血液型は?</h1>
<p>A型</p>
</body>
</html>

次にPHPのビルドインサーバーでWEBサーバーを起動します。

$ php -S 127.0.0.1:8000 -t ./faq

テキストを引っ張ってきて、新しくHTMLを生成する

旧サイトのHTMLを 新しくこんな感じの構造に書き換えたいとします。

<dl>
<dt>質問1</dt>
<dd>質問1答え</dd>
<dt>質問2</dt>
<dd>質問2答え</dd>
<dt>質問3</dt>
<dd>質問3答え</dd>
</dl>

やっていきます。 URLが連番になっているのでページの分だけ処理を繰り返せるよう、ループを回します。 URLを指定してscraperを生成しscrapeメソッドの引数に成功時の処理を書いていきます。 変数questionにh1のテキストを入れて、変数answerにpのテキストを入れます。 jQueryオブジェクトがコールバックの引数として渡ってくるので、これよりも複雑な構造になっていたとしても普段jQueryを使ってる人にとっては割と簡単に情報が取得できると思います。

const scraperjs = require('scraperjs');
for (let i = 1; i <= 3; i++) {
    let url = `http://127.0.0.1:8000/0${i}.html`;
    scraperjs.StaticScraper.create(url).scrape(($) => {
        let question = $('h1').text();
        let answer = $('p').text();
        let html = `<dt>${question}</dt>\n<dd>${answer}</dd>\n`;
        console.log(html);
    }).catch((error) => {
        console.error('Error:', error);
    });
}

ES6のテンプレートリテラルで生成するHTMLの雛形を作成します。今までテンプレートリテラルはあまり使ったことなかったのですが、改行コードなどもそのまま書けるし読みやすいのでいいですね。

let html = `<dt>${question}</dt>\n<dd>${answer}</dd>\n`;

そして生成したHTMLを出力します。

console.log(html);

出力先のHTMLファイルを作成し、ターミナルでscrape.jsを実行します。出力先を先ほど作ったHTMLファイルに指定します。

$ touch ./new.html
$ node scrape.js > new.html

実行結果

<!-- new.html -->
<dt>お名前は?</dt>
<dd>山崎です。</dd>

<dt>出身は?</dt>
<dd>静岡</dd>

<dt>血液型は?</dt>
<dd>A型</dd>

出力できました。あとは生成したHTMLをまるっとコピーしてdlの中に貼り付ければおしまいです。

まとめ

今回のケースの場合は、量が少なかったのでここまでやるぐらいだったら、コピペした方が早いと思いますが、量次第ではかなり時間がかかってきますし、確認作業や修正も大変だと思うので、こういう機会があったら、とても簡単なので試してみてください。

scraperjs公式Git-Hub

最後まで読んでいただいた方ありがとうございました。

  • このエントリーをはてなブックマークに追加

この記事を読んだ人にオススメ