[ ご注意 ]
記事タイトルがあまり適切でないことは認識しています。
自分は同期/非同期処理のキーワードに気付かないとこの回答に辿り着けなかったので、
そのキーワードに気付けなくても検索で辿り着けるようなタイトルにしています。
最近 Node.js を触り始めました。
「Node.js でスクレイピングする方法」みたいな記事をよく見かけてたので簡単にできるものと思ってたけど、タイトルの通りのことができなくて混乱しました。
非同期ではなく、同期的に Web ページの取得をしたい場合の方法です。
やりたいことを他の言語で書くと以下のようになります。
PHP
Goutte
を使うとサクッと書けます。
$ composer require fabpot/goutte
<?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();
$ php request-sample.php
Yahoo! JAPAN
Ruby
Nokogiri
を使って以下のようになります。
$ gem install nokogiri
require 'nokogiri'
require 'open-uri'
doc = Nokogiri::HTML(open('http://www.yahoo.co.jp'))
puts doc.css('title')
$ ruby request-sample.rb
<title>Yahoo! JAPAN</title>
GoogleAppsScript
UrlFetchApp
を使います。
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 だとどう書くのかと思って調べると、request
や cheerio-httpcli
などを使うらしい。
$ npm install request
var request = require('request');
var response = '';
request('http://www.yahoo.co.jp', function(error, response, body) {
response = body;
});
console.log(response);
$ node request-sample.js
$
…あれ?
$ npm install cheerio-httpcli
var client = require('cheerio-httpcli');
var response = '';
client.fetch('http://www.yahoo.co.jp', function (err, $, res) {
response = $;
});
console.log(response);
$ node request-sample2.js
$
んんん??
どちらを使っても結果が出力されない…。
公式サイトや色んな記事を見ても書いてあるのは request
や fetch
の中で処理を続ける方法のみ。
でも自分が本当にやりたいのは、取得した内容を呼び出し元関数に返して処理を続けるようなことなので、fetch
の中などに書いて続けるやり方じゃ要件を満たせないのです。。
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());
$ node request-sample3.js
(getContent からの返り値が空文字なので、正規表現の処理に失敗してエラーになる)
正解は sync-request
日本語記事でそれらしきものがなくて、公式サイトを見てもよくわからないので Stack Overflow で聞いてみました。
速攻で回答が来て、案の定「request
の中に書け」と言われる。。。
そんなことわかってんだよと思いながら「中じゃなくて外で値を利用したい」と返したら「promises
と async.js
は見た?」との返事を頂く。
その他寄せられた回答を元に調べ直し、sync-request
を利用したら見事にうまくいきました。
$ npm install sync-request
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());
$ node request-sample4.js
(Yahoo! のソースコードが出力される)
- sync-request
- 同期処理でrequestモジュールの戻り値を返す(Node.js)(非コールバック) - designetwork
- Node.jsで同期処理でHTTPリクエストを実行してページを取得する。 - 望月いちろうのREADME.md
前述の本当にやりたいこと(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());
$ node request-sample3.js
Yahoo! JAPAN
(^o^)/
原因とまとめ
原因というか仕様というか。
Node.js では基本的に同期処理ではなくて非同期処理でリクエストが実行されるようです。
今回のように同期処理で値を取得したい場合はライブラリを使ったりなどコツがいるようです。
sync-request
の公式サイトには注意書きとして、利用は非推奨で本番環境では使うなよ!と書いてあります。
自分はテストとして使いたいだけなので問題ないですが、ご利用は計画的にお願いします…。
最後に
繰り返しになりますが、記事タイトルがあまり適切でないことは認識しています。
自分は同期/非同期処理のキーワードに気付かないとこの回答に辿り着けなかったので、
そのキーワードに気付けなくても検索で辿り着けるようなタイトルにしています。