問題
用python的pymssql模塊讀取舊業務系統后台SQL Server 2000數據庫展示數據為亂碼
開發環境
- 操作系統:windows 8
- 數據庫 MS SQL Server 2000,默認配置
- python 2.7.6
- pymssql 2.1.1
- 開發工具:PyCharm 4.0
業務邏輯
數據庫的[rooms]表記錄一些功能房間列表,與其他接口數據進行對比,然后輸出對比結果。
rooms表結構:
CREATE TABLE [rooms] ( [id] [int] IDENTITY (1, 1) NOT NULL , [name] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL , PRIMARY KEY CLUSTERED ([id] ON [PRIMARY] , UNIQUE NONCLUSTERED ([des]) ON [PRIMARY] ) ON [PRIMARY] GO
模擬代碼
# -*- coding: utf-8 -*- import pymssql rooms=None with pymssql.connect(host='192.168.1.100',database='builds', user='sa',password='password', # charset='utf8', ) as conn: cur=conn.cursor() sql="select id,name from rooms" cur.execute(sql) rooms=cur.fetchall() if rooms and isinstance(rooms,(list,tuple)): for room_id,room_name in rooms: print "\t".join([str(room_id),room_name])
在通用環境中運行代碼,room_name變量列顯示亂碼
問題分析
- 調整連接字符集
首先想到的解決辦法是,指定pymssql.connect參數charset的字符集值,使得內外數據編碼一致。
依據,“默認情況下,SQL Server 2000使用ISO字符集(代碼頁1252)。這個字符集也叫ISO-8859-1 Latin1 或者ANSI字符集。它和Windows9x及Windows NT/2000操作系統相兼容,提供了與大多數語言最大兼容性。SQL Server2000中還包含代碼頁936(簡體中文),該字符集包含對簡體中文支持的字符”,將charset設置為gbk或cp936,更為合適。查看pymssql使用文檔,發現官方沒有給出此參數可接收的實例字符串。進行猜測性調試:
<charset='gbk'>運行拋出異常:pymssql.OperationalError: (20017, 'DB-Lib error message 20017, severity 9:\nUnexpected EOF from the server\nDB-Lib error message 20002, severity 9:\nAdaptive Server connection failed\n')
<charset='cp936'>調試模式下pymsql.connect無異常信息,但程序直接退出
<charset='utf8'>運行正常,輸出依然亂碼;不指定此參數值時,程序使用默認值'UTF-8'
結論:此路不通 - 特定字符串調試
使用PyCharm調試程序,選定特定room_name值,來進行分析
# 注意此時輸出標記為u,說明識別為unicode編碼,正常時此時print出是真實值 >>> room_name u'\xbf\xec\xb5\xdd\xbc\xe4\xa3\xa8\xc3\xc5\xc4\xda\xa3\xa9' # 打印原始值為亂碼,所以懷疑實際存儲的是被標記為unicode的其他編碼 >>> print room_name ¿ìµÝ¼ä£¨ÃÅÄÚ£© # 這時可以將引號內賦值,再使用chardet.detect()判斷 >>> aa='\xbf\xec\xb5\xdd\xbc\xe4\xa3\xa8\xc3\xc5\xc4\xda\xa3\xa9' >>> aa '\xbf\xec\xb5\xdd\xbc\xe4\xa3\xa8\xc3\xc5\xc4\xda\xa3\xa9' # 果然,檢測出的結果是GB2312編碼 >>> chardet.detect(aa) {'confidence': 0.99, 'encoding': 'GB2312'} # 輸出正常 >>> print aa.decode('gb2312') 快遞間(門內) # 此時,需要unicode->encode('Latin1')->decode('GB2312') >>> room_name.encode('latin1').decode('GB2312') u'\u5feb\u9012\u95f4\uff08\u95e8\u5185\uff09' >>> print room_name.encode('latin1').decode('GB2312') 快遞間(門內)
解決辦法
pymssql基礎實現使用的是cpython,從GitHub的官方代碼文件_mssql.pyx,可以看到一些處理過程。使用strcpy函數對數據交換,因為對cpython不了解,懷疑是在處理雙字節文字轉碼時的一點bug。
這個問題有兩個解決辦法:
- 代碼中顯式轉碼
方法:unicode變量.encode('latin1').decode('gbk'),詳細情況可以參考下方的“PYTHON-進階-編碼處理小結”
一般情況下對unicode編碼不做encode處理,但必要時可以encode為Latin1,實現脫unicode操作,然后再以合適字符集decode為正確unicode
print "\t".join([str(room_id),room_name.encode('latin1').decode('gbk')])
- 字符定義使用NVARCHAR
這種方式在存儲和讀取時都使用unicode編碼,和python運轉字節碼一致,可以很好避免此類問題。當然數據庫存儲空間要犧牲一些。
[room_name] [nvarchar] (50) COLLATE Chinese_PRC_CI_AS NULL
原文:這里
參考:
1)"UnicodeDecodeError: ‘gbk’ codec can’t decode bytes in position 2-3: illegal multibyte sequence"
2)水木社區:用pymssql的時候出現了很詭異的字符集問題