2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

WebアプリのDBからjsonデータを取得⇒Wordpress取り込み用にデータ変換⇒一括で記事登録したのでその時の様子を書く

Posted at

今回やったこと

やったことサマリ

もともと運営していた全国の絶景を集めたWebアプリがあって、そこのデータベースから情報を抜いてきて、いい感じに編集して、Wordpressの記事を一括登録(300件くらい)したよ!

誰か同じことを考えている方に役に立つかもしれないからQiitaにしておいたよ!

こんな感じです。

背景

私はもともと、絶景マップというWebアプリを持っていました。

これは、旅好きの私が実際にその場所を訪れて「ここは絶景やな!」と思ったポイントを登録していき、地図一覧や写真一覧でオススメスポットが見れるよ、という主に私向けの弱小アプリでした。こんな感じです↓
zekkei-map1.PNG
なんですけど、まあこんな弱小アプリでは集客できるわけでもなく(自分向けですしね)、大したPVはありませんでした。

このアプリ自体の行く末としては、今のまま弱小でPV無くてもよかったんですが、実はこの度バイクツーリング特化のブログを作成しまして、こちらでバイクツーリングにオススメな絶景スポットを紹介し始めました。

既にせっかくWebアプリの方にデータが溜まっているのに、また新たに一からWordpressでブログ記事を書いていくのは辛すぎる(Webアプリの方に300カ所程度の場所情報が詰まっているので...)ので、今回Webアプリのデータベースに溜まっている情報をもとに、Wordpressの記事(ドラフトレベル)を一括で作成して楽してやろう!と思い立ち、2時間程で変換ツール作成及びWordpressへのインポートが正常終了したことを確認したので、Qiitaに残してみようかなと思いました。そんな感じです。

DBからjsonデータ取得⇒変換⇒Wordpressに一括記事登録の全貌を書いていく

1. DBからjsonデータ取得(5分)

もともとのWebアプリは、レンタルサーバ(ロリポップ)のphpMyAdminにログインし、GUIからダウンロードしてきました。継続的にデータを連携する必要があれば、ここもphpにしておくべきですが、今回はこの1回きりですので、普通にダウンロードします。

まずは「エクスポート」をクリック
lolipop-1.png

エクスポートタブで、「フォーマット」をJSONにして「実行」押下でOKです。
lolipop-2.png

ここで1点注意で、phpMyAdminからJSONとしてダウンロードすると、先頭にコメントが付いてきます。これをそのままjavascriptとかでimportするとエラーになるので(JSONはコメントNGなんで)、事前に消しておきます。
jsondata-1.PNG
↑こんな感じで色々コメントが付いていました。消します。

これでJSONデータの準備は完了です。とっても簡単です!

一応、テーブルの構造を書いておく(なんの参考にもならないけど)

一応、今ダウンロードしたDBのテーブルの構造について書いておきます。正直これは何のタメにもならないですが、話がちょっと分かりやすくなるかなと。(後のソース見た時とか)

今回、Webアプリ入力は色々とテーブルがあれど、Wordpressの記事にするために使用するのはわずか2テーブルでして、それが下記のとおり。
lolipop3.PNG

MHM_M_POINT_DATA(絶景の場所情報メインを司るテーブル)

物理名 意味
id ID
name 場所の名称
lat 緯度
lng 経度
prefecture 都道府県名
zip_no 郵便番号
address 住所
caption その場所に対するコメント(長文)
favorite お気に入り度(今回不使用)
accessibility アクセスのかんたんさ(今回不使用)
crowdness_ave 混雑度(今回不使用)
place_type 場所の種別(高原とか建築物とかそういう感じ)(今回不使用)
gmap_by_latlng 不使用(今回不使用)
image_url_top サムネイル画像のURL(今回不使用)
is_private プレイべート扱いにする時に使うフラグ(今回不使用)
create_datetime 作成日(今回不使用)
update_datetime 更新日(今回不使用)

MHM_M_PICTURES(絶景の場所情報に紐づく画像を保持しておくテーブル)

物理名 意味
id ID
seq 連番
image_url 画像のURL
image_url_thumb 画像のサムネイルURL
comment 画像へのコメント
visit_date 撮影日
month 撮影した月
timing_of_month 上旬/中旬/下旬の区分
author 撮影者
recomend 推奨度
is_private プライベート画像の場合ON
create_datetime 作成日
update_datetime 更新日

