2011-01-12 70 views
24

Tengo grandes hojas de trabajo de Excel que quiero poder leer en MySQL usando PHPExcel.¿Cómo leer hojas de cálculo grandes de grandes archivos de Excel (27 MB +) con PHPExcel?

Estoy usando el recent patch que le permite leer en las hojas de trabajo sin abrir todo el archivo. De esta manera puedo leer una hoja de trabajo a la vez.

Sin embargo, un archivo de Excel tiene 27 MB de tamaño. Puedo leer con éxito en la primera hoja de trabajo, ya que es pequeña, pero la segunda es tan grande que la tarea cron que inició el proceso a las 22:00 no terminó a las 8:00 a.m., la hoja de trabajo es demasiado grande.

¿Hay alguna forma de leer en una línea de trabajo por línea, p. algo como esto:

$inputFileType = 'Excel2007'; 
$inputFileName = 'big_file.xlsx'; 
$objReader = PHPExcel_IOFactory::createReader($inputFileType); 
$worksheetNames = $objReader->listWorksheetNames($inputFileName); 

foreach ($worksheetNames as $sheetName) { 
    //BELOW IS "WISH CODE": 
    foreach($row = 1; $row <=$max_rows; $row+= 100) { 
     $dataset = $objReader->getWorksheetWithRows($row, $row+100); 
     save_dataset_to_database($dataset); 
    } 
} 

Adición

@ Marcos, he utilizado el código que envió para crear el siguiente ejemplo:

function readRowsFromWorksheet() { 

    $file_name = htmlentities($_POST['file_name']); 
    $file_type = htmlentities($_POST['file_type']); 

    echo 'Read rows from worksheet:<br />'; 
    debug_log('----------start'); 
    $objReader = PHPExcel_IOFactory::createReader($file_type); 
    $chunkSize = 20; 
    $chunkFilter = new ChunkReadFilter(); 
    $objReader->setReadFilter($chunkFilter); 

    for ($startRow = 2; $startRow <= 240; $startRow += $chunkSize) { 
     $chunkFilter->setRows($startRow, $chunkSize); 
     $objPHPExcel = $objReader->load('data/' . $file_name); 
     debug_log('reading chunk starting at row '.$startRow); 
     $sheetData = $objPHPExcel->getActiveSheet()->toArray(null, true, true, true); 
     var_dump($sheetData); 
     echo '<hr />'; 
    } 
    debug_log('end'); 
} 

Como muestra el siguiente archivo de registro, se ejecuta bien en un pequeño archivo de Excel 8K, pero cuando lo ejecuto en un archivo de Excel 3 MB, nunca pasa el abeto st trozo, ¿hay alguna manera de que pueda optimizar el código para el funcionamiento, de lo contrario, no parece que no es suficiente para conseguir performant trozos de un gran archivo de Excel:

2011-01-12 11:07:15: ----------start 
2011-01-12 11:07:15: reading chunk starting at row 2 
2011-01-12 11:07:15: reading chunk starting at row 22 
2011-01-12 11:07:15: reading chunk starting at row 42 
2011-01-12 11:07:15: reading chunk starting at row 62 
2011-01-12 11:07:15: reading chunk starting at row 82 
2011-01-12 11:07:15: reading chunk starting at row 102 
2011-01-12 11:07:15: reading chunk starting at row 122 
2011-01-12 11:07:15: reading chunk starting at row 142 
2011-01-12 11:07:15: reading chunk starting at row 162 
2011-01-12 11:07:15: reading chunk starting at row 182 
2011-01-12 11:07:15: reading chunk starting at row 202 
2011-01-12 11:07:15: reading chunk starting at row 222 
2011-01-12 11:07:15: end 
2011-01-12 11:07:52: ----------start 
2011-01-12 11:08:01: reading chunk starting at row 2 
(...at 11:18, CPU usage at 93% still running...) 

Adición 2

Cuando comento hacia fuera:

//$sheetData = $objPHPExcel->getActiveSheet()->toArray(null, true, true, true); 
//var_dump($sheetData); 

Luego se analiza a una velocidad aceptable 10 (aproximadamente 2 filas por segundo), ¿hay alguna manera de aumentar el rendimiento de toArray()?

