今回やったこと
やったことサマリ
もともと運営していた全国の絶景を集めたWebアプリがあって、そこのデータベースから情報を抜いてきて、いい感じに編集して、Wordpressの記事を一括登録(300件くらい)したよ!
誰か同じことを考えている方に役に立つかもしれないからQiitaにしておいたよ!
こんな感じです。
背景
私はもともと、絶景マップというWebアプリを持っていました。
これは、旅好きの私が実際にその場所を訪れて「ここは絶景やな!」と思ったポイントを登録していき、地図一覧や写真一覧でオススメスポットが見れるよ、という主に私向けの弱小アプリでした。こんな感じです↓
なんですけど、まあこんな弱小アプリでは集客できるわけでもなく(自分向けですしね)、大したPVはありませんでした。
このアプリ自体の行く末としては、今のまま弱小でPV無くてもよかったんですが、実はこの度バイクツーリング特化のブログを作成しまして、こちらでバイクツーリングにオススメな絶景スポットを紹介し始めました。
既にせっかくWebアプリの方にデータが溜まっているのに、また新たに一からWordpressでブログ記事を書いていくのは辛すぎる(Webアプリの方に300カ所程度の場所情報が詰まっているので...)ので、今回Webアプリのデータベースに溜まっている情報をもとに、Wordpressの記事(ドラフトレベル)を一括で作成して楽してやろう!と思い立ち、2時間程で変換ツール作成及びWordpressへのインポートが正常終了したことを確認したので、Qiitaに残してみようかなと思いました。そんな感じです。
DBからjsonデータ取得⇒変換⇒Wordpressに一括記事登録の全貌を書いていく
1. DBからjsonデータ取得(5分)
もともとのWebアプリは、レンタルサーバ(ロリポップ)のphpMyAdminにログインし、GUIからダウンロードしてきました。継続的にデータを連携する必要があれば、ここもphpにしておくべきですが、今回はこの1回きりですので、普通にダウンロードします。
エクスポートタブで、「フォーマット」をJSONにして「実行」押下でOKです。
ここで1点注意で、phpMyAdminからJSONとしてダウンロードすると、先頭にコメントが付いてきます。これをそのままjavascriptとかでimportするとエラーになるので(JSONはコメントNGなんで)、事前に消しておきます。
↑こんな感じで色々コメントが付いていました。消します。
これでJSONデータの準備は完了です。とっても簡単です!
一応、テーブルの構造を書いておく(なんの参考にもならないけど)
一応、今ダウンロードしたDBのテーブルの構造について書いておきます。正直これは何のタメにもならないですが、話がちょっと分かりやすくなるかなと。(後のソース見た時とか)
今回、Webアプリ入力は色々とテーブルがあれど、Wordpressの記事にするために使用するのはわずか2テーブルでして、それが下記のとおり。
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
<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
メインの変換ロジックを担当しているのが下記↓
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>
<h2>__REPLACED__の基本情報</h2>
<h3>__REPLACED__の場所(地図)</h3>
<p>__REPLACED__の場所は下記の通りです。</p>
__ADDRESS__
__MAP_LINK__
<h2>__REPLACED__はこんな場所です</h2>
__MAIN_CONTENTS__
<h2>おわりに</h2>
`
}
特段解説は要らないと思いますが、基本的にはTEMPLATEという実際の投稿記事のテンプレート文字列を用意しておいて、それのバインド文字列部分(HOGEHOGE)をreplaceで置換していくという感じです。
また、1. でDBのテーブルからjsonデータを抽出してきていますが、それも一番最初に
$.ajax({
url: "data/MHM_M_POINT_DATA.json",
dataType: "json",
})
で取得してきてます。
また、途中もいい感じで絶景の位置に関する情報と、画像情報を紐づけて、Wordpressの投稿データを構築しています。
変換ツールを実行してcsvを取得する
上記変換ツールを実際に実行して、wordpress取り込み用のcsvを取得します。
結果のcsvはこちら。
ようやくここまで来ました!
3. Wordpressに一括記事登録(5分)
あとは簡単です。wordpressの「Really Simple CSV Importer」を使用して、wordpressに一括登録します。
- プラグインで「Really Simple CSV Importer」を検索してインストール & 有効化
- wordpressメニューのツール⇒インポート
- ファイルを選択で、先ほど作成したcsvを選択
- 「ファイルをアップロードしてインポート」で取り込み実行
です。
取り込みは今回200件以上おこないましたが、割と一瞬で終わりました。
結果がこちら。
下書き208とありますが、これが全て今回一括記事登録したデータです。
実際に、1つ1つの記事を見ていくと、ちゃんと記事が作成されていました!感動!!
今回は全て下書き(draft)にしています。
というのも、あくまでブログ記事を書くための土台にしようという案ですので。このドラフトをベースに、さらに必要情報を追記していって、ユーザに魅力的な情報を提供していこうと思います!
おわりに
今回は、Webアプリのデータを溜めていたDBのテーブル情報を引っこ抜いて、変換ツールを噛まして変換、Wordpressに一括記事登録したよ!という内容でした。
何か真新しいことがあるわけではないので恐縮ですが...
まあしかし、こんな感じで「とあるデータ⇒いい感じに加工⇒別システムに投入」とかっていうことはまあよくあると思うので、何かの参考になれば幸いです。