Blog

大量ページの作成・更新を自動化してコマンド1つで終わらせる

今回は、スタティックな大量ページの作成・更新をCSVとgulp.jsを使用して、自動化する方法を備忘録を兼ねて紹介しようと思います。

カタログ系のページなどのコンテンツを持っているサイトの場合、特にページ数が多いと予算の兼ね合いで、CMSなどのコンテンツ管理システムを導入するのが難しく、そのまま手作業で更新をしているケースが結構あったりします。

スタティックなページとはいえ、gulpなどのタスクランナーを使用して自動化することができれば、開発工数を大幅に短縮することができます。

ここで、取り上げている内容は、サンプルとして以下のgithubのレポジトリーに公開していますので、参考にしてください。

GitHub - joey-i/gulp-csv2html-sample: gulp-ejsとcsv-parserを使用してページ生成をするサンプルファイルです。

原稿となるCSVを用意する

まずは、原稿となるCSVを用意します。CSVの原稿を、ページを生成する際のテンプレートと照らし合わせて、項目数などを決めて作成していきます。

今回は、京都市のオープンデータとして公開されている観光情報のCSVを使用して観光情報のページをサンプルとして作成してみたいと思います。

サンプル内にあるCSVは、以下よりダウンロードしたものを、素材として適切になるように一部改変して使用しています。

京都観光見もの情報 | 京都市オープンデータポータルサイト

必要なモジュールをインストールしておく

適当な作業ディレクトリを用意します。 そこで、以下のコマンドを順に実行します。

npm init //いろいろ聞かれますが、すべて Enter で大丈夫です。

これで、作業ディレクトリ内にpackage.jsonが作成されたかと思います。 ここに、gulpで使用するモジュールなどのリストが記述されていきます。

npm install gulp --save-dev
npm install gulp-ejs --save-dev
npm install gulp-load-plugins --save-dev
npm install gulp-rename --save-dev
npm install csv-parser --save-dev
npm install fs --save-dev

Gulp本体の他に、テンプレートエンジンのgulp-ejs、gulpのプラグインの読み込みを簡単にしてくれるgulp-load-plugins、ファイルを保存する際にファイル名を変更できるgulp-rename、CSVを読み込むcsv-parser、HTMLを保存する際に必要なfsをインストールしています。

各コマンド最後にある—save-devは、package.jsonに依存モジュールとして追記しておくためのものです。

テンプレートの元になるHTMLファイルを用意する

今回のサンプルでは、トップページ、カテゴリーページ、詳細ページの3つのテンプレートを用意するので、以下のHTMLをそれぞれ使用します。

サンプルなので、ベースのソースコードは、bootstrap4から拝借。

トップページ

以下の内容でファイルを作成し、src/ejsディレクトリにindex.ejsとして保存しておきます。

<!doctype html>
<html lang=“ja”>
<head>
<!— Required meta tags —>
<meta charset=“utf-8”>
<meta name=“viewport” content=“width=device-width, initial-scale=1, shrink-to-fit=no”>

<!— Bootstrap CSS —>
<link rel=“stylesheet” href=“https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css” integrity=“sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh” crossorigin=“anonymous”>

<title>京都市観光施設一覧</title>
</head>
<body>
    <header class=“bg-dark”>
        <div class=“container py-5”>
            <div class=“row”>
                <div class=“col-md-12”>
                    <h1 class=“text-center text-white”>京都市観光施設一覧</h1>
                </div>
            </div>
        </div>
    </header>
    <main class=“container my-5”>
        <div class=“row”>
            <div class="col-md-12">
                <ul class=“list-group">
                    <li class=“list-group-item”><a href=“#”>カテゴリー</a></li>
                </ul>
            </div>
        </div>
    </main>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity=“sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n” crossorigin="anonymous”></script>
<script src=“https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js” integrity=“sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo” crossorigin=“anonymous”></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</body>
</html>

カテゴリーページ

以下の内容でファイルを作成し、src/ejsディレクトリにgroup.ejsとして保存しておきます。

<!doctype html>
<html lang=“ja”>
<head>
<!— Required meta tags —>
<meta charset=“utf-8”>
<meta name=“viewport” content=“width=device-width, initial-scale=1, shrink-to-fit=no”>

<!— Bootstrap CSS —>
<link rel=“stylesheet” href=“https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css” integrity=“sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh” crossorigin=“anonymous”>

