Analisando enormes arquivos XML em PHP

Estou tentando analisar o conteúdo DMOZ / estruturas de arquivos XML no MySQL, mas todos os scripts existentes para fazer isso são muito antigos e não funcionam bem. Como faço para abrir um arquivo XML grande (+ 1 GB) em PHP para análise?

Existem apenas duas APIs de php que são realmente adequadas para o processamento de arquivos grandes. O primeiro é o antigo expi api, eo segundo é o mais recente XMLreader funções. Esses apis lêem streams contínuos em vez de carregar toda a tree na memory (o que é simplexml e DOM).

Por exemplo, você pode querer analisar este analisador parcial do catálogo DMOZ:

_file = $file; $this->_parser = xml_parser_create("UTF-8"); xml_set_object($this->_parser, $this); xml_set_element_handler($this->_parser, "startTag", "endTag"); } public function startTag($parser, $name, $attribs) { array_push($this->_stack, $this->_current); if ($name == "TOPIC" && count($attribs)) { $this->_currentId = $attribs["R:ID"]; } if ($name == "LINK" && strpos($this->_currentId, "Top/Home/Consumer_Information/Electronics/") === 0) { echo $attribs["R:RESOURCE"] . "\n"; } $this->_current = $name; } public function endTag($parser, $name) { $this->_current = array_pop($this->_stack); } public function parse() { $fh = fopen($this->_file, "r"); if (!$fh) { die("Epic fail!\n"); } while (!feof($fh)) { $data = fread($fh, 4096); xml_parse($this->_parser, $data, feof($fh)); } } } $parser = new SimpleDMOZParser("content.rdf.u8"); $parser->parse(); 

Esta é uma pergunta muito semelhante à melhor maneira de processar XML grande em PHP, mas com uma resposta específica muito boa, abordada abordando o problema específico da análise do catálogo DMOZ. No entanto, uma vez que este é um bom sucesso do Google para XMLs em geral, em geral, eu também repito minha resposta da outra pergunta:

Minha opinião:

https://github.com/prewk/XmlStreamer

