WordPressへCSV形式で記事を移行

4年ぶり?にサイトリニューアルしました。PHPによる独自システムからWordPressベースに変更。デザインそのものはほとんど変わってないというか新しいのが思いつかなかった。WordPressに移行したことで、スマートフォンと携帯での閲覧にも最適化できてるはずです。プラグインさまさまですな。過去の記事もこれから順次移行していきますので。あ、以前のサイトはhttp://ska.xii.jp/にしばらく残しておきます。

CSV Importerでインポート

その記事の移行には、いろいろ迷った挙句CSV Importerプラグインを利用しています。名前通り、CSV形式の記事データをWordPressにインポートしてくれるプラグイン。記事だけじゃなくて、コメントの移行にも対応してます。基本的な使い方は図解入りのページがあるので、こちらをありがたく参照。

弊社の場合、移行元の記事データが独自フォーマットなので、WordPressに標準で用意されてる他ブログからのマイグレーション用プラグイン類(CSVのほか、RSSからとか、Movable Typeからとか)がそのまま利用できない。まず、移行元のデータをそれらの汎用的なフォーマットに変換してあげないといけなかった。それらのなかではCSVがいちばん簡単なわけで(フォーマットを理解するのも、それ用の変換コードを書くのも)。

移行元データの様式

移行元の記事データのフォーマットはこんなかんじで、1記事を1テキストファイルに保存している。

<!-- [query]
title=記事タイトル
tag[0]=雑談
tag[1]=PHP
photo[0]=20120213134001.jpg
modified=2012/02/13 13:13:40 JST
-->
記事本文...

記事を読み込むときは、このファイルをfile_get_contents()して、<!-- [query]...-->で囲まれた部分をURLのクエリ部分(?以降)とみなして、各要素末の改行(LF)を&(アンパサンド)に置換したものを、parse_str()で変数に展開。手抜き実装ですねw

いっぽう、コメントの方は、タブ区切りにして1コメントごとにテキストファイルに保存。このときファイル名を親記事の保存ファイル名+連番にすることで、親記事とひもづける方法。手抜き実装ですねw

2012/02/13 13:13:40 JST(tab)コメント本文(tab)投稿者

まず、変換コードを通してCSVに変換

上記のファイル群をぜんぶローカルに保存しておいてから、PHPで以下の変換用コードを書いて、ローカルのコマンドプロンプトから実行。

<?php
ob_end_clean;
mb_internal_encoding("EUC-JP");
foreach(glob("D:/entry/2012*.txt") as $entrypath){
  $entry = file_get_contents($entrypath);
  $entry = str_replace("\r", "", $entry);
  $query = preg_match("/&lt;!--.+?\[query\](.+?)--&gt;/is", $entry, $matches)?$matches[1]:"";
  $query = str_replace("\n", "&", trim($query,"\n"));
  unset($matches, $tag);
  parse_str($query);

// タグとカテゴリ
  $tag = str_ireplace(
    array("photo","制作","瑞希","楓","水月","まほろ","七菜","まこと","なる","環"),
    array("写真","ものづくり","高瀬瑞希","水澄楓","速瀬水月","安藤まほろ","北小路七菜","木野まこと","成瀬川なる","向坂環"),$tag);
  /* 1つめのタグをカテゴリとみなす */
  $category = $tag[0];
  array_shift($tag);
  $tags = join(",", $tag);

// 本文
  $entry = str_replace("\n", "", preg_replace("/&lt;!--.+?\[query\].+?--&gt;/is", "", $entry));
  $entry = str_replace('"', "'", $entry);
  $entry = preg_replace("@&lt;!--.*?--&gt;@", "", $entry);
  /* a要素の相対パス部分を絶対パスのショートコードに変換 */
  $entry = preg_replace("@(href|src)='([^h].+?)'@", "$1='[ blog_url ]/$2'", $entry);
  /* デザイン指定のために複数のa要素をdiv要素で囲む */
  $entry = preg_replace("@(|^)((?:&lt;a .+?&gt;&lt;img .+?/&gt;&lt;/a&gt;){1,})(&lt;p&gt;|$)@", "$1&lt;div class='ska-photo'&gt;$2&lt;/div&gt;$3", $entry);
  /* 移行先のディレクトリ構成にあわせてパスを変更 */
  $entry = preg_replace("@photo/(\d{4})(\d{2})/idx/(.+?)\.jpg@", "wp-content/uploads/$1/$2/$3-150x150.jpg", $entry);
  $entry = preg_replace("@photo/(\d{4})(\d{2})/(.+?)@", "wp-content/uploads/$1/$2/$3", $entry);

// 概要と作成日時
  $description = mb_strimwidth(strip_tags($entry),0,240,"...");
  /* JST → UTC */
  $modified = date("Y-m-d H:i:s", strtotime($modified));

// コメント
  $c = dirname($entrypath)."/../comment/".basename($entrypath,  ".txt")."*.txt";
  foreach(glob($c) as $commentpath){
    $comment = file_get_contents($commentpath);
    list($comment_modified, $comment_description, $comment_author) = explode("\t", $comment);
    $comment_modified = date("Y-m-d H:i:s", strtotime($comment_modified));
    $comment_description = htmlspecialchars($comment_description, ENT_COMPAT | ENT_HTML401, "EUC-JP", false);
    $comment_array[] = join("\t", array($comment_author, $comment_description, $comment_modified));
  }
  $count = count($comment_array);
  $max_comment = $count > $max_comment? $count: $max_comment;

// 出力用バッファに格納
  $outarray = array($title, $entry, "post", $description, $category, $tags, $modified);
  foreach($comment_array as $value) $outarray = array_merge($outarray, explode("\t", $value));
  foreach($outarray as &$value) $value = '"'.$value.'"';
  unset($value, $comment_array);
  $outbuf .= join(",", $outarray)."\n";
}