2011-01-12 11:40:51: ----------start 
2011-01-12 11:40:59: reading chunk starting at row 2 
2011-01-12 11:41:07: reading chunk starting at row 22 
2011-01-12 11:41:14: reading chunk starting at row 42 
2011-01-12 11:41:22: reading chunk starting at row 62 
2011-01-12 11:41:29: reading chunk starting at row 82 
2011-01-12 11:41:37: reading chunk starting at row 102 
2011-01-12 11:41:45: reading chunk starting at row 122 
2011-01-12 11:41:52: reading chunk starting at row 142 
2011-01-12 11:42:00: reading chunk starting at row 162 
2011-01-12 11:42:07: reading chunk starting at row 182 
2011-01-12 11:42:15: reading chunk starting at row 202 
2011-01-12 11:42:22: reading chunk starting at row 222 
2011-01-12 11:42:22: end 

adición 3

Esto parece funcionar adecuadamente, por ejemplo, al menos en el archivo 3 MB:

for ($startRow = 2; $startRow <= 240; $startRow += $chunkSize) { 
    echo 'Loading WorkSheet using configurable filter for headings row 1 and for rows ', $startRow, ' to ', ($startRow + $chunkSize - 1), '<br />'; 
    $chunkFilter->setRows($startRow, $chunkSize); 
    $objPHPExcel = $objReader->load('data/' . $file_name); 
    debug_log('reading chunk starting at row ' . $startRow); 
    foreach ($objPHPExcel->getActiveSheet()->getRowIterator() as $row) { 
     $cellIterator = $row->getCellIterator(); 
     $cellIterator->setIterateOnlyExistingCells(false); 
     echo '<tr>'; 
     foreach ($cellIterator as $cell) { 
      if (!is_null($cell)) { 
       //$value = $cell->getCalculatedValue(); 
       $rawValue = $cell->getValue(); 
       debug_log($rawValue); 
      } 
     } 
    } 
} 
+0

El var_dump de $ sheetData era sólo en mi fragmento de código para demostrar cómo funciona el chunking, probablemente no es algo que había necesidad de una utilización "mundo real". El método rangeToArray() que actualmente estoy agregando a la clase Hoja de trabajo también sería más eficiente que el método toArray() si tuviera que hacer un volcado de datos de la hoja de trabajo. –

+0

@Edward Tanguay hola, ¿encontró alguna solución/alternativa para esto? Tengo el mismo problema –

+1

