分割されたXMLファイルの前後を調節して、整合性をとるvbsを作成してくれる方、お願いします。

300~500pt 完全回答をお願いします。補足なども読んでください。質問はコメント欄にお願いします。

アドレス帳のような、どでかいXMLファイルがあります。後で処理するには重すぎるので、ファイル分割ツールを使って適当な大きさに分割しましたが、xml構造を無視して分割してしまうので、整合性をとれる形にしたいと思っています。
次のようなxmlファイルです

addressbooktokyo.xmlの中身
<xml version="1.0" encoding="utf-8"?>
<myaddress version="1.0">
<header>
<各種ヘッダー情報、たとえば地域などファイルによって微妙に違う>
</header>
<body>
<person>
<name>山田さん</name>
<phone>0123456789</phone>
</person>
膨大な繰り返し・・・
</body>
</myaddress>

質問などはコメントにお願いします。

回答の条件
  • 1人1回まで
  • 登録:
  • 終了:2012/12/20 13:24:43
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。
id:Nigitama

分割されたファイルは次のようになります。

addressbooktokyo000.xml

addressbooktokyo001.xml

addressbooktokyo002.xml

addressbooktokyo003.xml

・・・

addressbooktokyo050.xml

初回のファイルのみにヘッダ(開始から</header>まで)があります。末尾のファイルのみにフッタ(</body></myaddress>)があります。

作業の流れ

1. 000ファイルを開いて、ヘッダをどこかに保存する

2. 001ファイルを開いて、開始から最初の</person>までを削除しつつ、変数に読み込んで、000の末尾に追加する

3. 000の末尾にフッタ(</body></myaddress>)を追加して、保存して閉じる

4. 001の開始部分にヘッダを挿入する

5. 002ファイルを開いて、開始から最初の</person>までを削除しつつ、変数に読み込んで、001の末尾に追加する

3. 001の末尾にフッタ(</body></myaddress>)を追加して、保存して閉じる

以下ループ

最後. 最後のファイルのみ、フッタはつけずに保存して閉じる

以上です。

以前に、次のURLで質問をしたのですが、対象のXMLファイルがあまりにも大きすぎたので、ファイル分割ツールで指定したサイズにぶった切ってから、あとで整合性をとるやり方に切り替えたいと思います(分割ツールは速いんです)。

http://q.hatena.ne.jp/1355133738

回答1件)

id:Mook No.1

回答回数1314ベストアンサー獲得回数393

いろいろ考えてみましたが、なかなか納得できる結果ではなかったので参考出品です。
100M のファイルを 10M に分割するのは数十秒でしたが 数Gのファイルを処理する時間は未確認ですので、前回のスクリプトと大差ないようでしたら(&ほかに回答なければ)、質問をキャンセルください。


PHP は使用可能ということなので、PHP での実装です。
なお、分割したファイルを処理するのではなく、直接分割する例です。

下記を適当な名前(divFile.php)等で処理するファイルがあるフォルダに保存し、
コマンドラインで php.exe のフルパスに引数でスクリプトを指定し実行してみてください。

下記はCドライブの Program Files 下に PHPがインストールされている実行例です。

C:\>"C:\Program Files\PHP\php.exe" divFile.php




<?php
// 引数は処理するファイル名
DivideFile( 'sample.xml' );