2. 変換(Javascriptで変換ツール作成)(2時間)

さて、データソースを無事取得できたので、今度はそのデータをWordpressに取り込める形に変換していきます。

今回wordpressへ一括記事取り込みに使用したプラグインは「Really Simple CSV Importer」で、このプラグインでの記事取り込みは、下記のようなcsvを作れば良いとのこと。

"post_id","post_name","post_author","post_date","post_type","post_status","post_title","post_content","post_category","post_tags","custom_field"

物理名 意味
post_id 記事のID
post_name 記事名前(スラッグ?)
post_author 投稿者
post_date 公開日(draftだったら要らないと思う)
post_type 投稿タイプ(投稿なら"post"でok)
post_status "draft"か"publish"をセット
post_title タイトル
post_content 本文
post_category カテゴリ名
post_tags タグ
custom_field カスタムフィールド?用途不明...

というわけで、この手順2. のターゲットとしては、この形式を満たすcsvを作成することです。

これには様々すぎる方法があるのでアレですが、今回は私の好きなjsでやっていきます。

作った変換ツールのソース

早速作ったソースがこちら↓ htmlファイル1つとjsファイル1つになっています

html

index.html
<html>

<head>

    <meta charset="UTF-8">

    <title>DBからダウンロードしたjsonデータからWordpress取り込み用のcsvを作る</title>

    <style>
    .button-wrapper{
        text-align: center;
    }
    #convert-exec {
        width: 80%;
        text-align: center;
        display: inline-block;
        padding: 0.5em 1em;
        text-decoration: none;
        background: #668ad8;
        color: #FFF;
        border-bottom: solid 4px #627295;
        border-radius: 3px;
    }
    #convert-exec:active {
        -webkit-transform: translateY(4px);
        transform: translateY(4px);
        border-bottom: none;
    }
    </style>

    <script src="https://code.jquery.com/jquery-3.4.1.js" integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU=" crossorigin="anonymous"></script>

    <!-- 変換メインロジック -->
    <script src="js/JsonWpcsvConverter.js"></script>

</head>

<body>
    <div class="button-wrapper">
        <a id="convert-exec" href="#">実行</a>
    </div>

    <script> 

    $(document).ready(function(){

        // 実行ボタンにイベントをアタッチ
        $("#convert-exec").on("click", async function(){

            const jwc = new JsonWpcsvConverter()

            // csvデータに変換する
            const output = await jwc.convert()

            // csvをダウンロードする
            downloadCsv(output)
        })

        /**
         * csv文字列からcsvを生成してダウンロードする
         * @param {Array<Object>} arr 
         */
        function downloadCsv(csvStr){
            const bom     = new Uint8Array([0xEF, 0xBB, 0xBF])
            const blob    = new Blob([bom, csvStr], {'type': 'text/csv'})
            const url     = window.URL || window.webkitURL
            const blobURL = url.createObjectURL(blob)

            let a      = document.createElement('a')
            a.download = decodeURI("mycsv.csv")
            a.href     = blobURL
            a.type     = 'text/csv'

            a.click()
        }
    })
        
    </script>

</body>

</html>

htmlの方はホントシンプルで、ボタン1つ、ボタンへのイベントリスナセット、csvダウンロードロジックのみ記述してます。

js

メインの変換ロジックを担当しているのが下記↓

JsonWpcsvConverter.js
class JsonWpcsvConverter{

    /**
     * 変換ロジック実行
     */
    async convert(){

        // jsonを取得(テーブル1)
        const pointJsonData = await $.ajax({
            url: "data/MHM_M_POINT_DATA.json",
            dataType: "json",
        })

        // jsonを取得(テーブル2)
        const pictureJsonData = await $.ajax({
            url: "data/MHM_M_PICTURES.json",
            dataType: "json",
        })

        // 配列を使いやすいようにmapに変換しておく
        const pointData = this._processArray2Obj(pointJsonData, "id") // ← こっち不要だったかも
        const pictureData = this._processArray2ObjWithArray(pictureJsonData, "id")

        // wordpress csv取り込み用カラム定義をもつobjectの配列に変換する
        const res = Object.keys(pointData)
            .map(key=> 
                this._convOriginalObj2WpObj(pointData[key], pictureData[key])
            )


        // csvデータに変換する
        const output = this._getValuesFromObject(res)

        return output
    }
    