Uma class simples que extrairá todas as crianças para o elemento raiz XML ao transmitir o arquivo. Testado no arquivo XML de 108 MB da pubmed.com.

 class SimpleXmlStreamer extends XmlStreamer { public function processNode($xmlString, $elementName, $nodeIndex) { $xml = simplexml_load_string($xmlString); // Do something with your SimpleXML object return true; } } $streamer = new SimpleXmlStreamer("myLargeXmlFile.xml"); $streamer->parse(); 

Eu recentemente tive que analisar alguns documentos XML bastante grandes, e precisava de um método para ler um elemento por vez.

Se você tiver o seguinte arquivo complex-test.xml :

    Title 1 It's name goes here                 

E queria retornar o s

PHP:

 require_once('class.chunk.php'); $file = new Chunk('complex-test.xml', array('element' => 'Object')); while ($xml = $file->read()) { $obj = simplexml_load_string($xml); // do some parsing, insert to DB whatever } ########### Class File ########### options['element']/>s found within file. * * Each call to read() returns the whole element including start and end tags. * * Tested with a 1.8MB file, extracted 500 elements in 0.11s * (with no work done, just extracting the elements) * * Usage: *  * // initialize the object * $file = new Chunk('chunk-test.xml', array('element' => 'Chunk')); * * // loop through the file until all lines are read * while ($xml = $file->read()) { * // do whatever you want with the string * $o = simplexml_load_string($xml); * } *  * * @package default * @author Dom Hastings */ class Chunk { /** * options * * @var array Contains all major options * @access public */ public $options = array( 'path' => './', // string The path to check for $file in 'element' => '', // string The XML element to return 'chunkSize' => 512 // integer The amount of bytes to retrieve in each chunk ); /** * file * * @var string The filename being read * @access public */ public $file = ''; /** * pointer * * @var integer The current position the file is being read from * @access public */ public $pointer = 0; /** * handle * * @var resource The fopen() resource * @access private */ private $handle = null; /** * reading * * @var boolean Whether the script is currently reading the file * @access private */ private $reading = false; /** * readBuffer * * @var string Used to make sure start tags aren't missed * @access private */ private $readBuffer = ''; /** * __construct * * Builds the Chunk object * * @param string $file The filename to work with * @param array $options The options with which to parse the file * @author Dom Hastings * @access public */ public function __construct($file, $options = array()) { // merge the options together $this->options = array_merge($this->options, (is_array($options) ? $options : array())); // check that the path ends with a / if (substr($this->options['path'], -1) != '/') { $this->options['path'] .= '/'; } // normalize the filename $file = basename($file); // make sure chunkSize is an int $this->options['chunkSize'] = intval($this->options['chunkSize']); // check it's valid if ($this->options['chunkSize'] < 64) { $this->options['chunkSize'] = 512; } // set the filename $this->file = realpath($this->options['path'].$file); // check the file exists if (!file_exists($this->file)) { throw new Exception('Cannot load file: '.$this->file); } // open the file $this->handle = fopen($this->file, 'r'); // check the file opened successfully if (!$this->handle) { throw new Exception('Error opening file for reading'); } } /** * __destruct * * Cleans up * * @return void * @author Dom Hastings * @access public */ public function __destruct() { // close the file resource fclose($this->handle); } /** * read * * Reads the first available occurence of the XML element $this->options['element'] * * @return string The XML string from $this->file * @author Dom Hastings * @access public */ public function read() { // check we have an element specified if (!empty($this->options['element'])) { // trim it $element = trim($this->options['element']); } else { $element = ''; } // initialize the buffer $buffer = false; // if the element is empty if (empty($element)) { // let the script know we're reading $this->reading = true; // read in the whole doc, cos we don't know what's wanted while ($this->reading) { $buffer .= fread($this->handle, $this->options['chunkSize']); $this->reading = (!feof($this->handle)); } // return it all return $buffer; // we must be looking for a specific element } else { // set up the strings to find $open = '<'.$element.'>'; $close = ''; // let the script know we're reading $this->reading = true; // reset the global buffer $this->readBuffer = ''; // this is used to ensure all data is read, and to make sure we don't send the start data again by mistake $store = false; // seek to the position we need in the file fseek($this->handle, $this->pointer); // start reading while ($this->reading && !feof($this->handle)) { // store the chunk in a temporary variable $tmp = fread($this->handle, $this->options['chunkSize']); // update the global buffer $this->readBuffer .= $tmp; // check for the open string $checkOpen = strpos($tmp, $open); // if it wasn't in the new buffer if (!$checkOpen && !($store)) { // check the full buffer (in case it was only half in this buffer) $checkOpen = strpos($this->readBuffer, $open); // if it was in there if ($checkOpen) { // set it to the remainder $checkOpen = $checkOpen % $this->options['chunkSize']; } } // check for the close string $checkClose = strpos($tmp, $close); // if it wasn't in the new buffer if (!$checkClose && ($store)) { // check the full buffer (in case it was only half in this buffer) $checkClose = strpos($this->readBuffer, $close); // if it was in there if ($checkClose) { // set it to the remainder plus the length of the close string itself $checkClose = ($checkClose + strlen($close)) % $this->options['chunkSize']; } // if it was } elseif ($checkClose) { // add the length of the close string itself $checkClose += strlen($close); } // if we've found the opening string and we're not already reading another element if ($checkOpen !== false && !($store)) { // if we're found the end element too if ($checkClose !== false) { // append the string only between the start and end element $buffer .= substr($tmp, $checkOpen, ($checkClose - $checkOpen)); // update the pointer $this->pointer += $checkClose; // let the script know we're done $this->reading = false; } else { // append the data we know to be part of this element $buffer .= substr($tmp, $checkOpen); // update the pointer $this->pointer += $this->options['chunkSize']; // let the script know we're gonna be storing all the data until we find the close element $store = true; } // if we've found the closing element } elseif ($checkClose !== false) { // update the buffer with the data upto and including the close tag $buffer .= substr($tmp, 0, $checkClose); // update the pointer $this->pointer += $checkClose; // let the script know we're done $this->reading = false; // if we've found the closing element, but half in the previous chunk } elseif ($store) { // update the buffer $buffer .= $tmp; // and the pointer $this->pointer += $this->options['chunkSize']; } } } // return the element (or the whole file if we're not looking for elements) return $buffer; } } 

Eu sugeriria usar um analisador baseado em SAX em vez de análise baseada em DOM.

Informações sobre o uso de SAX em PHP: http://www.brainbell.com/tutorials/php/Parsing_XML_With_SAX.htm

Esta não é uma ótima solução, mas apenas para lançar outra opção lá fora:

Você pode quebrar muitos grandes arquivos XML em pedaços, especialmente aqueles que são realmente apenas listas de elementos semelhantes (como eu suspeito que o arquivo que você está trabalhando seria).

Por exemplo, se o seu documento parecer:

  .... .... .... .... .... .... ...  

Você pode lê-lo em meg ou dois de cada vez, enrolle artificialmente as poucas etiquetas

completas que você carregou em uma etiqueta de nível de raiz e, em seguida, carregue-as via simplexml / domxml (usei domxml, ao tomar essa abordagem).

Francamente, eu prefiro essa abordagem se estiver usando o PHP <5.1.2. Com 5.1.2 e superior, o XMLReader está disponível, provavelmente a melhor opção, mas antes disso, você está preso com a estratégia de fragmentação acima ou a antiga SAX / expat lib. E eu não sei sobre o resto de vocês, mas eu odeio escrever / manter analisadores SAX / expat.

Note, no entanto, que esta abordagem NÃO é realmente prática quando seu documento não consiste em muitos elementos de nível inferior idênticos (por exemplo, funciona de forma excelente para qualquer tipo de lista de arquivos, URLs, etc., mas não faria sentido para analisar um grande documento HTML)