首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 讀取 Excel/CSV 檔案完整指南:fgetcsv、PhpSpreadsheet、匯入資料庫實戰

     編輯:PHP 相關知識

前言

讀取 Excel/CSV 是資料匯入、報表分析的常見需求。PHP 提供內建 fgetcsv 處理 CSV,若需支援真正的 .xls/.xlsx(多工作表、公式、樣式),則需第三方套件 PhpSpreadsheet(已取代舊版 PHPExcel)。本文涵蓋 CSV 讀取、編碼處理、大檔分塊、.xlsx 讀取、以及「CSV 匯入 MySQL」完整實戰範例。

一、內建 fgetcsv 讀取 CSV

基本語法

array fgetcsv ( resource $handle [, int $length = 0 [, string $delimiter = "," [, string $enclosure = '"' [, string $escape = "\\" ]]]] )
  • $handle:fopen() 取得的檔案指標。
  • $length:單行最大長度,0 或省略為不限制(PHP 5.1+)。
  • $delimiter:分隔符,預設逗號。
  • $enclosure:包裹字元,預設雙引號。
  • 回傳:解析後的陣列;EOF 回傳 false;空行回傳 ['']。

範例:逐行讀取並轉 UTF-8

<?php
$file = 'data.csv';
$handle = fopen($file, 'r');
if (!$handle) die('無法開啟檔案');

$rowNum = 0;
while (($data = fgetcsv($handle, 0, ',')) !== false) {
    $rowNum++;
    // 跳過空行
    if (count($data) === 1 && $data[0] === '') continue;

    // Big5/GBK 轉 UTF-8
    $data = array_map(function($v) {
        return iconv('big5', 'utf-8//IGNORE', $v); // 依實際檔案編碼調整
    }, $data);

    if ($rowNum === 1) {
        $header = $data; // 第一列當標題
        continue;
    }
    $record = array_combine($header, $data);
    // 處理 $record ...
    print_r($record);
}
fclose($handle);

進階:大檔分塊處理

避免一次載入所有資料到記憶體:

function csvGenerator($filepath, $encoding = 'big5') {
    $handle = fopen($filepath, 'r');
    $header = null;
    while (($row = fgetcsv($handle)) !== false) {
        if (count($row) === 1 && $row[0] === '') continue;
        $row = array_map(fn($v) => iconv($encoding, 'utf-8//IGNORE', $v), $row);
        if ($header === null) { $header = $row; continue; }
        yield array_combine($header, $row);
    }
    fclose($handle);
}

// 使用:每次只佔用單行記憶體
foreach (csvGenerator('huge.csv') as $record) {
    // 寫入 DB、送 API、運算...
}

二、通用 CSV 解析函式(自動偵測標題、編碼轉換)

/**
 * 讀取 CSV 回傳關聯式陣列陣列
 * @param string $filepath
 * @param string $inputEncoding 檔案編碼:big5, gbk, utf-8 等
 * @param string $delimiter 分隔符
 * @return array
 */
function readCsvToArray($filepath, $inputEncoding = 'big5', $delimiter = ',') {
    $handle = fopen($filepath, 'r');
    if (!$handle) throw new RuntimeException("無法開啟:$filepath");

    $header = null;
    $data = [];
    while (($row = fgetcsv($handle, 0, $delimiter)) !== false) {
        if (count($row) === 1 && $row[0] === '') continue;
        if (strtolower($inputEncoding) !== 'utf-8') {
            $row = array_map(fn($v) => iconv($inputEncoding, 'utf-8//IGNORE', $v), $row);
        }
        if ($header === null) { $header = $row; continue; }
        // 欄位數對齊
        if (count($row) < count($header)) $row = array_pad($row, count($header), '');
        elseif (count($row) > count($header)) $row = array_slice($row, 0, count($header));
        $data[] = array_combine($header, $row);
    }
    fclose($handle);
    return $data;
}

三、讀取真正的 Excel (.xls/.xlsx) — PhpSpreadsheet

安裝:composer require phpoffice/phpspreadsheet

基本讀取範例

<?php
require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;

$file = 'report.xlsx';
try {
    $spreadsheet = IOFactory::load($file);
} catch (ReaderException $e) {
    die('讀取失敗:' . $e->getMessage());
}

$sheet = $spreadsheet->getActiveSheet(); // 或 getSheetByName('Sheet2')
$rows = $sheet->toArray(null, true, true, true); // 索引從 A1 開始的關聯式陣列

// $rows 結構:[ 1 => ['A' => 值, 'B' => 值...], 2 => ... ]
// 若第一列是標題,可自行轉關聯式:
$header = array_values($rows[1] ?? []);
unset($rows[1]);
$data = [];
foreach ($rows as $row) {
    $data[] = array_combine($header, array_values($row));
}
print_r($data);

大檔優化:ReadFilter 只讀需要的欄位/列

use PhpOffice\PhpSpreadsheet\Reader\IReadFilter;

class MyReadFilter implements IReadFilter {
    public function readCell($column, $row, $worksheetName = '') {
        // 只讀 A~F 欄,第 1~1000 列
        return in_array($column, range('A', 'F')) && $row >= 1 && $row <= 1000;
    }
}