    /**
     * オブジェクトからcsv文字列を生成する
     * @param {Array<Object>} arr 
     */
    _getValuesFromObject(arr){

        // ヘッダのカラム情報を生成
        let res = JsonWpcsvConverter.HEADER_DEF.map(s=> `"${s}"`).join(",") + "\r\n"

        // body部を生成
        res += arr
            .map(v=> {

                const res = JsonWpcsvConverter.HEADER_DEF
                    .map(s=> `"${v[s]}"`)
                    .join(",")

                return res
            })
            .join("\r\n")

        return res

    }

    /**
     * keyPropNameをキーとするobjectに変換
     * @param {Array<Object>} data 
     * @param {string} keyPropName 
     */
    _processArray2Obj(data, keyPropName){

        const res = data
            .reduce((p, c)=> {
                return {
                    ...p,
                    [c[keyPropName]]: c
                }
            }, {})

        return res
    }

    /**
     * keyPropNameをキーとするobjectに変換(1キーあたり複数要素の紐づけ)
     * @param {Array<Object>} data 
     * @param {string} keyPropName 
     */
    _processArray2ObjWithArray(data, keyPropName){
        const res = data
            .reduce((p, c)=> {

                const current = p[c[keyPropName]]
                const newVal = !!current ? [...current, c] : [c]

                return {
                    ...p,
                    [c[keyPropName]]: newVal
                }
            }, {})

        return res
    }

    /**
     * jsonデータをwordpress取り込み向けのobjectに変換
     * @param {Object} pointData 
     * @param {Object} pictureData 
     */
    _convOriginalObj2WpObj(pointData, pictureData){

        const pd = pointData

        return {
            post_id: "",
            post_name: pd.name,
            post_author: "hidetaso",
            post_date: "",
            post_type: "post",
            post_status: "draft",
            post_title: `【${pd.prefecture}の絶景】${pd.name}`,
            post_content: this._getPostContent(pd, pictureData),
            post_category: "",
            post_tags: "",
            custom_field: "",

        }
        /*
        ↓こんな感じのデータが入ってる↓
        accessibility: "7"
        address: "山梨県南都留郡富士河口湖町小立"
        caption: "山梨県の河口湖南岸に位置する公園です。/brみたいなコメントがね!"
        create_datetime: "2017-06-23 00:19:48"
        crowdness_ave: "3"
        favorite: "7"
        gmap_by_latlng: ""
        id: 2017042301
        image_url_top: "http://korega-watashino-img/test.png"
        is_private: ""
        lat: "35.5120911"
        lng: "138.7528314"
        name: "八木崎公園"
        place_type: "P"
        prefecture: "山梨"
        update_datetime: "2017-06-23 00:19:48"
        zip_no: "401-0302"
        */
    }

