pymssql讀取varchar字段中文顯示亂碼的問題分析

問題

  用python的pymssql模塊讀取舊業務系統後臺SQL Server 2000數據庫展現數據爲亂碼html

開發環境

  • 操做系統:windows 8
  • 數據庫 MS SQL Server 2000,默認配置
  • python 2.7.6
  • pymssql 2.1.1
  • 開發工具:PyCharm 4.0

業務邏輯

  數據庫的[rooms]表記錄一些功能房間列表,與其餘接口數據進行對比,而後輸出對比結果。python

  rooms表結構sql

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變量列顯示亂碼windows

問題分析

  1. 調整鏈接字符集
    首先想到的解決辦法是,指定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'
    結論:此路不通
  2. 特定字符串調試
    使用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。ide

  這個問題有兩個解決辦法:函數

  1. 代碼中顯式轉碼
    方法: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')])
  2. 字符定義使用NVARCHAR
    這種方式在存儲和讀取時都使用unicode編碼,和python運轉字節碼一致,能夠很好避免此類問題。固然數據庫存儲空間要犧牲一些。
    [room_name] [nvarchar] (50) COLLATE Chinese_PRC_CI_AS NULL 

原文:這裏工具

參考:post

1)"UnicodeDecodeError: ‘gbk’ codec can’t decode bytes in position 2-3: illegal multibyte sequence"開發工具

2)水木社區:用pymssql的時候出現了很詭異的字符集問題

3)PYTHON-進階-編碼處理小結

相關文章
相關標籤/搜索