// 出力用バッファにヘッダ行追加
$outarray = array(
  "csv_post_title", "csv_post_post", "csv_post_type", "csv_post_excerpt", 
  "csv_post_categories", "csv_post_tags", "csv_post_date");
for($i=1; $i<=$max_comment; $i++)
  array_push($outarray, "csv_comment_".$i."_author", "csv_comment_".$i."_content", "csv_comment_".$i."_date");

// バッファからファイル出力
foreach($outarray as &$value) $value = '"'.$value.'"';
$outbuf = join(",", $outarray)."\n".$outbuf;
file_put_contents("D:/output/import.txt",
  mb_convert_encoding($outbuf,"UTF-8","EUC-JP"));
?>

コーディング上の注意点

  /* 移行先のディレクトリ構成にあわせてパスを変更 */
  $entry = preg_replace("@photo/(\d{4})(\d{2})/idx/(.+?)\.jpg@", "wp-content/uploads/$1/$2/$3-150x150.jpg", $entry);
  $entry = preg_replace("@photo/(\d{4})(\d{2})/(.+?)@", "wp-content/uploads/$1/$2/$3", $entry);

WordPressのデフォルトでは、メディアライブラリにメディアファイルを追加するとwp-content/uploads/yyyy/mm/にメディアファイルが保存されるようになっているので、それにあわせて変更。ただし、今回の移行では画像類はメディアライブラリからではなく、直接FTPでアップロードすることにした。よってメディアファイルと記事との紐付けはいっさいされない。これのおかげで、あとでちょっと苦労する羽目になったのだけど……その話はまた別の機会に。

// 概要と作成日時
  $description = mb_strimwidth(strip_tags($entry),0,240,"...");
  /* JST → UTC */
  $modified = date("Y-m-d H:i:s", strtotime($modified));

投稿日時のデータを含めるとき、データの書式そのものはPHP(strtotime()関数)で認識できる書き方ならなんでもいい(はず)のですが、JSTをつけたローカルタイムを明示する書式だと、実際にインポートしたときに9時間遅れの時刻で認識されてしまった。明示しなければ意図した日時で表示されるので、date()関数でJSTの部分を取り除いた書式に変換。なんかUTC扱いになってるっぽくて気持ち悪いのですが。WordPressの内部的にはタイムゾーンがUTCになってるらしいので、それと関係があるんかしら。

// コメント
  $c = dirname($entrypath)."/../comment/".basename($entrypath, ".txt")."*.txt";
  foreach(glob($c) as $commentpath){
    $comment = file_get_contents($commentpath);
    list($comment_modified, $comment_description, $comment_author) = explode("\t", $comment);
    $comment_modified = date("Y-m-d H:i:s", strtotime($comment_modified));
    $comment_description = htmlspecialchars($comment_description, ENT_COMPAT | ENT_HTML401, "EUC-JP", false);
    $comment_array[] = join("\t", array($comment_author, $comment_description, $comment_modified));
  }
  $count = count($comment_array);
  $max_comment = $count > $max_comment? $count: $max_comment;
for($i=1; $i<=$max_comment; $i++)
  array_push($outarray, "csv_comment_".$i."_author", "csv_comment_".$i."_content", "csv_comment_".$i."_date");

コメントは、まずヘッダ行に「csv_comment_1_author」「csv_comment_1_content」「csv_comment_1_date」「csv_comment_2_author」…というフィールドをコメントの最大数ぶん追加して、コメントが存在する各記事のレコードにデータを「コメント1の投稿者」「コメント1の本文」「コメント1の投稿日時」「コメント2の投稿者」…という感じでコメントの数だけ追加する。コーディング上は「コメントの最大数」をカウントしてから、あとでarray_push()でヘッダ行を格納する配列にフィールドを必要分追加する形にしている。

できたCSVファイルをアップロード

上記のコードを実行するとCSV形式のテキストファイル(import.txt)が吐き出されるので、これをCSV Importer経由でアップロードする。カテゴリやタグ、HTMLタグなんかは個別に記事編集で修正・追加。記事に貼ってた画像は別途、FTPでアップロードする。

関連(してるかもしれない)記事たち

wptouch-mobile
アップデートに負けないWPtouchカスタマイズ
カスタマイズした内容がアップデート適用で消える WPtouch Moblie Plugin(以下WPtouch) は自分...
コメント処理を軽く
コメントの投稿処理(投稿ボタンをクリックしてから画面が更新されるまでの処理時間)を軽くしてみました。 右側の「COMME...
IPアドレスでコメントブロック
中国からの政治的な中傷コメントがたびたび見かけられるので、ちょっと対策。中国のホストの大半が逆引きできない(IPアドレス...