前回までのあらすじ】



王に命じられ森林奥深くに眠る財宝を持ち帰るべく旅に出た青年を険しい山が阻む。そのとき彼が手にしたのは大きな気球だった。気球は森林奥深くへと誘うように吹く冷たく強い風にのり無事に山を越えた。山からほど近い池のそばに着陸した彼がそのとき目にしたものとは...







あ、もういいですか。そうですか。

Voxからのデータ保存

結局、前回書いた手順でデータの保存には成功しました。

  1. 年毎アーカイヴを取得/スクレイピング → 件数からそれぞれのページ数割り出し
  2. 年毎各ページの情報をフィードで取得
  3. 各種情報を整理してDBへ(またはインポート用データへ変換)

1番目ではどうしてもスクレイピングしないといけませんが、5年書いていても5回で済むのでそれほど大きな負担ではないかと。で、前回書いたとおり全文配信RSSを使えばエントリ自体のスクレイピングはせずに済むのでその後は楽です(あ、もし全文配信RSSが404返すまでループさせるのであれば年毎アーカイヴのスクレイピングも必要ありませんね)。

でもって記事の方は全部で120件ありましたが、RSSにアクセスした回数は全部で17回なのでそれほど問題になるような数ではないかなぁと。

最終的にはSmarty使ってテンプレを読み込んで、MovableType形式で吐き出して保存しました。動画や写真の貼り付けなど、Voxならではの機能が本文中に独創的に配置されているのでそれら全てを置換ないしは排除した純粋な本文を取得するのは困難ですが、とりあえず鬱陶しくない程度には保存できているかなと。

このためにでっち上げたPHPプログラムはこちら



実際にインポートしてみる(Blogger編)

今回、吐き出したファイルはMovableType用なので、MovableType形式でのインポートに対応しているサービス(はてなダイアリーなど)ならそのままインポートできるはずです。表題にしているGoogleのブログサービス「Blogger」の場合、MovableType形式に対応していないのでファイルを変換してやる必要があります。

Blogger形式への変換はこちらのサイトで行うことが出来ます。

MovableType2Blogger conversion utility

「1MBまでのファイルしかダウンロードできないぜ」と書いてるので大量にエントリを書かれている方は無理かも知れませんが...とりあえず今回作成したファイルを投げてみたところ問題なく変換できました。それを、Bloggerの管理画面内、「設定 » ブログのインポート」からインポートしてやれば20秒くらいで完了します。速い。

出来たのはこちら。

Voxからのインポートテスト

Bloggerはニコニコ動画の外部プレイヤーに対応していないので、仕方なく放置。Flickrとかその辺も同じく。本気で移行するなら気になったときにその辺いじるとかかなぁ。


一応、元ネタはこちら。

nobodyplace with Vox - Vox

こうして見返しちゃうと、確かにVoxは賑やかで色んな機能が付いてるけどいまいち要らない機能が多いような気がするね...




ソース

最後にでっち上げたプログラムとMovableType形式テンプレートを晒しておきます。

抜粋なのでいきなりシングルトンとかなんで?って感じだし、色々アレですが一応、成果物の1つということで。MovableType形式テンプレートの方は、ある程度決めうちしたテンプレになってます。


VoxからデータをサルベージするためのPHPプログラム

<?php
VoxSalvage::salvage();

class VoxSalvage
{
  const USER = 'nobodyplace';

  /**
   * @var HTTP_Client
   */
  private static $client;
  /**
   * @var Smarty
   */
  private static $smarty;
  /**
   * @var integer
   */
  private static $startYear;

  private static function initialize()
  {
    require_once 'HTTP/Client.php';
    require_once 'Smarty.class.php';
    if(is_null(self::$client) === true)
      self::$client = new HTTP_Client();
    self::$smarty = new Smarty();
    self::$smarty->template_dir = './templates';
    self::$smarty->compile_dir  = './templates_c';
    self::$startYear = 2006;
  }

  /**
   * salvage
   *
   * @return bool
   */
  public static function salvage()
  {
    self::initialize();
    self::$smarty->assign(array(
      'user'    => self::USER,
      'entries' => self::getEntries()
    ));
    $fh = fopen('./' . self::USER . '_mt.txt', 'w');
    fwrite($fh, self::$smarty->fetch('import_mt.tpl'));
    fclose($fh);
    return true;
  }

  /**
   * 全エントリを取得
   *
   * @return array
   */
  private static function getEntries()
  {
    $entries = array();
    for($i = self::$startYear;$i <= date('Y');$i++):
      $entries = array_merge($entries, self::getEntriesByYear($i));
    endfor;
    return $entries;
  }
  /**
   * 年別アーカイヴをParse
   *
   * @param integer $year
   * @return array
   */
  private static function getEntriesByYear($year)
  {
    var_dump($year);
    $entries = array();
    for($i = 1;$i <= self::getPageCountByYear($year);$i++):
      $rss = 'http://' . self::USER . '.vox.com/library/posts/' . $year . '/page/' . $i . '/rss-full.xml';
      $xml = simplexml_load_file($rss);
      foreach($xml->channel->item as $item):
      //bodyから「コメントへのリンク」「Vox招待へのリンク」を削除
        $body = preg_replace("/<p style=\"clear:both;\">.*<\/p>/s", '', (string) $item->description, -1, $count);
        $entries[] = array(
          'title'   => (string) $item->title,
          'pubTime' => strtotime((string) $item->pubDate),
          'body'    => $body
        );
      endforeach;
    endfor;
    return $entries;
  }
  /**
   * 年別ページ数を取得
   *
   * @param integer $year
   * @return integer
   */
  private static function getPageCountByYear($year)
  {
    $url = 'http://' . self::USER . '.vox.com/library/posts/' . $year . '/';
    self::$client->get($url);
    $response = self::$client->currentResponse();
    if($response['code'] != 200)
      return (int) 0;
    $body = $response['body'];

  //タイトルから件数を取得
    preg_match('/<title>(.+?)<\/title>/', $body, $matches);
    $title = $matches[1];
    if(preg_match('/^(\d+)/', $title, $matches) <= 0):
      return (int) 0;
    else:
      $entryCount = $matches[1];
    endif;
    return (int) ceil($entryCount / 10);
  }
}
?>


MovableType用Smartyテンプレート

{foreach from=$entries item=entry}
AUTHOR: {$user}
TITLE: {$entry.title|escape}
BASENAME: post_{$entry.pubTime}
STATUS: Publish
ALLOW COMMENTS: 1
CONVERT BREAKS: 1
ALLOW PINGS: 1
DATE: {$entry.pubTime|date_format:"%m/%d/%Y %I:%M:%S %p"}