<title>カテゴリー名|京都市観光施設一覧</title>
</head>
<body>
    <header class=“bg-dark”>
        <div class=“container py-5”>
            <div class=“row”>
                <div class=“col-md-12”>
                    <h1 class=“text-center text-white”>カテゴリー名</h1>
                </div>
            </div>
        </div>
    </header>
    <main class=“container my-5”>
        <div class=“row”>
            <div class="col-md-12">
                <ul class=“list-group">
                    <li class=“list-group-item”><a href="#">施設詳細ページ</a></li>
                </ul>
            </div>
        </div>
    </main>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js” integrity=“sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n” crossorigin=“anonymous”></script>
<script src=“https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js” integrity=“sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo” crossorigin=“anonymous”></script>
<script src=“https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js” integrity=“sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6” crossorigin=“anonymous”></script>
</body>
</html>

詳細ページ

以下の内容でファイルを作成し、src/ejsディレクトリにspot.ejsとして保存しておきます。

<!doctype html>
<html lang=“ja”>
<head>
<!— Required meta tags —>
<meta charset=“utf-8”>
<meta name=“viewport” content=“width=device-width, initial-scale=1, shrink-to-fit=no”>

<!— Bootstrap CSS —>
<link rel=“stylesheet” href=“https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css” integrity=“sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh” crossorigin=“anonymous”>

<title>施設名|京都市観光施設一覧</title>
</head>
<body>
    <header class=“bg-dark”>
        <div class=“container py-5”>
            <div class=“row”>
                <div class=“col-md-12”>
                    <h1 class=“text-center text-white”>施設名</h1>
                    <strong class=“text-center text-white d-block”>ふりがな</strong>
                </div>
            </div>
        </div>
    </header>
    <main class="container my-5">
        <div class="row">
            <div class=“col-md-12”>

                <p class="mb-5">説明文</p>

                <div class="table-responsive">
                    <table class="table table-striped">
                        <tr>
                            <th scope=“row”>休館・休園日</th>
                            <td>ー</td>
                        </tr>
                        <tr>
                            <th scope=“row”>備考</th>
                            <td>ー</td>
                        </tr>
                        <tr>
                            <th scope=“row”>料金</th>
                            <td>ー</td>
                        </tr>
                        <tr>
                            <th scope=“row”>予約</th>
                            <td>ー</td>
                        </tr>
                    </table>
                </div>
            </div>
        </div>
    </main>
<!— Optional JavaScript —>
<!— jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous”></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js” integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src=“https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js” integrity=“sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6” crossorigin=“anonymous”></script>
</body>
</html>

ページを生成するタスクをgulpfile.jsに記述する

gulpを使用してタスクを実行させるには、gulpfile.jsというファイルを作成して、実行すべきタスクを定義しないといけません。

使用するモジュールを読み込む

gulpfile.jsの冒頭部分に今回使用するモジュールを読み込む記述をしておきます。

const gulp = require(‘gulp’);
const $    = require(‘gulp-load-plugins’)();
const fs   = require(‘fs’);
const csv  = require(‘csv-parser’);

タスクの記述

今回のサンプルのタスクをまとめると以下のような感じになります。 流れとしては、CSVを読み込む→データ整形を行う→テンプレートに変数として渡す→ページを生成 になります。