//---------------------------------------------------------------------
function DivideFile( $filePath )
//---------------------------------------------------------------------
{
  // 処理用データ
  //------------------------
    $headerKeyword = "<body>";    // ヘッダ部分の最終キーワード
    $footerKeyword = "</body>";   // フッダ部分の先頭キーワード
    $recordKeyword = "</person>"; // 分割内の最終キーワード
    $fileSize = 10 * 1024 * 1024; // 分割サイズ ex) 10MB

  // 読込ファイルのオープン
  //------------------------
    $rfp = fopen( $filePath, "rb" );

  // ヘッダの切り出し
  //------------------------
    $readData = fread( $rfp, $fileSize );
    $spos = strpos( $readData, $headerKeyword );
    if( $spos === false ) {
        echo "分割サイズ内に".$headerKeyword."がありません。";
        return;
    }
    while( ord($readData[$spos]) != 13 ) { $spos++; }
    if( ord($readData[$spos+1]) == 10 ) { $spos++; }
    $header = substr($readData, 0, $spos + 1);

  // フッダの切り出し
  //------------------------
    fseek( $rfp, $fileSize * (-1), SEEK_END );
    $readData = fread( $rfp, $fileSize );
    $epos = strrpos( $readData, $footerKeyword );
    if( $epos === false ) {
        echo "分割サイズ内に".$footerKeyword."がありません。";
        return;
    }
    while( ord($readData[$epos]) != 13 && ord($readData[$epos]) != 10 ) { $epos--; }

    $footer = substr($readData, $epos + 1 );


  // サイズの整合
  //------------------------
   $fileSize -= strlen( $header );
   $fileSize -= strlen( $footer );

  // 分割処理
  //------------------------
    fseek( $rfp, $spos + 2, SEEK_SET );
    $restBuffer = "";
    $fileIndex = 1;
    while( !feof( $rfp ) ){
        $readBuffer = fread( $rfp, $fileSize );
        if ( $readBuffer === false ) {
            echo "ファイルの読み込みに失敗しました。\n";
            return;
        }
        $readBuffer = $restBuffer.$readBuffer;
        if( ( $pos = strrpos( $readBuffer, $recordKeyword ) ) === false ) {
            echo "検索文字が見つかりませんでした。\n";
            return;
        }
        if ( strpos( $readBuffer, "\n", $pos ) === false ){
            $pos = strrpos( $readBuffer, $recordKeyword, $pos - strlen($readBuffer) - 1 );
        }
        while( ord($readBuffer[$pos]) != 13 && $pos < strlen($readBuffer) ) { $pos++; }
        if( ord($readBuffer[$pos+1]) == 10 ) { $pos++; }

        // 出力ファイル名
        echo "--->". $fileIndex." 番目のファイルを書き出しています\n";
        $wFile = sprintf( 'sample_%03d.xml',$fileIndex );
        $wfp = fopen( $wFile, "wb" );
        fwrite( $wfp, $header );
        fwrite( $wfp, substr($readBuffer, 0, $pos + 1) );
        fwrite( $wfp, $footer );
        fclose( $wfp );
        $restBuffer = substr( $readBuffer, $pos );
        $fileIndex++;
    }
}
?>
id:Nigitama

ありがとうございます!
さっそく試してみたのですが・・・やはり処理がだいぶ重くなってしまいました。

結局、行単位で分割できるツールを導入して、1ファイルを1万行(6.5MBくらい)に分割して、自作したVBSで前後の整合性をとることにしました。6MB強のファイルが450くらいありましたが、1分ちょっとで整形が終わりました。

VBSにしろPHPにしろ、やはり一度に処理するファイルの大きさを小さくするといろいろスムーズですね。当然と言えば当然ですが。

450ファイル×6.5MBを後処理のPHPに回してみたところ、1ファイル20秒前後で処理されて、しばらく放置していたら300万エントリがDBに追加されていました。

2012/12/20 13:05:49
id:Mook

やはり大容量ファイルを扱うにはコマンドベースの処理が必要のようですね。

他に回答なければ、質問はキャンセル下さい。

2012/12/20 13:55:20
  • id:Mook
    締め切り中のようなので、とりあえずコメントまで。

    やりたい趣旨は理解できましたけれど、ファイルの分割は問題なくできているでしょうか。
    中身に関わらずサイズで分割してしまうと、文字コードの途中で切られてしまうので先頭や文末の文字が壊れてしまう気がします。

    文字単位で分割されていれば処理可能な気がしますが、分割のファイルサイズは1ファイルどのくらいなのでしょうか。
  • id:SweetSmile1978
    SweetSmile1978 2012/12/14 19:20:15
    .NET Framework を使うプログラムとして作ったのですが
    締切りですか。
    まぁ勉強にはなったんでいいですが。
    5万件程度なら30秒程度でできるようです。

    1GB だと 300倍あるのでやはり 3時間はかかりますね。
    Core i7 などのCPUを使っていれば
    並列でできるので時間の短縮はできるかもしれません。
  • id:Nigitama
    お二人ともありがとうございます。

    私の方で、不格好なものができそうだったので、一時停止しましたが、その後忙しくなって放置してしまいました。結局、一応はできたのですが、ほかの方の作ったものも見てみたいので回答受付を再開します。

    1. ファイルの分割について
    ファイル破断、というツールを使ってみているのですが、テキストを開いて目視する限り、切れ目が壊れているようには見受けられません。テキストのままです。ひょっとして、壊れた部分はテキストファイル上で見えなくなってしまうのでしょうか。UTF-8です。

    2. 1ファイルは、後の処理のことを考えて数MB~10MBくらいにすると良さそうです。ですので、このサイズのものを900件くらい処理できればと思っています。

    id:SweetSmile1978 さん、.NET Frameworkというものをよく知らないのですが、特別な環境を用意せずにできるのであれば是非ともご回答いただきたいです。単一ツールをインストールする程度であれば喜んで設定します。が、あちこちいじらなければ環境が用意できないというのであれば、この1つの自動化のためにそこまでするかな・・という具合です。


    3. 現在の私のプログラム
    vbs で ADODB.Stream を使いました。
    3つのオブジェクトを作り、前ファイルオブジェクト、後ファイルオブジェクト、一時ファイルオブジェクトとします。まず、初回ループでヘッダ部分を変数に格納します。ループに入ったら、後ファイルobjの先頭行から、</person>までを読み込んで、前ファイルobjの Position = Size から書き込み、フッタをつけて実の前ファイルに保存して閉じます。一時ファイルobjにヘッダを書き込みます。そして、後ファイルobjの現在位置(不要部分を終えた位置)から一時ファイルobjの現在位置(ヘッダが終わった位置)にコピー CopyTo します。一時ファイルobjにフッタをつけて実の後ファイルに保存してとじる。

    とまぁ、こういう具合で正常に動いているような、気がします。
  • id:Mook
    ファイル破断をネットで見てみましたが、内容を見ずに単純にバイト数で分割するソフトに見受けられました。

    もともと分割後に再結合をするためのプログラムのようですから、分割されたファイルが文字単位になっている気がしないのですけれど・・・。

    UTF-8 は1文字が1バイト~6バイトからなっていますから、文字の途中で分割されてしまうと、ファイルの最後の文字1文字欠落した状態になっていると思います。

    .Net Framwork は処理をするためのエンジンなので、そういった言語があるわけではありません。大抵は VB、C++、C# などのプログラミング言語を使用してコードを書きます。
    Windows7 以降であれば、スクリプトとして使用できる PowerShell(XP以降でもMSからダウンロード可)もこれを利用しています。

    速さを求めるのであればこれらのプログラムを利用した方が良いでしょうが、開発環境まで構築したくないというのであれば、スクリプトを利用するかコンパイルされたバイナリ(フリーウェアやシェアウェアもこの形態)を利用することになるでしょう。

    大容量データを処理する場合はどうしても、速度と手軽さのトレードオフになりそうです。
  • id:Nigitama
    Mookさん、夜分にすみません。
    では、ファイルを行単位で分割するソフトに切り替えてみます。
    これなら安心できると思います。

    .Net Frameworkについても解説ありがとうございます。Windows 7 64bitで、CドライブはSSD 128GB、DドライブがHDDの1GBという構成です。先のことを考えれば開発環境のようなものを用意しておけばいいのかもしれませんけど、どうなんでしょう。嫌ではないんですが、躊躇しますね。

    実用的な範囲であればあまり速度は求めていません。そもそも、2GBのXMLを処理することはまれです。普段は50MBもあれば大きい方だと思います。

    私はプログラムの基礎知識というものがなく、見よう見まねで動いたらOK、みたいなレベルです。が、そんなレベルでもPHPは独学でガリガリ書けるようにはなりました。ひょっとしたら、PHPで分割後の処理をさせた方が私の手間的には速いかもしれません。

    現に、整形後のXMLファイルはPHP経由でMySQLに入れている始末ですし、MySQLに入れたデータを毎日何百回もPHP経由で取り出しています。

この質問への反応(ブックマークコメント)

トラックバック

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません