にっきダイアリー

はてなダイアリーからはてなblogに移動してみました。

mixiの日記ページを再利用する

先日「窓の杜」を見てたら、mixiのデータをバックアップする「撤退!mixi」っていうフリーソフトの紹介記事があった。これを使えば今までにmixi上で書いた日記やメッセージを丸ごと保存してくれるというので、さっそくソフトをダウンロードして試してみた。
mixiにはデータのエクスポート機能なんてものはないので、撤退!mixiは日記リストページのデータを解析し、そこから日記ページを一つ一つ取得し添付画像とともに保存するようになっているらしい。
出来上がったバックアップをブラウザで見てみたら、ホントにmixiの日記ページまるごと持ってきてるのだね。ページデザインのパーツなども別フォルダにダウンロードしてあって、オンラインで見るのとほとんど同じ気分で日記を閲覧できるようになってる。
閲覧するだけならこれで充分だけど、広告から何から全部入りだから日記データとして別の何かに再利用がしにくい。
ていうかオフラインでまでこんなアクセス性の悪いオリジナルの画面のまま読みたくない!
などと思いながら配布元の説明書を読むと「XML形式で保存する」という機能もあるらしい。別のblogにインポートするならXML形式のファイルを使えと言うことなんだろう。
しかし、XML形式のファイルには日記本文だけが抜き出されていてマイミクのコメントが入っていない。他人の文章を、勝手によそで公開するとまずいという考えからそうしてるんだろうな、と、思いつつ、しかしなんだかもったいない。
ので、「撤退!mixi」がダウンロードしてくれたHTMLファイルから自分で使いやすい形式のテキストファイルを作成するスクリプトを作ってみた。
Windows版のPHP5.3で動作確認。あんまりすごいことはしてないけれど pathinfoで filename 使ってるのでたぶん5.2以降用。
PHPで作ったのはHTMLをパースするのにSimple HTML DOM Parserがすごく楽だったから、という以外にあんまり理由はない。
このスクリプトのままだと出力はMT形式で一日1ファイル。mtout関数の出力部分を変えれば他の形式で書き出せると思う。

<?php
/**
 * 撤退mixiが落としてくれた日記を読み込んでMTエクスポート用ファイルを作成する
 **/
////////////////////////////////////////////////////////////////////
define("ORG_DIR", "D:/MYHOME/MIXI/tmixi/diary/"); // 撤退mixiが作成した日記データ
define("EXP_DIR", "D:/MYHOME/MIXI/mtexp/entry/"); // MT形式にしたデータの保存先
define("IMG_DIR", "D:/MYHOME/MIXI/mtexp/image/"); // 画像の保存先
define("THM_DIR", "D:/MYHOME/MIXI/mtexp/thumb/"); // サムネールの保存先
define("IMG_URL", "./image/"); // MTでの画像の保存先
define("THM_URL", "./thumb/"); // MTでのサムネールの保存先
define("AUTHOR", "nikki"); // 自分のハンドル
define("CONTAIN_PHOTOS", true); // 写真の処理もする
define("CONTAIN_COMMENTS", true); // コメントも付ける
////////////////////////////////////////////////////////////////////

// http://simplehtmldom.sourceforge.net/
require_once("simplehtmldom_1_5/simple_html_dom.php");

date_default_timezone_set("Asia/Tokyo");

$entries = Array();
$images = Array();

if ($dh = opendir(ORG_DIR)) {
  while (($file = readdir($dh)) !== false) {
    $pinfo = pathinfo($file);
    if($pinfo['extension'] == 'html'){ // HTML
      $id = $pinfo['filename'];
      $entries[$id] = dparse(ORG_DIR . $file);
    }
    if(CONTAIN_PHOTOS){
      if($pinfo['extension'] == 'jpg'){ // 画像(JPG)
        if(substr($file, -5, 1) == 's'){ // サムネ
          echo "Copying thumbnail file: $file \n";
//          copy(ORG_DIR . $file, THM_DIR . $file);
        }
        else{
          $names = explode('_', $pinfo['filename']);
          $images[$names[0]][$names[1]] = $file;
          echo "Copying image file: $file ($names[0] - $names[1])\n";
//          copy(ORG_DIR . $file, IMG_DIR . $file);
        }
      }
    }
  }
  closedir($dh);
  
  // エントリーを書きだす
  echo "Formatting\n";
  $all = "";
  ksort($entries);
  foreach($entries as $id => $entry){
    $img = (isset($images[$id])) ? $images[$id] : false;
    $extfile = EXP_DIR . $id . '.txt';
    $all .= mtout($extfile, $id, $entry, $img);
    echo "Writing file: $extfile \n";
  }
  file_put_contents(EXP_DIR . "all.txt", $all);
  
}
else{
  echo("ディレクトリがオープンできません\n");
}

