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

PHP fgetcsv 讀取 CSV 亂碼完美解決:編碼轉換與 Locale 設定

     編輯:PHP 綜合知識

CSV 匯入是常見需求,但編碼問題頻發:Windows 產出的 CSV 多為 Big5/GBK,Linux 環境預設 UTF-8,直接 fgetcsv 會產生亂碼。本文提供雙重解決方案:PHP 端編碼轉換與系統 Locale 設定。

一、問題重現

<?php
function get_csv_contents($file_target) {
    $handle = fopen($file_target, 'r');
    while ($data = fgetcsv($handle, 1000, ",")) {
        $num = count($data);
        echo "

$num fields in line $row:
\n"; $row++; for ($c = 0; $c < $num; $c++) { echo $data[$c] . "
\n"; // 這裡出現亂碼 } } fclose($handle); } ?>

原因: CSV 為 ANSI/GBK 編碼,PHP 腳本執行環境為 UTF-8,瀏覽器以 UTF-8 顯示 → 亂碼。

二、方案一:PHP 端編碼轉換(推薦,可攜性佳)

<?php
function readCsvAutoEncoding($filePath, $targetEncoding = 'UTF-8') {
    $handle = fopen($filePath, 'r');
    if (!$handle) throw new Exception("無法開啟檔案: $filePath");
    
    $rows = [];
    // 偵測編碼(簡易版:嘗試常見編碼)
    $encodings = ['GBK', 'BIG5', 'UTF-8', 'UTF-16LE', 'UTF-16BE'];
    
    while (($data = fgetcsv($handle, 0, ",")) !== false) {
        $converted = [];
        foreach ($data as $cell) {
            $detected = mb_detect_encoding($cell, $encodings, true);
            if ($detected && $detected !== $targetEncoding) {
                $converted[] = mb_convert_encoding($cell, $targetEncoding, $detected);
            } else {
                $converted[] = $cell;
            }
        }
        $rows[] = $converted;
    }
    fclose($handle);
    return $rows;
}

// 使用
$rows = readCsvAutoEncoding('data.csv');
foreach ($rows as $row) {
    print_r($row);
}
?>

快速修正:已知來源編碼

<?php
// 明確指定來源為 GBK
$data = fgetcsv($handle, 1000, ",");
$data = array_map(function($cell) {
    return mb_convert_encoding($cell, 'UTF-8', 'GBK');
}, $data);

// 或使用 eval 技巧(原文方法,不推薦生產環境)
$data = eval('return ' . iconv('GBK', 'UTF-8', var_export($data, true)) . ';');
?>

三、方案二:系統 Locale 設定(Linux 伺服器必備)

fgetcsv 依賴 C 函式庫 strtok 解析,受 LC_CTYPE 影響。若系統 Locale 不支援來源編碼,解析即會錯誤(不只是顯示亂碼,連欄位分割都會錯)。

檢查系統支援 Locale

# Linux 終端機執行
locale -a | grep -E 'zh_(CN|TW)'
# 輸出範例:
# zh_CN.gb2312
# zh_CN.gbk
# zh_CN.gb18030
# zh_CN.utf8
# zh_TW.big5
# zh_TW.utf8

PHP 設定 Locale

<?php
// 在 fgetcsv 前設定
$localeSet = setlocale(LC_ALL, [
    'zh_CN.gbk',
    'zh_CN.gb2312', 
    'zh_CN.gb18030',
    'zh_TW.big5',
    'C' // 兜底
]);

if ($localeSet === false) {
    error_log('Warning: 無法設定中文 Locale,CSV 解析可能異常');
}

// 現在可正確解析 GBK/Big5 CSV
$handle = fopen('data.csv', 'r');
while (($data = fgetcsv($handle, 0, ",")) !== false) {
    // $data 已正確分割,但編碼仍為原始編碼
    // 後續仍需 mb_convert_encoding 轉 UTF-8
}
fclose($handle);
?>

四、完整最佳實踐:CSV 匯入服務類

<?php
class CsvImporter {
    private $sourceEncoding;
    private $targetEncoding = 'UTF-8';
    
    public function __construct($sourceEncoding = 'auto') {
        $this->sourceEncoding = $sourceEncoding;
    }
    
    public function import($filePath, callable $rowHandler) {
        // 1. 設定 Locale 確保正確分割
        $this->setLocaleForEncoding($this->sourceEncoding);
        
        $handle = fopen($filePath, 'r');
        if (!$handle) throw new Exception("檔案不存在或不可讀: $filePath");
        
        $headers = null;
        $rowNum = 0;
        
        while (($data = fgetcsv($handle, 0, ",")) !== false) {
            $rowNum++;
            
            // 2. 編碼轉換
            if ($this->sourceEncoding !== 'auto') {
                $data = $this->convertRow($data, $this->sourceEncoding);
            } else {
                $data = $this->autoConvertRow($data);
            }
            
            // 3. 標題列處理
            if ($rowNum === 1) {
                $headers = $data;
                continue;
            }
            
            // 4. 組合關聯陣列
            if ($headers) {
                $row = array_combine($headers, $data);
            } else {
                $row = $data;
            }
            
            // 5. 交給呼叫端處理
            $rowHandler($row, $rowNum);
        }
        
        fclose($handle);
    }
    
    private function setLocaleForEncoding($encoding) {
        $localeMap = [
            'GBK' => ['zh_CN.gbk', 'zh_CN.gb2312', 'zh_CN.gb18030'],
            'BIG5' => ['zh_TW.big5'],
            'UTF-8' => ['zh_CN.utf8', 'zh_TW.utf8', 'en_US.utf8'],
        ];
        $locales = $localeMap[strtoupper($encoding)] ?? ['C'];
        setlocale(LC_ALL, $locales);
    }
    
    private function convertRow($row, $fromEncoding) {
        return array_map(fn($cell) => mb_convert_encoding($cell, $this->targetEncoding, $fromEncoding), $row);
    }
    
    private function autoConvertRow($row) {
        return array_map(function($cell) {
            $detected = mb_detect_encoding($cell, ['GBK', 'BIG5', 'UTF-8'], true);
            return $detected && $detected !== $this->targetEncoding 
                ? mb_convert_encoding($cell, $this->targetEncoding, $detected)
                : $cell;
        }, $row);
    }
}

// 使用範例
$importer = new CsvImporter('GBK'); // 或 'auto'
$importer->import('data.csv', function($row, $rowNum) {
    // $row 為 ['欄位名' => '值', ...] 已轉 UTF-8
    User::create($row);
});
?>

五、關鍵差異總結

層面方案一:PHP 轉碼方案二:Locale 設定
解決問題顯示層亂碼解析層欄位錯誤
適用場景已正確分割欄位,僅編碼不符中文欄位含逗號、引號導致分割錯誤
可攜性高(純 PHP)依賴系統安裝的 Locale
效能略低(逐儲存格轉碼)高(C 層級正確解析)
建議必須做Linux 伺服器必須做

核心結論:Locale 確保正確分割,PHP 轉碼確保正確顯示,兩者緩一不可,共同構建健壯的 CSV 匯入管道。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved