4

スクリプトが毎日解析する必要がある 300,000 を超えるエントリを含む XML ファイルがあります。

xml の構造は次のとおりです。

<root>
     <item>
        <proper1></proper1>
        <proper2></proper2>
    </item>
</root>

PHP で実行できるように、大きな XML ファイルを小さなファイルに分割する必要があります。現在、メモリの使用量が多すぎるため処理できません。誰でもそれで私を助けることができますか?

4

2 に答える 2

10

XMLファイルの構造に大きく依存します。

たとえば、次のようなことができます(構造が投稿したものであり、キャリッジリターンが含まれていると仮定すると、それ以外の場合はさらに複雑になります)。

ラインチョッピングバージョン:「適切に」フォーマットされている場合は大きなXMLファイルを高速でスライスし
、ファイルがそのように正確にフォーマットされていない場合はクラッシュして書き込みます

$fp = fopen(XMLFILE, 'r');
$decl = fgets($fp, 1024);  // Drop the XML declaration '<?xml...?>'
$root = fgets($fp, 1024);  // Drop the root declaration
$n = 1;
while(!feof($fp)) {
    $tag = fgets($fp, 1024);
    if ('<item>' === $tag) {
        isset($gp) || trigger_error('Unexpected state');
        $gp = fopen("chunk{$n}.xml", 'w'); $n++;
        // Write the header of the file we saved from before
        fwrite($gp, $decl);
        fwrite($gp, $root);
    } else if ('</item>' === $tag) {
        fwrite($gp, $tag);
        fwrite($gp, '</root>');
        fclose($gp); unset($gp);
        continue;
    }
    if (!isset($gp)) {
        if ('</root>' === $tag /* EOF */) {
            break;
        } else {
            trigger_error('Unexpected state 2');
        }
    }
    fwrite($gp, $tag);
}
fclose($fp);
isset($gp) || trigger_error('Unexpected state 3');

これには、XML解析スクリプトを「リサイクル」できるという大きな利点があります(実際、$ gpを閉じるとすぐにXML解析スクリプトを呼び出すことができます。さらに良いのは、ファイルにまったく書き込まず、fwriteをエンキューすることです。バッファ、およびそのバッファでスクリプトを呼び出します)。

もう1つの利点は、異なるサブサーバー間でファイルを「アウトソーシング」できることです。たとえば、DNS解決、DB呼び出し、HTTP / SOAP呼び出し、フィードバックの必要性などのためにXML処理が長くなります。その場合、($ n%NUM_CLIENTS)に基づいてファイルを異なるサブディレクトリに保存でき、すべてのクライアントが一度に1つのファイルをフェッチし、それを処理して削除し、続行できます。

ただし、続行するための最良の方法は、代わりに、XMLをメモリにロードせずに、XMLパーサーのサポートを使用して一度に少しずつ解析するために、スクリプトを書き直すことです。

妥協案は、XMLパーサーを使用してXMLファイルをスライスし、それを既存のスクリプトに「そのまま」フィードすることです。

XMLparseバージョン:
XMLが実際にどのようにまとめられるかを気にせずに、大きなXMLファイルを効率的にスライスおよびダイシングします。

xmlparse関数はコールバックを介して機能します。つまり、データをエントリポイント(xml_parse)にフィードし、エントリポイント(xml_parse)がデータを分析して分割し、さまざまなチャンクを定義した適切なサブ関数にルーティングします。xml_parseはエンコーディングと空白を処理するため、上記のコードの最大の欠点の1つである、エンコーディングと空白に対処する必要がなくなります。xmlparseコア自体はデータを保持しないため、ギガバイト(またはテラバイト)のファイルでも定数メモリの実装を実現できます。

それでは、XMLParserのコードを書き直し、特定のタグの特定の回数の繰り返しを分割して大きなファイルをチャンク化する方法を見てみましょう。

つまり、入力ファイル:

 <root><item>(STUFF OF ITEM1)</item><item>(STUFF OF ITEM2)/item>....ITEM1234...</root>

出力ファイル:

 FILE1: <root><item>(1)</item><item>(2)</item>...(5)</root>
 FILE2: <root><item>(6)</item><item>(7)</item>...(10)</root>
 ...

これを行うには、N個(ここではN = 5)のアイテムの各「チャンク」を抽出してチャンクプロセッサにフィードするXMLparserを作成します。チャンクプロセッサは、受信時にタグ間でラップし、XMLヘッダーを追加します。元の大きなファイルと同じ構文で、ただし5つの項目しかないファイルを生成します。