// mixiの日記ページHTMLを解析して要素を抜き出す
function dparse($file){
  $item = Array();
  echo "Parsing $file ";

  // 元ファイル読み込み;
  $str = file_get_contents($file);
  $str = mb_convert_encoding($str, 'UTF-8', 'EUC-JP');
//  $str = mb_convert_encoding($str, 'Shift_JIS', 'EUC-JP');
  $h = str_get_html($str);
  
  // タイトル
  $item['title'] = $h->find('title', 0)->plaintext;
  
  // 日付
/*
body > div#page > div#bodyArea > div#bodyMainArea > div#div#bodyMainAreaMain
 div.viewDiaryBox > div.listDiaryTitle > dl
  dt < EntryTitle and other タイトルはTITLEタグから抜き出すのでここは無視
  dd < date
*/
  // YYYY年MM月DD日hh:mm > MM/DD/YYYY hh:mm:ss
  $dstr = $h->find('div.listDiaryTitle', 0)->children(0)->children(1)->plaintext;
  $item['date'] = getdatearray($dstr);

  // 画像
/*
body > div#page > div#bodyArea > div#bodyMainArea > div#div#bodyMainAreaMain
 div.viewDiaryBox > div.txtconfirmArea > div.diaryPhoto
  table > tbody > tr
   td < photo 1〜3
*/
  // にあるけど、考えたら画像ファイルが実在するかしないかで判断すりゃいいと

  // 本文
/*
body > div#page > div#bodyArea > div#bodyMainArea > div#div#bodyMainAreaMain
 div.viewDiaryBox > div.txtconfirmArea > div#diary_body
*/
  $cont = $h->find('div#diary_body', 0)->innertext;
  $item['content'] = gettextstr($cont);

  // コメント
  $comms = Array();
/*
body > div#page > div#bodyArea > div#bodyMainArea > div#div#bodyMainAreaMain
 div#diaryComment > form#delete_comment_form > div.commentListArea
  ul
   li
    p
    dl
     dt
      a < Name href=id
      span < date
     dd < text
*/
  if(CONTAIN_COMMENTS){
    $dcomm = $h->find('div.commentListArea', 0);
    if($dcomm){
      $clist = $dcomm->children(0)->children();
      foreach($clist as $li){
        $dt = $li->children(1)->children(0);
        $comm['name'] = $dt->children(0)->plaintext; // a
        $comm['date'] = getdatearray($dt->children(1)->plaintext); // span
        $comm['text'] = gettextstr($li->children(1)->children(1)->innertext); // dd
        echo(".");
        $comms[] = $comm;
      }
      $item['comm'] = $comms;
    }
  }
  echo("\n");
  return $item;
}

// MT形式で出力
function mtout($file, $id, $entry, $images){
  $str = 'AUTHOR: ' . AUTHOR . "\nTITLE: " . $entry['title'];
  $str .= "\nBASENAME: " . $entry['date'];
  $str .= "\nSTATUS: Publish\nCONVERT BREAKS: 0\nPRIMARY CATEGORY: MIXI\nDATE: ";
  $str .= mt_getdatestr($entry['date']) . "\n-----\nBODY: \n";
  if(CONTAIN_PHOTOS && $images){
    $str .= '<p class="images">';
    foreach($images as $img => $tmp){
      $imgurl = IMG_URL . $id . '_' . $img . '.jpg';
      $thmurl = THM_URL . $id . '_' . $img . 's.jpg';
      $str .= '<a href="' . $imgurl . '"><img src="' . $thmurl . '" alt=""></a> ';
    }
    $str .= "</p>\n";
  }
  $str .= mt_gettextstr($entry['content']) . "\n-----\n";
  if(CONTAIN_COMMENTS && isset($entry['comm'])){
    foreach($entry['comm'] as $comm){
      $str .= "COMMENT:\nAUTHOR: ". $comm['name'];
      $str .= "\nDATE: ". mt_getdatestr($comm['date']) . "\n";
      $str .= mt_gettextstr($comm['text']) . "\n-----\n";
    }
  }
  $str .= "--------\n";
  file_put_contents($file, $str);
  return $str;
}

// 日時フォーマット YYYY年MM月DD日 hh:mm → YYYYMMDD-hhmm
function getdatearray($dstr){
  preg_match_all("/(\d+)/", $dstr, $d);
  $ret['y'] = $d[0][0];
  $ret['m'] = $d[0][1];
  $ret['d'] = $d[0][2];
  $ret['h'] = $d[0][3];
  $ret['i'] = $d[0][4];
  return $ret;
}

// 日時フォーマット YYYY年MM月DD日 hh:mm → MM/DD/YYYY hh:mm:00
function mt_getdatestr($m){
  return $m['m']."/".$m['d']."/".$m['y']." ".$m['h'].":".$m['i'].":00";
}

// 本文フォーマット
function gettextstr($str=""){
  $str = preg_replace("/<wbr\/>/", "", $str);
  $str = preg_replace("/ *<br \/>/", "\n", $str);
  $str = preg_replace("/\n+$/", "\n", $str);
  return $str;
}
// 本文フォーマット
function mt_gettextstr($str=""){
  $str = preg_replace("/\n/", "<br>\n", $str);
  $str = preg_replace("/(<br>\n)+$/", "\n", $str);
  $str = preg_replace("/<br>\n<br>\n/", "\n</p><p>\n", $str);
  return "<p>\n". $str . "\n</p>\n";
}

とりあえず。

追記 2011/09/12

本文やコメントの改行まわりの処理がおかしかったので修正。