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

PHP 匯入 CSV 檔案亂碼完整解決方案:Windows 與 Linux 跨平台編碼處理

     編輯:PHP 相關知識

前言

CSV 是資料交換最通用的格式,但跨平台(Windows ↔ Linux)匯入時,編碼問題層出不窮:中文變亂碼、欄位錯位、甚至導入失敗。本文系統整理兩大核心問題的成因與解決方案,並提供可直接部署的完整導入代碼。

問題一:Windows 環境中文亂碼

成因

Windows 繁體/簡體中文版預設編碼為 CP950 (Big5) / CP936 (GBK),而 PHP 內部與資料庫多使用 UTF-8。直接用 fgetcsv() 讀取 GBK/Big5 編碼的 CSV,中文會被誤判為單字節字元,導致截斷或亂碼。

解決方案

<?php
// 讀取每行後,立即轉換編碼
$line = fgetcsv($handle, 1000, ',');
if ($line) {
    // 方法一:mb_convert_encoding(需安裝 mbstring 擴充)
    $line = array_map(function($v) {
        return mb_convert_encoding($v, 'UTF-8', 'GBK'); // 或 'CP950'、'Big5'
    }, $line);

    // 方法二:iconv(較通用)
    $line = array_map(function($v) {
        return iconv('GBK', 'UTF-8//IGNORE', $v); // IGNORE 忽略無法轉換字元
    }, $line);
}
?>

問題二:Linux 環境 fgetcsv() 讀取單位元編碼錯誤

成因

Linux 伺服器 Locale 常設為 en_US.UTF-8。fgetcsv() 對 Locale 設定敏感:當 Locale 為 UTF-8 時,遇到單位元編碼(GBK/Big5)的 CSV,會因多字節字元被誤判為分隔符或換行,導致欄位錯位、行數錯誤。

解決方案:設定正確 Locale

<?php
// 在 fopen/fgetcsv 之前呼叫,僅影響當前程序
setlocale(LC_ALL, 'zh_CN.GBK');   // 簡體中文 GBK
// 或
setlocale(LC_ALL, 'zh_TW.Big5');  // 繁體中文 Big5
// 或通用
setlocale(LC_ALL, 'zh_CN');       // 視系統安裝的 locale 而定

// 驗證是否生效
var_dump(setlocale(LC_CTYPE, 0)); // 輸出當前 LC_CTYPE 設定
?>

關鍵點:

  • 系統必須已安裝對應 locale(執行 locale -a 查看)。
  • 若系統缺少 zh_CN.GBK,需安裝語言包(Ubuntu: apt-get install language-pack-zh-hans)。
  • setlocale() 設定的是 進程級 Locale,不影響其他 PHP-FPM worker 或系統全域設定。

完整可用導入腳本(支援雙平台)

<?php
/**
 * 通用 CSV 導入類
 * 自動偵測編碼、設定 Locale、批次寫入資料庫
 */
class CsvImporter {
    private $handle;
    private $encoding = 'UTF-8';   // 目標編碼
    private $source_encoding = 'GBK'; // 來源編碼(可由 mb_detect_encoding 偵測)
    private $locale_set = false;

    public function __construct(string $file_path) {
        // 1. 設定 Locale(解決 Linux fgetcsv 單位元問題)
        $this->setLocale();

        // 2. 開啟檔案
        $this->handle = fopen($file_path, 'r');
        if (!$this->handle) throw new Exception("無法開啟檔案: {$file_path}");

        // 3. 偵測來源編碼(讀取前幾行樣本)
        $sample = fread($this->handle, 8192);
        rewind($this->handle);
        $detected = mb_detect_encoding($sample, ['GBK', 'BIG5', 'UTF-8', 'CP950'], true);
        if ($detected) $this->source_encoding = $detected;
    }

    private function setLocale(): void {
        if ($this->locale_set) return;
        $locales = ['zh_CN.GBK', 'zh_CN', 'zh_TW.Big5', 'zh_TW', 'C'];
        foreach ($locales as $loc) {
            if (setlocale(LC_ALL, $loc) !== false) {
                $this->locale_set = true;
                break;
            }
        }
        if (!$this->locale_set) {
            trigger_error('無法設定任何中文 Locale,fgetcsv 可能異常', E_USER_WARNING);
        }
    }

    public function import(callable $row_handler, int $batch_size = 100): int {
        $count = 0;
        $batch = [];

        // 跳過標題列(視需求)
        fgetcsv($this->handle);

        while (($row = fgetcsv($this->handle, 4096, ',')) !== false) {
            // 編碼轉換
            $row = array_map(function($v) {
                return mb_convert_encoding($v, $this->encoding, $this->source_encoding);
            }, $row);

            $batch[] = $row;
            $count++;

            if (count($batch) >= $batch_size) {
                $row_handler($batch);
                $batch = [];
            }
        }

        if ($batch) $row_handler($batch);
        fclose($this->handle);
        return $count;
    }
}

// === 使用範例 ===
try {
    $importer = new CsvImporter($_FILES['csv_file']['tmp_name']);

    $imported = $importer->import(function($rows) {
        // $rows 為二維陣列,每筆已轉為 UTF-8
        foreach ($rows as $row) {
            // 寫入資料庫(PDO 範例)
            $stmt = $pdo->prepare("INSERT INTO products (name, price, description) VALUES (?, ?, ?)");
            $stmt->execute([$row[0], $row[1], $row[2]]);
        }
    });

    echo "成功導入 {$imported} 筆資料";
} catch (Exception $e) {
    echo "導入失敗: " . $e->getMessage();
}
?>

常見編碼對照表

Locale 識別碼適用編碼說明
zh_CN.GBK / zh_CNGBK / CP936大陸簡體中文預設
zh_TW.Big5 / zh_TWBig5 / CP950台灣繁體中文預設
zh_HK.Big5-HKSCSBig5-HKSCS香港繁體擴展字符集
en_US.UTF-8UTF-8英文系統預設,讀取單位元 CSV 會出錯

其他常見坑點與對策

  • BOM 標記:UTF-8 CSV 可能含 BOM (EF BB BF),導致第一欄位多出隱形字元。讀取首行後用 ltrim($row[0], "\xEF\xBB\xBF") 移除。
  • 換行符差異:Windows CRLF、Linux LF、舊 Mac CR。PHP 5.3+ fgetcsv() 已支援 auto_detect_line_endings,可在開啟檔案前設定 ini_set('auto_detect_line_endings', true)。
  • 欄位含逗號/引號:CSV 標準要求用雙引號包裹含分隔符的欄位,fgetcsv() 會自動處理,但若資料本身含雙引號需轉義(雙寫 "")。
  • 大檔案記憶體:使用 fgetcsv() 逐行讀取而非 file() 一次載入全檔,配合批次寫入資料庫,避免記憶體耗盡。

總結

CSV 匯入亂碼的核心在於 來源編碼識別 與 Locale 正確設定。採用「偵測編碼 → 設定 Locale → 逐行讀取 → 即時轉碼 → 批次入庫」的標準化流程,並封裝為可複用類別,即可一次解決 Windows/Linux 跨平台所有編碼難題。

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