2012-07-07 104 views
2

我有與我的劇本有每天解析超過300 000項XML文件..如何使用PHP將大型XML文件分成小型文件?

的XML與結構爲:

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

我需要大的XML文件分割成更小的文件所以我的PHP可以運行它們,目前它不能處理它,因爲它使用了太多的內存。 任何人都可以幫助我嗎?

+1

分割文件當然很容易,但您需要提供更多細節,例如您希望如何分割文件。此外,如果您每次都將整個內容加載到內存中,那麼如果它是1個文件或100個文件就沒有區別,它將在完成時採用相同的內存。你需要閱讀整個過程並存儲它嗎? – Woody 2012-07-07 12:22:26

+0

我的腳本解析文件有數據庫查詢和圖像處理,所以我認爲如果我準備xml有一些條目限制每個文件20 000 - 30 000會更好。 所以這個拆分應該用最大限制拆分條目。 – Svetoslav 2012-07-07 12:30:23

回答

5

請看看這篇文章PHP XML Parsing

  1. SAX解析
  2. XML閱讀器XMLReader pull解析器

將是最適合這個工作

看了上面的文章嘗試一些代碼,你一定會得到這個答案

+0

我知道這些事情,但在解析器中解析文件時,它會運行db查詢和圖像處理,這需要大量內存和時間進行處理,這就是爲什麼我需要在解析器獲取之前拆分這個大文件。 – Svetoslav 2012-07-07 12:34:45

+0

我曾經懷疑過,但maxjackie的建議(+1)很好:爲了能夠有效地分割文件,您可以做出快速的假設(如我所做的那樣),或者使用更加智能和快速的XML Parser。 – LSerni 2012-07-07 12:47:15

7

很大程度上取決於您的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解析腳本,甚至更好,根本不寫入任何文件,而是將fwrites排入緩衝區,然後調用腳本與那個緩衝區)。

另一個優點是能夠「外包」不同的子服務器中的文件,例如是XML處理長由於DNS解析,DB調用,HTTP/SOAP調用,需要反饋等等。在這種情況下,您可以基於($ n%NUM_CLIENTS)將文件保存在不同的子目錄中,並且每個客戶端可以一次獲取一個文件,處理並刪除它並繼續。

但是,最好的處理方法是改寫腳本,以便不加載內存中的XML,但使用XML Parser支持一次解析一次。

一個折衷的辦法是使用XML解析器對XML文件進行分片,並按照「原樣」將其提供給現有腳本。

XMLPARSE版本:高效切片和切塊大型XML文件
無需擔心XML實際上是如何放在一起

的XMLPARSE職能工作,通過回調,即你給你的數據條目point(xml_parse)然後分析數據,將其分割,將各個塊路由到您定義的相應子函數。xml_parse將處理編碼和空白,從而使您免於應對相同的需求,這是上述代碼中最大的缺點之一。 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> 
... 

我們寫一個XMLPARSER將提取每個的N 「塊」 做這個(這裏N = 5)項和將其饋送到塊處理器,該處理器在接收到...時會將其包裹在標籤之間,添加XML標頭,從而生成具有與原始大文件相同語法的文件,但只有五個項目。

要保存在單獨的文件中,我們跟蹤塊號碼。

function processChunk() { 
     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); 
      fwrite($xp, "</root>"); 
     fclose($xp); 
     print "Written {$file}\n"; 
     $CHUNKS++; 
     $PAYLOAD = ''; 
     $ITEMCOUNT = 0; 
    } 

xmlparse函數需要回調函數:一個接收標記OPENING,一個標記CLOSING,一個獲取內容,另一個獲取任何內容。我們對此毫無興趣,所以我們只填補了前三名處理者。

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(); 
    } 

然後我們調用本機:「拆分的test.xml到項目標籤的情況下,5件」

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

此實現運行作爲開頭愚蠢的分塊約五倍慢,但可以處理同一行中的標籤,甚至可以擴展以驗證XML。

+0

P * E * R * F * E * C * T !!! – Disco 2012-09-18 13:44:02

+0

糾錯;不那麼直接..我有一個XML文件,共有38'000項。出於某種原因,您的腳本只能轉儲30,000次。任何想法 ?我認爲它缺少1-2塊。 – Disco 2012-09-18 14:14:57

+0

爲了理解原因,我不得不花很長時間仔細研究XML。您可以將調試代碼添加到解析器 - 您正在使用哪一個? - 檢查發生了什麼。 – LSerni 2012-09-18 18:55:10