Edited at

Node.js で取得した Web ページを変数に保存して利用する方法

More than 1 year has passed since last update.


[ ご注意 ]

記事タイトルがあまり適切でないことは認識しています。

自分は同期/非同期処理のキーワードに気付かないとこの回答に辿り着けなかったので、

そのキーワードに気付けなくても検索で辿り着けるようなタイトルにしています。


最近 Node.js を触り始めました。

「Node.js でスクレイピングする方法」みたいな記事をよく見かけてたので簡単にできるものと思ってたけど、タイトルの通りのことができなくて混乱しました。

非同期ではなく、同期的に Web ページの取得をしたい場合の方法です。

やりたいことを他の言語で書くと以下のようになります。


PHP

Goutte を使うとサクッと書けます。


Terminal

$ composer require fabpot/goutte



request-sample.php

<?php

require 'vendor/autoload.php';

$client = new Goutte\Client();
$crawler = $client->request('GET', 'http://www.yahoo.co.jp');

echo $crawler->filter('title')->text();
// or
echo $crawler->filter('title')->eq(0)->text();



Terminal

$ php request-sample.php

Yahoo! JAPAN


Ruby

Nokogiri を使って以下のようになります。


Terminal

$ gem install nokogiri



request-sample.rb

require 'nokogiri'

require 'open-uri'

doc = Nokogiri::HTML(open('http://www.yahoo.co.jp'))
puts doc.css('title')



Terminal

$ ruby request-sample.rb

<title>Yahoo! JAPAN</title>


GoogleAppsScript

UrlFetchApp を使います。


コード.gs

function myFunction() {

var response = UrlFetchApp.fetch('http://www.yahoo.co.jp');

var myRegexp = /<title>([\s\S]*?)<\/title>/i;
var match = myRegexp.exec(response);
var title = match[1];

title = title.replace(/(^\s+)|(\s+$)/g, '');
Logger.log(title);
}


[17-04-XX XX:05:02:350 JST] Yahoo! JAPAN


Node.js

では Node.js だとどう書くのかと思って調べると、requestcheerio-httpcli などを使うらしい。


Terminal

$ npm install request



request-sample.js

var request = require('request');

var response = '';

request('http://www.yahoo.co.jp', function(error, response, body) {
response = body;
});

console.log(response);



Terminal

$ node request-sample.js

$

…あれ?


Terminal

$ npm install cheerio-httpcli



request-sample2.js

var client = require('cheerio-httpcli');

var response = '';

client.fetch('http://www.yahoo.co.jp', function (err, $, res) {
response = $;
});

console.log(response);



Terminal

$ node request-sample2.js

$

んんん??

どちらを使っても結果が出力されない…。

公式サイトや色んな記事を見ても書いてあるのは requestfetch の中で処理を続ける方法のみ。

でも自分が本当にやりたいのは、取得した内容を呼び出し元関数に返して処理を続けるようなことなので、fetch の中などに書いて続けるやり方じゃ要件を満たせないのです。。


request-sample3.js

var request = require('request');

function myFunction() {
var response = getContent('http://www.yahoo.co.jp');

var myRegexp = /<title>([\s\S]*?)<\/title>/i;
var match = myRegexp.exec(response);
var title = match[1];

title = title.replace(/(^\s+)|(\s+$)/g, '');
return title;
}

function getContent(url) {
var response = '';
request(url, function(error, response, body) {
response = body;
});
return response;
}

console.log(myFunction());



Terminal

$ node request-sample3.js

(getContent からの返り値が空文字なので、正規表現の処理に失敗してエラーになる)


正解は sync-request

日本語記事でそれらしきものがなくて、公式サイトを見てもよくわからないので Stack Overflow で聞いてみました。

速攻で回答が来て、案の定「request の中に書け」と言われる。。。

そんなことわかってんだよと思いながら「中じゃなくて外で値を利用したい」と返したら「promisesasync.js は見た?」との返事を頂く。

その他寄せられた回答を元に調べ直し、sync-request を利用したら見事にうまくいきました。


Terminal

$ npm install sync-request



request-sample4.js

var request = require('sync-request');

var response = request('GET', 'http://www.yahoo.co.jp');

console.log(response.getBody().toString());
// or
console.log(response.body.toString());



Terminal

$ node request-sample4.js

(Yahoo! のソースコードが出力される)

前述の本当にやりたいこと(request-sample3.js)を修正すると以下のようになります。


request-sample3.js

//var request = require('request');

var request = require('sync-request');

function myFunction() {
var response = getContent('http://www.yahoo.co.jp');

var myRegexp = /<title>([\s\S]*?)<\/title>/i;
var match = myRegexp.exec(response);
var title = match[1];

title = title.replace(/(^\s+)|(\s+$)/g, '');
return title;
}

function getContent(url) {
var response = '';

// request(url, function(error, response, body) {
// response = body;
// });
response = request('GET', url);

// return response;
return response.body;
}

console.log(myFunction());



Terminal

$ node request-sample3.js

Yahoo! JAPAN

(^o^)/


原因とまとめ

原因というか仕様というか。

Node.js では基本的に同期処理ではなくて非同期処理でリクエストが実行されるようです。

今回のように同期処理で値を取得したい場合はライブラリを使ったりなどコツがいるようです。

sync-request の公式サイトには注意書きとして、利用は非推奨で本番環境では使うなよ!と書いてあります。

自分はテストとして使いたいだけなので問題ないですが、ご利用は計画的にお願いします…。


最後に

繰り返しになりますが、記事タイトルがあまり適切でないことは認識しています。

自分は同期/非同期処理のキーワードに気付かないとこの回答に辿り着けなかったので、

そのキーワードに気付けなくても検索で辿り着けるようなタイトルにしています。