gulp.task( “ejs”, function () {
    let json;
    let csv_group_data = [];

    return fs.createReadStream(__dirname + ‘/src/csv/kyoto.csv’)
        .pipe(csv())
        .on(‘data’, function(data){
            if(typeof csv_group_data[data[‘group’]] == ‘undefined’) csv_group_data[data[‘group’]] = [];
            csv_group_data[data[‘group’]].push(data);
        })
        .on(‘end’, function(){
            json = {
                spot : csv_group_data
            };

            // console.log(json[‘spot’]);
            gulp.src([“./src/ejs/index.ejs”])
                .pipe($.ejs(json))
                .pipe($.rename(
                    {
                        extname: ‘.html’
                    }))
                .pipe( gulp.dest( “./html” ) );

            ////////////////////////
            for(let group in csv_group_data){
                json = {
                    spot : csv_group_data[group]
                };
                let output_dir = csv_group_data[group][0][‘directory’]
                gulp.src([“./src/ejs/group.ejs”])
                .pipe($.ejs(json))
                .pipe($.rename(
                    { 
                        basename: ‘index’, 
                        extname: ‘.html’ 
                    }))
                .pipe( gulp.dest( "./html" + output_dir ) );

                for(let spot_id in csv_group_data[group]){
                    json = {
                        spot_data : csv_group_data[group][spot_id]
                    };
                    let output_dir = json['spot_data']['directory']
                    gulp.src(["./src/ejs/spot.ejs”])
                    .pipe($.ejs(json))
                    .pipe($.rename(
                        { 
                            basename: json['spot_data’][‘filename’], 
                            extname: ‘’ 
                        }))
                    .pipe( gulp.dest( “./html” + output_dir ) );
                }
            }
        });
});

部分解説

最小に、ejsという名前のタスクを定義して、タスク内で使用するjsoncsv_group_dataという変数を初期化しておきます。

gulp.task( “ejs”, function () {
    let json;
    let csv_group_data = [];
});

/src/csv/kyoto.csvからCSVを読み込んで、データ整形をします。 一行ごとのCSVのデータをグループごとに施設情報のデータが格納されるように整形し直しています。今回は、グループごとに施設情報の配列を持たせる形式にしていますが、仕様に合わせてデータを整形する場合は、このセクションで行います。

return fs.createReadStream(__dirname + ‘/src/csv/kyoto.csv’)
        .pipe(csv())
        .on(‘data’, function(data){
            if(typeof csv_group_data[data[‘group’]] == ‘undefined’) csv_group_data[data[‘group’]] = [];
            csv_group_data[data[‘group’]].push(data);
        })

テンプレートに渡す変数を作成して、テンプレートを読み込んでいます。 このセクションでは、トップページのテンプレートを読み込んで、 jsonという変数を渡しています。テンプレートによるページ生成が完了した時点で、gulp-renameモジュールでindex.ejsからindex.htmlに拡張子を変更してhtmlディレクトリに出力するようにしています。

.on(‘end’, function(){
    json = {
        spot : csv_group_data
    };

    gulp.src([“./src/ejs/index.ejs”])
        .pipe($.ejs(json))
        .pipe($.rename(
            {
                extname: ‘.html’
            }))
        .pipe( gulp.dest( “./html” ) );

次に、データ整形しCSVをループ処理をして、カテゴリーページの生成と、施設詳細ページの生成を行っています。カテゴリーページは、出力時のファイル名をgroup.ejsからindex.htmlへ変更、施設詳細ページは、出力時のファイル名をCSVで指定しているので、ファイル名をCSVデータから読み込むようにし、拡張子を空にしています。

for(let group in csv_group_data){
    json = {
        spot : csv_group_data[group]
    };
    let output_dir = csv_group_data[group][0][‘directory’]
    gulp.src([“./src/ejs/group.ejs”])
    .pipe($.ejs(json))
    .pipe($.rename(
        { 
             basename: ‘index’, 
             extname: ‘.html’ 
                    }))
    .pipe( gulp.dest( “./html” + output_dir ) );

    for(let spot_id in csv_group_data[group]){
       json = {
            spot_data : csv_group_data[group][spot_id]
       };
       let output_dir = json['spot_data']['directory']
                    gulp.src(["./src/ejs/spot.ejs"])
                    .pipe($.ejs(json))
                    .pipe($.rename(
                        { 
                            basename: json['spot_data’][‘filename’], 
                            extname: ‘’ 
                        }))
                    .pipe( gulp.dest( “./html” + output_dir ) );
                }
            }
        });

HTMLをテンプレートのタグを記述する

gulpfile.jsの準備ができたので、テンプレートに渡された変数を処理できるようにテンプレートにタグを記述していきます。

トップページ

<ul class=“list-group">
    <li class=“list-group-item”><a href=“#”>カテゴリー</a></li>
</ul>

トップページのHTML内では、上のコードが各カテゴリーをループして表示させる箇所なので、以下のように変更します。これで、カテゴリー部分がループ表示されるようになります。

<ul class=“list-group”>
    <% for (var group in spot) { %>
        <li class=“list-group-item”><a href=“<%- spot[group][0][‘directory’] %>index.html”><%= group %></a></li>
    <% } %>
</ul>

カテゴリーページ

<ul class=“list-group">
    <li class=“list-group-item”><a href="#">施設詳細ページ</a></li>
</ul>

カテゴリーページでは、上のコードが施設詳細をループして表示させる箇所なので、以下のように変更します。これで、施設情報がループして表示されるようになりました。

<ul class=“list-group”>
    <% spot.forEach(function (data, id) { %>
        <li class=“list-group-item”><a href=“<%- data[‘directory’] %><%- data[‘filename’] %>”><%- data[‘title’] %></a></li>
    <% }) %>
</ul>

また、カテゴリーページのタイトル部分などには、グループ名が出力されるように以下のタグに変更しておきます。

<%= spot[0][‘group’] %>

詳細ページ

<header class=“bg-dark”>
    <div class=“container py-5”>
        <div class=“row”>
            <div class=“col-md-12”>
                <h1 class=“text-center text-white”>施設名</h1>
                <strong class=“text-center text-white d-block”>ふりがな</strong>
            </div>
        </div>
    </div>
</header>
<main class="container my-5">
    <div class="row">
        <div class=“col-md-12”>               
            <p class="mb-5">説明文</p>

            <div class="table-responsive">
                <table class="table table-striped">
                    <tr>
                        <th scope=“row”>休館・休園日</th>
                        <td>ー</td>
                    </tr>
                    <tr>
                        <th scope=“row”>備考</th>
                        <td>ー</td>
                    </tr>
                    <tr>
                        <th scope=“row”>料金</th>
                        <td>ー</td>
                    </tr>
                    <tr>
                        <th scope=“row”>予約</th>
                        <td>ー</td>
                    </tr>
                </table>
            </div>
        </div>
    </div>
</main>

詳細ページは、上のコードの部分に各施設のデータを表示するので、以下のように変更します。

<header class=“bg-dark”>
    <div class=“container py-5”>
        <div class=“row”>
            <div class=“col-md-12”>
                <h1 class=“text-center text-white”><%= spot_data[‘h1’] %></h1>
                <strong class=“text-center text-white d-block”><%= spot_data[‘ruby’] %></strong>
            </div>
        </div>
    </div>
</header>
<main class=“container my-5”>
    <div class=“row”>
        <div class=“col-md-12”>
            <% let description = spot_data[‘description’] ? spot_data[‘description’].replace(/\\n\\n/g, “<br>”) : ‘’ %>
            <p class=“mb-5”><%- description %></p>

            <div class="table-responsive”>
                <table class=“table table-striped”>
                    <tr>
                        <th scope=“row”>休館・休園日</th>
                        <td><%= spot_data[‘closed’] ? spot_data[‘closed’] : ‘ー’ %></td>
                    </tr>
                    <tr>
                        <th scope=“row”>備考</th>
                        <td><%- spot_data[‘note’] ? spot_data[‘note’].replace(/\\n\\n/g, “<br>”) : ‘ー’ %></td>
                    </tr>
                    <tr>
                        <th scope=“row”>料金</th>
                        <td><%- spot_data[‘charge’] ? spot_data[‘charge’].replace(/\\n\\n/g, “<br />”) : ‘ー’ %></td>
                    </tr>
                    <tr>
                        <th scope=“row”>予約</th>
                        <td><%- spot_data[‘reserve’] ? spot_data[‘reserve’].replace(/\\n\\n/g, “<br>”) : ‘ー’ %></td>
                    </tr>
                </table>
            </div>
        </div>
    </div>
</main>

少しだけ追加で解説をすると、京都市の観光情報のCSVにあるデータに改行コードである\nが文字列としてあったため、その文字列を<br>に置き換えるために.replace(/\\n\\n/g, “<br>”)を追加しています。

タスクを実行してページを生成する

これで、原稿のCSV、テンプレート、タスクを実行するgulfile.jsが揃ったので、 コマンドを実行してHTMLファイルを作成してみます。

npx gulp ejs 

ちなみにnpx gulpとすることで、プロジェクトフォルダにインストールしたgulpを実行しれくれます。

ローカル環境でサーバーを立ち上げてページを確認する

サーバーサイドの開発をする場合などは、Dockerなどを使用する場合がほとんどですが、 今回は、スタティックなページをサクッと確認できれば良いので、http-server というモジュールを使用してみたいと思います。

npm install http-server —save-dev 

http-serverをインストール後、package.jsonscriptsのセクションにhttp-serverのコマンドを追加します。

“scripts”: {
    “test”: “echo \”Error: no test specified\” && exit 1”,
    “http-server”: “http-server ./html —silent -p 3000”
  },

これで、コマンドラインで、以下を実行するとhttp://localhost:3000で内容を確認することができます。

npm run http-server

まとめ

いかがでしょうか? タスクランナーを使用すると、いろいろな作業を自動化することができ、開発工数を大幅に短縮したりできるので、是非チャレンジしてみてください。

今回作成したサンプルは、githubのレポジトリーに公開していますので、参考にしてみてください。

Githubのレポジトリーをダウンロード後、npm installのコマンドで、必要なモジュールなどがインストールされ、サンプルを実行してもらうことができます。

GitHub - joey-i/gulp-csv2html-sample: gulp-ejsとcsv-parserを使用してページ生成をするサンプルファイルです。

For Every type of your business

開発やコンサルティングのご相談は、
お問い合わせフォームからお気軽に。