mixi_export
今更ながらmixiプレミアムに課金してるの無意味っぽいから解約するかなあって思い立ったんですが、昔書いた日記は容量オーバーで消される前に退避しておきたいですね。
以前退会祭になった時分に日記をエクスポートするツールがいろいろあったと思ったのですが、その後の仕様変更(これが多いのでツールがなかなか出ない)で使えなくなってしまったものばかり。
かろうじてmixi_exportはまだ動作するっぽい…。
Proxyでhttps不可だけどサービス一覧とかからログイン可能。 てかCookie取得するだけなら管理者ツールからコピペとかでいい気もします。
しかし…
しかし動いたけど2010年以前の日記が取得できない…これもmixiの仕様変更が悪いのかたまたまそこでメモリ不足になってしまうのか…。ひとまず2010年以前の日記は諦める。
あと画像も回収してくれない。URLを絶対パスに置き換える処理だけしてくれて、保存されたHTML開けばサーバーに残ってる画像は表示されるので、退会する前にPDF化とかしておくのが一般的みたいです。
うーん。画像。。。ChromeのSavePageWEとか使えばページ単位では保存できるが、面倒。 SwiftProxyみたいなProxyでアクセスしたURL全部保存みたいなツール最近はないのかな(これもhttpsだとダメか)。
じゃあ自分で作るしかないかなあ…
というわけで作りました。単純にURLリストアップしてダウンロードすればいい気もしたけど、SavePageWE同様にIMGタグで埋め込まれてる画像をDataURIに置き換えることにします。
本当は全ての画像をDataURIに置き換えるのがベストなんだけど、そうすると同じファイルを読みに行かないようにキャッシュを導入しないといけなくなって面倒なので日記のフォトの画像だけにします。
本当はAタグのリンク先の元画像を拾うほうが高品質なのだけど、雰囲気がわかればいいのでサムネイルだけ拾うことにします。 元画像はそれこそDataURIで埋め込むには大きすぎるので普通にダウンロードするコード書いたほうがいいかもしれないし。
なお写真が今のサーバー(photoserviceなんちゃら)に移動したのは最近の仕様変更なので、昔作ったファイルに対しては動作しない可能性が高いです。
成果物
<?php /*** * mixi_exportの生成したHTML内のフォト(imgタグの一部)をDataURIに変換 * * 2021.04.10 fa */ // mixi_exportで出力されたログHTMLのあるフォルダ $path = './log2/'; $mime_types = array('png'=>'image/png', 'jpg'=>'image/jpeg', 'gif'=>'image/gif'); $context = stream_context_create( array( 'http' => array( "protocol_version" => "1.1", 'follow_location' => 1, 'max_redirects' => 5, 'timeout' => 60.0, 'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36', ), 'ssl' => array( 'verify_peer' => false, 'verify_peer_name' => false, ), ) ); if (!is_dir($path)){ _dlog('log path '.$path.' is not found',0); exit; } // サブディレクトリ走査 $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $path, FilesystemIterator::SKIP_DOTS |FilesystemIterator::KEY_AS_PATHNAME |FilesystemIterator::CURRENT_AS_FILEINFO ), RecursiveIteratorIterator::LEAVES_ONLY ); // htmlファイルだけチェック $iterator = new RegexIterator($iterator, "/\.html$/", RecursiveRegexIterator::MATCH); $i = 1; foreach($iterator as $pathname => $f){ $name = $f->getFilename(); $dir = $f->getPath(); $path = $f->getPathname(); $fullpath = realpath($path); // 目次ファイルはスキップ if ($name == 'index.html'){ continue; } // 処理済ファイルスキップ用 //if (preg_match("/".preg_quote(DIRECTORY_SEPARATOR,'/')."(2010|2011)/",$path)){ // _dlog("skip ".$path); // continue; //} _dlog($path); // 念のため2000マイクロ秒待つ usleep(2000); // ローカルのHTML読込 $contents = file_get_contents($path); // 置換処理 $contents2 = preg_replace_callback( '/\<img ([^\>]*)src="(https?:\/\/(?:photoservice|classic-imagecluster)[^"]+\.(' . implode('|', array_keys($mime_types)) . '))"/im', function ($matches) use ($mime_types,$context) { $url = trim($matches[2], '\'\"'); _dlog($url); // サーバアクセス前に2000マイクロ秒待つ usleep(2000); // リモートファイル取得 $http_response_header = null; $image = file_get_contents($url,false,$context); if ($image){ // DataURIに置換 return '<img '.$matches[1].'src="data:' . $mime_types[strtolower($matches[3])] . ';base64,' . base64_encode($image).'"'; }elseif (!$http_response_header){ // サーバの反応がなかった時、1秒置いて一度だけ再試行 _dlog("retry",0); sleep(1); $image = file_get_contents($url,false,$context); if ($image) { return '<img '.$matches[1].'src="data:' . $mime_types[strtolower($matches[3])] . ';base64,' . base64_encode($image).'"'; } } _dlog("ERROR! ".$url,2); return $matches[0]; }, $contents ); // 置き換えたHTMLだけ保存 if (strlen($contents) != strlen($contents2)){ $i++; // 元ファイルをリネームしてから保存 rename($path,$path.'.bak'); file_put_contents($path,$contents2); // 10ファイル処理毎に少し待つ if (!($i % 10)){ _dlog('wait'); sleep(20); } } } function _dlog($msg,$lv=0){ echo $msg.PHP_EOL; if ($lv) { file_put_contents('./error.log',date('Y/m/d-H:i:s ').$msg.PHP_EOL,FILE_APPEND); } //TODO エラー多い時は中断する処理 }
何も考えずに手書きしたので使われてないコードやクォートのバラつきがありますが手仕事なんてこんなものだよね(汗
追記
途中で落ちた件は仕様変更対応とかではなかった。追記