Una alternativa a PHPExcel es la biblioteca de código abierto [Spout] (https://github.com/box/spout). Es compatible con la lectura y escritura de archivos de gran tamaño, y no requiere más de 10 MB de memoria. ¡Y es super rápido! – Adrien

Respuesta

9

Es posible leer una hoja de "trozos" utilizando Read Filters, aunque no puedo garantizar la eficacia.

$inputFileType = 'Excel5'; 
$inputFileName = './sampleData/example2.xls'; 


/** Define a Read Filter class implementing PHPExcel_Reader_IReadFilter */ 
class chunkReadFilter implements PHPExcel_Reader_IReadFilter 
{ 
    private $_startRow = 0; 

    private $_endRow = 0; 

    /** Set the list of rows that we want to read */ 
    public function setRows($startRow, $chunkSize) { 
     $this->_startRow = $startRow; 
     $this->_endRow  = $startRow + $chunkSize; 
    } 

    public function readCell($column, $row, $worksheetName = '') { 
     // Only read the heading row, and the rows that are configured in $this->_startRow and $this->_endRow 
     if (($row == 1) || ($row >= $this->_startRow && $row < $this->_endRow)) { 
      return true; 
     } 
     return false; 
    } 
} 


echo 'Loading file ',pathinfo($inputFileName,PATHINFO_BASENAME),' using IOFactory with a defined reader type of ',$inputFileType,'<br />'; 
/** Create a new Reader of the type defined in $inputFileType **/ 

$objReader = PHPExcel_IOFactory::createReader($inputFileType); 



echo '<hr />'; 


/** Define how many rows we want to read for each "chunk" **/ 
$chunkSize = 20; 
/** Create a new Instance of our Read Filter **/ 
$chunkFilter = new chunkReadFilter(); 

/** Tell the Reader that we want to use the Read Filter that we've Instantiated **/ 
$objReader->setReadFilter($chunkFilter); 

/** Loop to read our worksheet in "chunk size" blocks **/ 
/** $startRow is set to 2 initially because we always read the headings in row #1 **/ 

for ($startRow = 2; $startRow <= 240; $startRow += $chunkSize) { 
    echo 'Loading WorkSheet using configurable filter for headings row 1 and for rows ',$startRow,' to ',($startRow+$chunkSize-1),'<br />'; 
    /** Tell the Read Filter, the limits on which rows we want to read this iteration **/ 
    $chunkFilter->setRows($startRow,$chunkSize); 
    /** Load only the rows that match our filter from $inputFileName to a PHPExcel Object **/ 
    $objPHPExcel = $objReader->load($inputFileName); 

    // Do some processing here 

    $sheetData = $objPHPExcel->getActiveSheet()->toArray(null,true,true,true); 
    var_dump($sheetData); 
    echo '<br /><br />'; 
} 

Tenga en cuenta que este filtro Lea siempre leerá la primera fila de la hoja de trabajo, así como las filas definidas por la norma trozo.

Al usar un filtro de lectura, PHPExcel aún analiza el archivo completo, pero solo carga aquellas celdas que coinciden con el filtro de lectura definido, por lo que solo utiliza la memoria requerida por ese número de celdas.Sin embargo, analizará el archivo varias veces, una por cada fragmento, por lo que será más lento. Este ejemplo muestra 20 filas a la vez: para leer línea por línea, simplemente configure $ chunkSize en 1.

Esto también puede causar problemas si tiene fórmulas que hacen referencia a celdas en diferentes "fragmentos", porque los datos simplemente no son t disponible para celdas fuera del "trozo" actual.

+0

Estoy intentando su código en un archivo de prueba, pero me dice 'Class 'ChunkReadFilter' not found'. Si me quito el 'implementa PHPExcel_Reader_IReadFilter', entonces encuentra la clase y me dice que debo' implementar la interfaz PHPExcel_Reader_IReadFilter', puse al principio de mi archivo 'require_once 'PHPExcelClasses/PHPExcel/Reader/IReadFilter.php'' y 'require_once 'PHPExcelClasses/PHPExcel/Reader/IReader.php'' pero aún no puede encontrar la clase si implemento esta interfaz, ¿hay algún otro archivo que deba incluir? –

+0

solo tenía que poner la clase 'ChunkReadFilter' encima del código principal, funciona ahora, gracias –

+0

Probé ese código en una prueba (publicada anteriormente). Aunque funciona en un archivo pequeño (8K), parece que no pasa el primer fragmento en un archivo de 3 MB. –

3

Actualmente leer .xlsx, .csv.ods y la mejor opción es una hoja de cálculo lector (https://github.com/nuovo/spreadsheet-reader), ya que puede leer los archivos sin tener que cargar todo en la memoria. Para la extensión .xls tiene limitaciones porque usa PHPExcel para leer.

1

/* * Esta es la ChunkReadFilter.php */

<?php 
Class ChunkReadFilter implements PHPExcel_Reader_IReadFilter { 

    private $_startRow = 0; 
    private $_endRow = 0; 

    /** Set the list of rows that we want to read */ 
    public function setRows($startRow, $chunkSize) { 
     $this->_startRow = $startRow; 
     $this->_endRow = $startRow + $chunkSize; 
    } 

    public function readCell($column, $row, $worksheetName = '') { 

     // Only read the heading row, and the rows that are configured in $this->_startRow and $this->_endRow 
     if (($row == 1) || ($row >= $this->_startRow && $row < $this->_endRow)) { 

      return true; 
     } 
     return false; 
    } 

} 
?> 

/* * Y este es el index.php y una aplicación no es perfecto, pero básicas al final de este archivo *. */

<?php 

require_once './Classes/PHPExcel/IOFactory.php'; 
require_once 'ChunkReadFilter.php'; 

class Excelreader { 

    /** 
    * This function is used to read data from excel file in chunks and insert into database 
    * @param string $filePath 
    * @param integer $chunkSize 
    */ 
    public function readFileAndDumpInDB($filePath, $chunkSize) { 
     echo("Loading file " . $filePath . " ....." . PHP_EOL); 
     /** Create a new Reader of the type that has been identified * */ 
     $objReader = PHPExcel_IOFactory::createReader(PHPExcel_IOFactory::identify($filePath)); 

     $spreadsheetInfo = $objReader->listWorksheetInfo($filePath); 

     /** Create a new Instance of our Read Filter * */ 
     $chunkFilter = new ChunkReadFilter(); 

     /** Tell the Reader that we want to use the Read Filter that we've Instantiated * */ 
     $objReader->setReadFilter($chunkFilter); 
     $objReader->setReadDataOnly(true); 
     //$objReader->setLoadSheetsOnly("Sheet1"); 
     //get header column name 
     $chunkFilter->setRows(0, 1); 
     echo("Reading file " . $filePath . PHP_EOL . "<br>"); 
     $totalRows = $spreadsheetInfo[0]['totalRows']; 
     echo("Total rows in file " . $totalRows . " " . PHP_EOL . "<br>"); 

     /** Loop to read our worksheet in "chunk size" blocks * */ 
     /** $startRow is set to 1 initially because we always read the headings in row #1 * */ 
     for ($startRow = 1; $startRow <= $totalRows; $startRow += $chunkSize) { 
      echo("Loading WorkSheet for rows " . $startRow . " to " . ($startRow + $chunkSize - 1) . PHP_EOL . "<br>"); 
      $i = 0; 
      /** Tell the Read Filter, the limits on which rows we want to read this iteration * */ 
      $chunkFilter->setRows($startRow, $chunkSize); 
      /** Load only the rows that match our filter from $inputFileName to a PHPExcel Object * */ 
      $objPHPExcel = $objReader->load($filePath); 
      $sheetData = $objPHPExcel->getActiveSheet()->toArray(null, true, true, false); 

      $startIndex = ($startRow == 1) ? $startRow : $startRow - 1; 
      //dumping in database 
      if (!empty($sheetData) && $startRow < $totalRows) { 
       /** 
       * $this->dumpInDb(array_slice($sheetData, $startIndex, $chunkSize)); 
       */ 

       echo "<table border='1'>"; 
       foreach ($sheetData as $key => $value) { 
        $i++; 
        if ($value[0] != null) { 
         echo "<tr><td>id:$i</td><td>{$value[0]} </td><td>{$value[1]} </td><td>{$value[2]} </td><td>{$value[3]} </td></tr>"; 
        } 
       } 
       echo "</table><br/><br/>"; 
      } 
      $objPHPExcel->disconnectWorksheets(); 
      unset($objPHPExcel, $sheetData); 
     } 
     echo("File " . $filePath . " has been uploaded successfully in database" . PHP_EOL . "<br>"); 
    } 

    /** 
    * Insert data into database table 
    * @param Array $sheetData 
    * @return boolean 
    * @throws Exception 
    * THE METHOD FOR THE DATABASE IS NOT WORKING, JUST THE PUBLIC METHOD.. 
    */ 
    protected function dumpInDb($sheetData) { 

     $con = DbAdapter::getDBConnection(); 
     $query = "INSERT INTO employe(name,address)VALUES"; 

     for ($i = 1; $i < count($sheetData); $i++) { 
      $query .= "(" . "'" . mysql_escape_string($sheetData[$i][0]) . "'," 
        . "'" . mysql_escape_string($sheetData[$i][1]) . "')"; 
     } 

     $query = trim($query, ","); 
     $query .="ON DUPLICATE KEY UPDATE name=VALUES(name), 
       =VALUES(address), 
       "; 
     if (mysqli_query($con, $query)) { 
      mysql_close($con); 
      return true; 
     } else { 
      mysql_close($con); 
      throw new Exception(mysqli_error($con)); 
     } 
    } 

    /** 
    * This function returns list of files corresponding to given directory path 
    * @param String $dataFolderPath 
    * @return Array list of file 
    */ 
    protected function getFileList($dataFolderPath) { 
     if (!is_dir($dataFolderPath)) { 
      throw new Exception("Directory " . $dataFolderPath . " is not exist"); 
     } 
     $root = scandir($dataFolderPath); 
     $fileList = array(); 
     foreach ($root as $value) { 
      if ($value === '.' || $value === '..') { 
       continue; 
      } 
      if (is_file("$dataFolderPath/$value")) { 
       $fileList[] = "$dataFolderPath/$value"; 
       continue; 
      } 
     } 
     return $fileList; 
    } 

} 

$inputFileName = './prueba_para_batch.xls'; 
$excelReader = new Excelreader(); 
$excelReader->readFileAndDumpInDB($inputFileName, 500); 
Cuestiones relacionadas