
CSV、Excel 與資料處理的中文編碼問題
中文 CSV 的編碼問題
這是實務上最常遇到的坑。同一個檔案,用不同軟體開啟可能出現亂碼。
問題的來源
- Excel 在部分環境下預設用系統編碼開啟 CSV,而非 UTF-8
- 舊系統可能匯出 Big5 編碼
- 不同來源的檔案編碼不一致
讓 Excel 正確開啟的做法
在 UTF-8 檔案開頭加上位元組順序記號,Excel 就能正確辨識:
import csv
with open('output.csv', 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f)
writer.writerow(['標題', '內容', '日期'])
writer.writerow(['測試', '中文內容', '2026-08-05'])
關鍵是 encoding='utf-8-sig'——它會自動加上記號。
兩個必要的參數
newline=''——不加的話在某些系統會產生多餘的空行encoding——明確指定,不要依賴系統預設
讀取來源不明的檔案
def read_csv_auto(path):
encodings = ['utf-8-sig', 'utf-8', 'cp950', 'big5']
for enc in encodings:
try:
with open(path, newline='', encoding=enc) as f:
return list(csv.DictReader(f)), enc
except UnicodeDecodeError:
continue
raise ValueError('無法判斷編碼')
注意 cp950 要放在 big5 前面——它是 Big5 的擴充,涵蓋較多字元。
Big5 的字元缺漏
若需要輸出 Big5 給舊系統,部分字元可能無法轉換——例如某些罕用字、特殊符號、以及使用者造字。
text = '測試內容'
try:
data = text.encode('cp950')
except UnicodeEncodeError as e:
print(f'無法轉換的字元位置: {e.start}')
# 可改用替代字元或 HTML 實體
data = text.encode('cp950', errors='xmlcharrefreplace')
實務建議:轉換前先檢查哪些字元會有問題,決定是替換、改用實體標記,還是回報給來源修正。
用 DictReader 與 DictWriter
處理有標題列的 CSV 時,用字典的方式比索引可靠得多:
import csv
# 讀取
with open('input.csv', newline='', encoding='utf-8-sig') as f:
rows = list(csv.DictReader(f))
for row in rows:
print(row['title'], row['slug'])
# 寫出
fields = ['title', 'slug', 'content']
with open('output.csv', 'w', newline='', encoding='utf-8-sig') as f:
w = csv.DictWriter(f, fieldnames=fields)
w.writeheader()
w.writerows(rows)
好處是欄位順序改變時程式不會壞掉,也比 row[3] 這種寫法容易理解。
欄位中包含換行或逗號
CSV 模組會自動處理引號跳脫,不要自己用字串拼接產生 CSV。
# 不要這樣做
line = f'{title},{content}\n' # content 有逗號就爆了
# 應該用 csv 模組
writer.writerow([title, content])
內容含 HTML 或多行文字時,這一點特別重要。
Excel 檔案的處理
from openpyxl import load_workbook, Workbook
# 讀取
wb = load_workbook('data.xlsx', data_only=True)
ws = wb.active
for row in ws.iter_rows(min_row=2, values_only=True):
print(row)
# 寫出
wb = Workbook()
ws = wb.active
ws.append(['標題', '數量'])
ws.append(['測試', 100])
wb.save('output.xlsx')
data_only=True 會讀取公式的計算結果而非公式本身——多數情況這才是你要的。
注意事項
- 舊的
.xls格式需要不同的套件 - 大型檔案讀取時考慮使用唯讀模式以節省記憶體
- 儲存格格式與樣式需要另外處理
JSON 處理
import json
# 讀取
with open('data.json', encoding='utf-8') as f:
data = json.load(f)
# 寫出(中文不要被轉成跳脫序列)
with open('output.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
ensure_ascii=False 很重要——不加的話中文會變成一堆跳脫字元,雖然功能正常但完全無法閱讀。
批次重新命名檔案
from pathlib import Path
import re
folder = Path('/path/to/images')
for f in folder.glob('*.jpg'):
# 轉小寫、空格改連字號、移除特殊字元
new_name = re.sub(r'[^a-z0-9\-.]', '',
f.name.lower().replace(' ', '-'))
if new_name != f.name:
target = f.with_name(new_name)
if target.exists():
print(f'略過(已存在): {new_name}')
continue
f.rename(target)
print(f'{f.name} -> {new_name}')
三個安全習慣
- 先跑一次「只印出不執行」的版本,確認結果符合預期
- 檢查目標檔名是否已存在,避免覆蓋
- 處理前備份
檔名對搜尋的影響見圖片的替代文字與檔名該怎麼寫。
資料清理的常見需求
import re
def clean_text(s):
if s is None:
return ''
s = str(s)
s = s.replace('\u00a0', ' ') # 不斷行空白
s = re.sub(r'[ \t]+', ' ', s) # 連續空白
s = re.sub(r'\n{3,}', '\n\n', s) # 過多空行
return s.strip()
不斷行空白是最常見的隱形問題——它看起來像空格但不是,會造成比對失敗或版面異常。
處理大檔案
不要一次把整個檔案載入記憶體:
with open('large.csv', newline='', encoding='utf-8-sig') as f:
for row in csv.DictReader(f):
process(row) # 逐列處理
需要輸出時同樣逐列寫入,記憶體用量就與檔案大小無關。
本文的範例以 Python 3 為例,實際的套件版本與 API 可能隨版本更新而異。在正式環境執行任何批次處理前,請先以少量資料測試並確實備份。