    /**
     * 位置objectと一致する画像情報をもとに、wordpress記事コンテンツ部を生成する
     * @param {Object} pd 
     * @param {Array<Object>} pict 
     */
    _getPostContent(pd, pict){

        // templeteから簡単な情報を一旦置換
        let postContent = JsonWpcsvConverter.TEMPLATE
            .replace(/__REPLACED__/g, pd.name)
            .replace(/__ADDRESS__/, `<p>住所:${!!pd.zip_no ? "" + pd.zip_no : ""} ${pd.address}</p>`)
            .replace(/__MAP_LINK__/, `<a href='https://www.google.com/maps/@${pd.lat},${pd.lng},15z?hl=ja'>GoogleMapで開く</a>`)

        
        // 元captionのデータを加工
        let mainContents = this._getMainComment(pd.caption)

        // 画像情報がある場合、それをwordpress記事コンテンツ部に付加
        if(!!pict){

            const mainContentsStr = pict.map((v, i)=> {

                const imgUrl = (v.image_url || "").replace("http://tasokori.net/webapps/zekkei-map/pictures/", "https://love-moto-touring.info/webapps/zekkei-map/pictures/")

                return `
[caption id="" align="alignnone" width="1024"]
<a href="${imgUrl}">
    <img class="size-large" src="${imgUrl}" alt="${pd.name}-${i}" width="1024" height="768" />
</a>
${v.comment} ${pd.name}-${v.visit_date}撮影
[/caption]
    `
            })

            mainContents += mainContentsStr.join("<br />")
        }

        // メイン部のコンテンツの書き換え、エスケープすべき文字を無効化
        return postContent.replace(/__MAIN_CONTENTS__/g, mainContents).replace(/"/g, "'").replace(/\n/g, " ")

        /*
        author: "hide"
        comment: ""
        create_datetime: "2017-06-23 00:20:10"
        id: 2013061601
        image_url: "http://watashino-img/mytest-main.png"
        image_url_thumb: "http://watashino-img/mytest-main.png"
        is_private: "0"
        month: ""
        recomend: ""
        seq: 1
        timing_of_month: ""
        update_datetime: "2017-06-23 00:20:10"
        visit_date: "2015-05-31"
        */
    }

    /**
     * 元データcaption部をいい感じに整形
     * @param {string} comment 
     */
    _getMainComment(comment){

        if(!comment) return ""

        const res = comment
            .split("/br")
            .map(s=> {
                return "<p>" + s + "</p>"
            })
            .join("")

        return res

    }

    /**
     * wordpress取り込み用カラム定義
     */
    static HEADER_DEF = ["post_id", "post_name", "post_author", "post_date", "post_type", "post_status", "post_title", "post_content", "post_category", "post_tags", "custom_field"]

    /**
     * 本文テンプレート
     */
    static TEMPLATE = `
    <h2>__REPLACED__とは</h2>
    &nbsp;
    <h2>__REPLACED__の基本情報</h2>
    &nbsp;
    <h3>__REPLACED__の場所(地図)</h3>
    <p>__REPLACED__の場所は下記の通りです。</p>
    &nbsp;
    __ADDRESS__
    &nbsp;
    __MAP_LINK__
    &nbsp;
    <h2>__REPLACED__はこんな場所です</h2>
    &nbsp;
    __MAIN_CONTENTS__
    &nbsp;
    <h2>おわりに</h2>
    &nbsp;
    `

}

特段解説は要らないと思いますが、基本的にはTEMPLATEという実際の投稿記事のテンプレート文字列を用意しておいて、それのバインド文字列部分(HOGEHOGE)をreplaceで置換していくという感じです。

また、1. でDBのテーブルからjsonデータを抽出してきていますが、それも一番最初に

$.ajax({
  url: "data/MHM_M_POINT_DATA.json",
  dataType: "json",
})

で取得してきてます。

また、途中もいい感じで絶景の位置に関する情報と、画像情報を紐づけて、Wordpressの投稿データを構築しています。

変換ツールを実行してcsvを取得する

上記変換ツールを実際に実行して、wordpress取り込み用のcsvを取得します。

tool-import-data.PNG

結果のcsvはこちら。

tool-import-data2.png

ようやくここまで来ました!

3. Wordpressに一括記事登録(5分)

あとは簡単です。wordpressの「Really Simple CSV Importer」を使用して、wordpressに一括登録します。

  1. プラグインで「Really Simple CSV Importer」を検索してインストール & 有効化
  2. wordpressメニューのツール⇒インポート
  3. ファイルを選択で、先ほど作成したcsvを選択
  4. 「ファイルをアップロードしてインポート」で取り込み実行

です。

取り込みは今回200件以上おこないましたが、割と一瞬で終わりました。

結果がこちら。

wp-import2.PNG

下書き208とありますが、これが全て今回一括記事登録したデータです。

実際に、1つ1つの記事を見ていくと、ちゃんと記事が作成されていました!感動!!

wp-import3.PNG

今回は全て下書き(draft)にしています。

というのも、あくまでブログ記事を書くための土台にしようという案ですので。このドラフトをベースに、さらに必要情報を追記していって、ユーザに魅力的な情報を提供していこうと思います!

おわりに

今回は、Webアプリのデータを溜めていたDBのテーブル情報を引っこ抜いて、変換ツールを噛まして変換、Wordpressに一括記事登録したよ!という内容でした。

何か真新しいことがあるわけではないので恐縮ですが...

まあしかし、こんな感じで「とあるデータ⇒いい感じに加工⇒別システムに投入」とかっていうことはまあよくあると思うので、何かの参考になれば幸いです。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?