$reader = IOFactory::createReader('Xlsx');
$reader->setReadFilter(new MyReadFilter());
$reader->setReadDataOnly(true); // 不讀樣式、公式,極大加快速度
$spreadsheet = $reader->load('huge.xlsx');

四、實戰:CSV 匯入 MySQL 資料庫

資料表

CREATE TABLE `members` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `name` VARCHAR(50) NOT NULL,
  `email` VARCHAR(100) NOT NULL,
  `phone` VARCHAR(20),
  `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

匯入腳本(含交易、錯誤處理、進度顯示)

<?php
// config
$db = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'user', 'pass');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$csvFile = 'members_import.csv';
$batchSize = 500; // 每批 commit 筆數

$handle = fopen($csvFile, 'r');
if (!$handle) die('檔案不存在');

$header = null;
$stmt = $db->prepare('INSERT INTO `members` (`name`, `email`, `phone`) VALUES (?, ?, ?)');
$count = 0;
$success = 0;
$errors = [];

$db->beginTransaction();
try {
    while (($row = fgetcsv($handle)) !== false) {
        $count++;
        if (count($row) === 1 && $row[0] === '') continue;

        // 編碼轉換(假設 CSV 為 Big5)
        $row = array_map(fn($v) => iconv('big5', 'utf-8//IGNORE', $v), $row);

        if ($header === null) {
            $header = $row; // 第一列標題
            continue;
        }

        $record = array_combine($header, $row);
        // 簡單驗證
        if (empty($record['name']) || empty($record['email'])) {
            $errors[] = "第 $count 列:姓名或 Email 為空";
            continue;
        }

        $stmt->execute([
            $record['name'],
            $record['email'],
            $record['phone'] ?? null
        ]);
        $success++;

        // 批次 commit
        if ($success % $batchSize === 0) {
            $db->commit();
            $db->beginTransaction();
            echo "已匯入 $success 筆...
"; ob_flush(); flush(); } } $db->commit(); } catch (Exception $e) { $db->rollBack(); die('匯入失敗:' . $e->getMessage()); } fclose($handle); echo "完成!共讀取 $count 列,成功匯入 $success 筆。"; if ($errors) { echo "
錯誤列表:
" . implode("\n", $errors) . "
"; }

關鍵點

  1. 交易批次 Commit:每 500 筆 commit 一次,平衡效能與回滾粒度。
  2. Prepared Statement:防 SQL 注入、自動處理引號逸出。
  3. 編碼轉換:CSV 常為 Big5/GBK,入庫前統一轉 UTF-8。
  4. 錯誤收集:不因單列錯誤中斷整批,記錄後繼續。

五、上傳檔案匯入完整流程(HTML + PHP)

<!-- upload.html -->
<form action="import.php" method="post" enctype="multipart/form-data">
  選擇 CSV 檔:<input type="file" name="csv_file" accept=".csv" required>
  <button type="submit">匯入資料庫</button>
</form>
<?php
// import.php
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || empty($_FILES['csv_file'])) {
    die('請上傳檔案');
}

$file = $_FILES['csv_file'];
if ($file['error'] !== UPLOAD_ERR_OK) {
    die('上傳錯誤代碼:' . $file['error']);
}

// 移至暫存目錄
$tmp = sys_get_temp_dir() . '/import_' . uniqid() . '.csv';
move_uploaded_file($file['tmp_name'], $tmp);

// 呼叫上方的匯入邏輯(建議封裝為函式/類別)
require 'import_logic.php'; // 包含 PDO 連線、匯入流程
// import_csv($tmp, $db); // 假設封裝好的函式

unlink($tmp); // 清理暫存檔

六、常見問題與解法

問題解決方案
fgetcsv 讀到一半停止/欄位錯亂檢查 CSV 是否有未閉合雙引號、欄位內含換行;必要時用 fgetcsv($h, 0, ',', '"', '\\') 明確指定逸出字元。
中文亂碼確認來源編碼(Big5/GBK/UTF-8),用 iconv 統一轉 UTF-8 再處理。
PhpSpreadsheet 記憶體爆掉啟用 setReadDataOnly(true)、使用 ReadFilter 只讀必要欄位/列、或改用 CSV 格式匯入。
超大檔(>100MB)上傳逾時調整 upload_max_filesize、post_max_size、max_execution_time、Nginx client_max_body_size;考慮分片上傳。
匯入速度慢關閉索引/外鍵檢查(SET FOREIGN_KEY_CHECKS=0)、批次 INSERT、使用 LOAD DATA INFILE(最快,需檔案在 DB 伺服器本機)。

總結

  • CSV:用內建 fgetcsv,搭配 Generator 處理大檔,零依賴、效能最佳。
  • .xlsx/.xls:用 PhpSpreadsheet,啟用 setReadDataOnly(true) 並用 ReadFilter 限制讀取範圍。
  • 匯入 DB:PDO Prepared Statement + 批次 Transaction + 編碼轉換 + 錯誤收集,穩健可維護。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved