복붙노트

PHP에서 거대한 XML 파일 구문 분석하기

PHP

PHP에서 거대한 XML 파일 구문 분석하기

DMOZ 콘텐츠 / 구조체 XML 파일을 MySQL로 구문 분석하려고하지만 기존의 모든 스크립트는 매우 오래되었고 잘 작동하지 않습니다. 파싱을 위해 PHP에서 큰 (+ 1GB) XML 파일을 열려면 어떻게해야합니까?

해결법

  1. ==============================

    1.대용량 파일을 처리하는 데 정말 적합한 두 가지 PHP API 만 있습니다. 첫 번째는 이전 expat API이고 두 번째는 더 새로운 XMLreader 함수입니다. 이 apis는 전체 트리를 메모리로로드하는 대신 (간단한 xml 및 DOM과 같은) 연속 스트림을 읽습니다.

    대용량 파일을 처리하는 데 정말 적합한 두 가지 PHP API 만 있습니다. 첫 번째는 이전 expat API이고 두 번째는 더 새로운 XMLreader 함수입니다. 이 apis는 전체 트리를 메모리로로드하는 대신 (간단한 xml 및 DOM과 같은) 연속 스트림을 읽습니다.

    예를 들어, DMOZ 카탈로그의 부분 구문 분석기를 살펴볼 수 있습니다.

    <?php
    
    class SimpleDMOZParser
    {
        protected $_stack = array();
        protected $_file = "";
        protected $_parser = null;
    
        protected $_currentId = "";
        protected $_current = "";
    
        public function __construct($file)
        {
            $this->_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();
    
  2. ==============================

    2.이것은 PHP에서 대용량 XML을 처리하는 가장 좋은 방법과 아주 비슷하지만, DMOZ 카탈로그 구문 분석의 특정 문제를 해결하는 매우 구체적인 답변을 제공합니다. 그러나 이것은 대용량 XML의 경우 일반적으로 좋은 Google 히트이기 때문에 다른 질문에서도 대답을 다시 게시 할 것입니다.

    이것은 PHP에서 대용량 XML을 처리하는 가장 좋은 방법과 아주 비슷하지만, DMOZ 카탈로그 구문 분석의 특정 문제를 해결하는 매우 구체적인 답변을 제공합니다. 그러나 이것은 대용량 XML의 경우 일반적으로 좋은 Google 히트이기 때문에 다른 질문에서도 대답을 다시 게시 할 것입니다.

    내 걸릴 :

    https://github.com/prewk/XmlStreamer

    파일을 스트리밍하는 동안 모든 자식을 XML 루트 요소로 추출하는 간단한 클래스입니다. pubmed.com의 108 MB XML 파일에서 테스트되었습니다.

    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();
    
  3. ==============================

    3.최근에 꽤 큰 XML 문서를 파싱해야했기 때문에 한 번에 하나의 요소를 읽는 방법이 필요했습니다.

    최근에 꽤 큰 XML 문서를 파싱해야했기 때문에 한 번에 하나의 요소를 읽는 방법이 필요했습니다.

    다음 파일이있는 경우 complex-test.xml :

    <?xml version="1.0" encoding="UTF-8"?>
    <Complex>
      <Object>
        <Title>Title 1</Title>
        <Name>It's name goes here</Name>
        <ObjectData>
          <Info1></Info1>
          <Info2></Info2>
          <Info3></Info3>
          <Info4></Info4>
        </ObjectData>
        <Date></Date>
      </Object>
      <Object></Object>
      <Object>
        <AnotherObject></AnotherObject>
        <Data></Data>
      </Object>
      <Object></Object>
      <Object></Object>
    </Complex>
    

    그리고 를 반환하고 싶었습니다.

    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
    ###########
    
    <?php
    /**
     * Chunk
     * 
     * Reads a large file in as chunks for easier parsing.
     * 
     * The chunks returned are whole <$this->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:
     * <code>
     *   // 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);
     *   }
     * </code>
     * 
     * @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 = '</'.$element.'>';
    
          // 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;
      }
    }
    
  4. ==============================

    4.DOM 기반 파싱이 아닌 SAX 기반 파서를 사용하는 것이 좋습니다.

    DOM 기반 파싱이 아닌 SAX 기반 파서를 사용하는 것이 좋습니다.

    PHP에서 SAX 사용에 대한 정보 : http://www.brainbell.com/tutorials/php/Parsing_XML_With_SAX.htm

  5. ==============================

    5.이것은 훌륭한 해결책은 아니지만 단지 다른 옵션을 거기에 버리는 것입니다.

    이것은 훌륭한 해결책은 아니지만 단지 다른 옵션을 거기에 버리는 것입니다.

    많은 대용량 XML 파일을 청크로 분해 할 수 있습니다. 특히 유사한 요소의 목록 일뿐입니다 (작업중인 파일이 의심 스럽기 때문에).

    예 : 문서가 다음과 같이 표시되는 경우

    <dmoz>
      <listing>....</listing>
      <listing>....</listing>
      <listing>....</listing>
      <listing>....</listing>
      <listing>....</listing>
      <listing>....</listing>
      ...
    </dmoz>
    

    한 번에 두세 가지로 읽을 수 있으며 루트 레벨 태그에로드 한 몇 가지 완전한

    태그를 인위적으로 래핑 한 다음 simplexml / domxml을 통해로드합니다 (이 방법을 사용할 때 domxml을 사용했습니다).

    솔직히 PHP 5.1.2를 사용하는 경우이 방법을 선호합니다. 5.1.2 이상에서는 XMLReader를 사용할 수 있습니다.이 옵션은 아마도 가장 좋은 옵션 일 수 있습니다. 그러나 그 전에는 위의 청크 전략 또는 이전 SAX / expat lib 중 하나를 사용해야합니다. 그리고 나머지는 모르겠지만 SAX / expat 파서를 작성 / 유지하는 것은 싫어합니다.

    그러나이 방법은 문서가 여러 개의 동일한 하위 수준 요소로 구성되지 않은 경우에는 실용적이지 않습니다 (예 : 모든 파일 목록 또는 URL 등에서 유용하지만 작성할 수 없음). 대형 HTML 문서 구문 분석 용 센스)

  6. from https://stackoverflow.com/questions/911663/parsing-huge-xml-files-in-php by cc-by-sa and MIT license