読者です 読者をやめる 読者になる 読者になる

Persistence

続けることに意味がある。

NAVERまとめから位置情報を含むスポット情報をJSON形式で取得する

現在開発中の別のサービスのために、位置情報を含むスポット情報が欲しかったので、たまたま見つけたNAVERまとめからデータを取得するツールを作ってみました。

URL

概要

NAVERまとめのURLを入力すると、そのページにあるタイトル・説明と位置情報付きのスポット情報をJSON形式で返します。

(実行例)

入力:

http://matome.naver.jp/odai/2134519339022942401

出力:

{
    "matome": {
        "title": "京都旅行で「カップルデート」にオススメの人気観光スポット",
        "discription": "日本国内でも、特に人気のデートスポットが京都です。京都には、古き良き町であったり、オイシイ食文化が残っています。京都旅行でオススメの人気観光スポットを紹介します!!"
    },
    "location": [
        {
            "spotName": "京都タワー",
            "address": "日本, 〒600-8216 京都府京都市下京区東塩小路町 京都タワー",
            "discription": "京都駅から下車してすぐの場所にあるので、アクセスは抜群です。1964年に建てられた古いタワーですが、京都のシンボルとして親しまれています。\n\n展望室営業時間:9:00~21:00\n大人:770円\nhttp://www.kyoto-tower.co.jp/",
            "image": {
                "thumbnailUrl": "http://rr.img.naver.jp:80/mig?src=http%3A%2F%2Fimgcc.naver.jp%2Fkaze%2Fmission%2FUSER%2F20120818%2F13%2F1169533%2F7%2F540x405x8c76a58e48ef57fe4f9388ef.jpg%2F300%2F600&twidth=300&theight=600&qlt=80&res_format=jpg&op=r",
                "originalUrl": "http://tkpkyoto.net/images/map_gckyoto_540.jpg",
                "sourceUrl": "http://tkpkyoto.net/access.shtml"
            },
            "lat": 34.98752,
            "lng": 135.759301
        },
        {
            "spotName": "東本願寺",
            "address": "日本, 〒600-8505 京都府京都市下京区常葉町 東本願寺",
            "discription": "巨大な建物の数々を見れば、度肝を抜かれる事は間違いなし、本当に大きい木造建築は圧巻\n\n京都の寺が詰まらないと思っている人は、最初に京都駅から近い東本願寺に行きましょう。京都駅から歩いて行けて、世界最大の木造建築に驚きます。\n\nhttp://www.higashihonganji.or.jp/",
            "image": {
                "thumbnailUrl": "http://rr.img.naver.jp:80/mig?src=http%3A%2F%2Fimgcc.naver.jp%2Fkaze%2Fmission%2FUSER%2F20120820%2F13%2F1169533%2F28%2F1280x960xa5a79e5818e4780010691a1.jpg%2F300%2F600&twidth=300&theight=600&qlt=80&res_format=jpg&op=r",
                "originalUrl": "http://userdisk.webry.biglobe.ne.jp/001/247/64/N000/000/000/117603302339416302404.JPG",
                "sourceUrl": "http://kontomo.at.webry.info/200704/article_1.html"
            },
            "lat": 34.9912688,
            "lng": 135.7587804
        },
        *長いので以下省略*
}

フォーム入力だけでなく、APIもあります。

コーディング

構成は Node.js + Express + Heroku です。 前に作った Beatube IIDX ~ver.SPADA~ のソースをかなり流用したため、書いた量自体はほんのわずかですが、その中でポイントを2点ほど紹介します。

関数プロパティによるメモ化パターン

いわゆるキャッシュです。 同じURLに対するリクエストが2回以上来た時、(ページが更新されていなければ)結果は同じなのに毎回ページを取得して処理するのは無駄になります。 なので、関数のプロパティにURLをキーとしたハッシュを持たせておき、同じキーがある場合はその値を返すようにしました。

シングルトン

シングルトンとは、あるクラスのインスタンスを一つだけにする(何回newしても必ず同じインスタンスが作られる)というデザインパターンです。 今回のケースですと、インスタンス化した関数にキャッシュを持たせているため、どこかでもう一つ別のインスタンスが作られた場合に、また一からキャッシュをためることになります。 なので、このパターンを当てはめました。

exports.Scraping = function Scraping(){
        // singleton
    // インスタンスをキャッシュする
    var instance = this;

    // ローカル変数
    var request = require('request');
    var cheerio = require('cheerio');

    // 変換データのキャッシュ
    var cache = {};

    /**
   * htmlのbodyを解析して位置情報のリストを返す
   * 
   * @method getLocation
   * @return {Array} 位置情報のリスト
   */ 
    this.getLocation = function(url, callback){

        if(cache[url]){
            return callback(cache[url]);
        }

        _getBody(url, function(error, body){
            if(error) {
                return callback({});
            }

            var $ = cheerio.load(body);

            var matomte = {
                title: $(".mdHeading01Ttl a").text() || "",
                discription: $(".mdHeading01DescTxt").text() || ""
            };

            var location = [];
            $(".MdMTMWidgetList01 ._jWidgetData").each(function(){
                var json = JSON.parse($(this).attr("data-contentdata"));
                // console.log(json);
                if(json.location){
                    var lo = {
                        spotName: json.location.spotName || json.title,
                        address: json.location.address,
                        discription: json.description,
                        image: {
                            thumbnailUrl: json.thumbnailUrl,
                            originalUrl: json.url,
                            sourceUrl: json.sourceUrl
                        },
                        lat: json.location.lat,
                        lng: json.location.lng
                    };
                    location.push(lo);
                }
            });

            var result = {
                matome: matomte,
                location: location
            };

            cache[url] = result;
            return callback(result);
        });
    };

    /**
   * リクエストのbody要素を返却する
   * 
   * @method _getBody
   * @return {String} body要素
   */
    function _getBody(url, callback){
        request({url: url}, function(error, response, body) {
            if(error || response.statusCode != 200){
                // console.log(error);
                return callback(error);
            }
            // console.log(body);
            return callback(null, body);
        });
    }

    // Singleton
    // コンストラクタを書き換える
    Scraping = function(){
        return instance;
    };
};

今回参考としたページ

JavaScriptパターン ―優れたアプリケーションのための作法

JavaScriptパターン ―優れたアプリケーションのための作法

  • p44 3.2 カスタムのコンストラクタ関数
  • p79 4.8 関数プロパティによるメモ化パターン
  • p147 7.1 シングルトン

余談

Herokuに登録するとき、何を間違えたか無意識にURLを「json-parse-hatena.herokuapp.com」にしていて、デプロイした後に気づきましたw


この記事は、習慣化の手助けをするためのアプリ「TheHabit」に背中を押してもらって書いています。 (ブログを書くタスク:3回目)