讀取 Excel/CSV 是資料匯入、報表分析的常見需求。PHP 提供內建 fgetcsv 處理 CSV,若需支援真正的 .xls/.xlsx(多工作表、公式、樣式),則需第三方套件 PhpSpreadsheet(已取代舊版 PHPExcel)。本文涵蓋 CSV 讀取、編碼處理、大檔分塊、.xlsx 讀取、以及「CSV 匯入 MySQL」完整實戰範例。
array fgetcsv ( resource $handle [, int $length = 0 [, string $delimiter = "," [, string $enclosure = '"' [, string $escape = "\\" ]]]] )
$handle:fopen() 取得的檔案指標。$length:單行最大長度,0 或省略為不限制(PHP 5.1+)。$delimiter:分隔符,預設逗號。$enclosure:包裹字元,預設雙引號。false;空行回傳 ['']。<?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 回傳關聯式陣列陣列
* @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;
}
安裝: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);
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');
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) . "";
}
<!-- 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 伺服器本機)。 |
fgetcsv,搭配 Generator 處理大檔,零依賴、效能最佳。setReadDataOnly(true) 並用 ReadFilter 限制讀取範圍。