別のファイルに保存するために、チャンク番号を追跡します。

    function processChunk($lastChunk = false) {
         GLOBAL $CHUNKS, $PAYLOAD, $ITEMCOUNT;
         if ('' == $PAYLOAD) {
             return;
         }
         $xp = fopen($file = "output-$CHUNKS.xml", "w");
         fwrite($xp, '<?xml version="1.0"?>'."\n");
             fwrite($xp, "<root>");
                 fwrite($xp, $PAYLOAD);
             $lastChunk || fwrite($xp, "</root>");
         fclose($xp);
         print "Written {$file}\n";
         $CHUNKS++;
         $PAYLOAD    = '';
         $ITEMCOUNT  = 0;
    }

xmlparse関数にはコールバックが必要です。1つはタグOPENINGを受け取り、1つはCLOSINGを受け取り、1つはコンテンツを取得し、もう1つは何でも取得します。私たちは何にも興味がないので、最初の3つのハンドラーだけを埋めます。

    function startElement($xml, $tag, $attrs = array()) {
        GLOBAL $PAYLOAD, $CHUNKS, $ITEMCOUNT, $CHUNKON;
        if (!($CHUNKS||$ITEMCOUNT)) {
            if ($CHUNKON == strtolower($tag)) {
                $PAYLOAD = '';
            }
        }
        $PAYLOAD .= "<{$tag}";
        foreach($attrs as $k => $v) {
            $PAYLOAD .= " {$k}=\"" .addslashes($v).'"';
        }
        $PAYLOAD .= '>';
    }

    function endElement($xml, $tag) {
        GLOBAL $CHUNKON, $ITEMCOUNT, $ITEMLIMIT;
        dataHandler(null, "</{$tag}>");
        if ($CHUNKON == strtolower($tag)) {
             if (++$ITEMCOUNT >= $ITEMLIMIT) {
                 processChunk();
             }
        }
    }

    function dataHandler($xml, $data) {
        GLOBAL $PAYLOAD;
        $PAYLOAD .= $data;
    }

    function defaultHandler($xml, $data) {
        // a.k.a. Wild Text Fallback Handler, or WTFHandler for short.
    }

createXMLParser関数は、わかりやすくするためにスタンドアロンです。

    function createXMLParser($CHARSET, $bareXML = false) {
            $CURRXML = xml_parser_create($CHARSET);
            xml_parser_set_option( $CURRXML, XML_OPTION_CASE_FOLDING, false);
            xml_parser_set_option( $CURRXML, XML_OPTION_TARGET_ENCODING, $CHARSET);
            xml_set_element_handler($CURRXML, 'startElement', 'endElement');
            xml_set_character_data_handler($CURRXML, 'dataHandler');
            xml_set_default_handler($CURRXML, 'defaultHandler');
            if ($bareXML) {
                xml_parse($CURRXML, '<?xml version="1.0"?>', 0);
            }
            return $CURRXML;
    }

最後に、ビッグファイル氏を開いてグラインダーに送信するフィードループ。

    function chunkXMLBigFile($file, $tag = 'item', $howmany = 5) {
         GLOBAL $CHUNKON, $CHUNKS, $ITEMLIMIT;

         // Every chunk only holds $ITEMLIMIT "$CHUNKON" elements at most.
         $CHUNKON   = $tag;
         $ITEMLIMIT = $howmany;

         $xml = createXMLParser('UTF-8', false);

         $fp = fopen($file, 'r');
         $CHUNKS  = 0;
         while(!feof($fp)) {
              $chunk = fgets($fp, 10240);
              xml_parse($xml, $chunk, feof($fp));
         }
         xml_parser_free($xml);

         // Now, it is possible that one last chunk is still queued for processing.
         processChunk(true);
    }

次に、マシンを「test.xmlをアイテムタグの5つのインスタンスに分割する」と呼びます。

    ChunkXMLBigFile('test.xml', 'item', 5);

この実装は、最初は愚かなチャンカーの約5倍の速度で実行されますが、同じ行のタグを処理でき、XMLを検証するために拡張することもできます。

于 2012-07-07T12:45:11.020 に答える
7

この記事をご覧くださいPHP XML Parsing

  1. SAX 解析
  2. XML リーダーXMLReaderプル パーサー

この仕事に最適だろう

上記の記事を読んで、いくつかのコードを試してみてください。これに対する答えは間違いなく得られます

于 2012-07-07T12:28:47.827 に答える