數據庫亂碼問題 & Python 編碼問題(Unicode 的 encode、decode 相互轉換 )


前言:
     
今天有個項目需要合並(A合並到B),我所做的就是數據庫的合並操作,其中出現的主要問題就是亂碼的問題。亂碼這個問題是很常見的問題,今天整理了下自己所理解的一點見解。

案例:   

NAME COMPANY RNAME
A the Feed Institute, CAAS tiezhengyuan
B ??����????????????????????????????��??��???�� ??????
C ����?????????��?��???????????? ?????��
D AB ??????
E ?????????????��?????????????????? ??????
F ???��???????��???��???????�� ??????
G ?????��302?????��?��???????????? ?��??
H ???????��?��?????????????? ?????��
I ???????????�� ??????

查看表的時候都是亂碼,根本不能進行合並。當時就認為肯定是自己查看時候的字符集不對,試了“set names = gbk、utf8、latin1”,均顯示亂碼。再懷疑是不是表結構的問題(根本不可能啊,項目都跑了好幾年了),不過表的編碼確實的用charset latin1 來建立的。而用lantin1 建立表,在數據庫層寫入數據(中文)的話,肯定會報warning,不會存成功的而且存的都是?符號,但上面的結果卻有� 符號,所以是顯示的問題,其實是正常的。經過排查確定是自己終端編碼顯示的問題,改成GBK,並且做默認的字符集連接下才能正常顯示(latin1:set names latin1),因為當時存數據就是做latin1的下面寫入的。上面說明表中的文字是GBK編碼。
注意:latin1可以存儲任何東西,包括漢字,二進制等。latin1是單字節的,存儲都會用內部的編碼去表示。只要輸入流和輸出流一致(怎么存就怎么取) 就不會出現亂碼情況。如:
         表的編碼是UTF8 ,那么在進入數據庫后,需要在utf8的字符集下才能正常(set names utf8)
         表的編碼是GBK ,那么在進入數據庫后,需要在gbk的字符集下才能正常(set names gbk)
         首先看到表的字符集編碼是什么,才能相應的set names。要是輸入結果像上面顯示的這樣,就和終端的編碼有關系了,需要自己設置下。

如何讓表字符編碼變成gbk呢?單單的alter 轉換是不行的。之后就嘗試用python來讀取插入表。
看看latin1編碼如何存漢字:(Python)

#!/usr/bin/python
#encoding: utf-8
#-------------------------------------------------------------------------------
# Name:        latin_to_gbk_bak.py
# Purpose:     gbk 編碼存到 latin1編碼
# Author:      zhoujy
# Created:     2012-10-31
# update:      2012-10-31
#-------------------------------------------------------------------------------

import MySQLdb

a=u'zhoujy'
b=u'中國'                        /*生成unicode */
c=u'周金義'
conn = MySQLdb.connect(host='localhost',user='root',passwd='123456',db='test')
cursor = conn.cursor()
   /*編碼成gbk存到latin1 的表中*/
query = "insert into users_latin(username,user_company,user_realname)    values('%s','%s','%s')" %(a.encode('gbk'),b.encode('gbk'),c.encode('gbk')) 
cursor.execute(query)
conn.commit()
print query
root@localhost : test 10:55:10>CREATE TABLE `users_latin` (
    ->   `username` varchar(25) NOT NULL DEFAULT '',
    ->   `user_realname` varchar(50) NOT NULL DEFAULT '',
    ->   `user_company` varchar(100) NOT NULL DEFAULT ''
    -> ) ENGINE=MyISAM DEFAULT CHARSET=latin1 ;
Query OK, 0 rows affected (0.07 sec)
zhoujy@zhoujy:~$ python latin_to_gbk_bak.py  1
insert into users_latin(username,user_company,user_realname) values('zhoujy','�й�','�ܽ���')
zhoujy@zhoujy:~$ python latin_to_gbk_bak.py  2
insert into users_latin(username,user_company,user_realname) values('zhoujy','中國','周金義')

root@localhost : test 10:55:14>select * from users_latin;  1 +----------+---------------+--------------+
| username | user_realname | user_company |
+----------+---------------+--------------+
| zhoujy   | �ܽ���        | �й�         |
+----------+---------------+--------------+
1 row in set (0.00 sec)

root@localhost : test 10:56:06>select * from users_latin;  2 +----------+---------------+--------------+
| username | user_realname | user_company |
+----------+---------------+--------------+
| zhoujy   | 周金義        | 中國         |
+----------+---------------+--------------+
1 row in set (0.00 sec)

上面的信息說明:latin1確實可以存中文。(在set names latin1下面讀的)
      <腳本>向latin1編碼的表中插入數據,執行腳本,第一次顯示亂碼,第二次顯示正常,他們的區別是 輸出流是gbk,而 系統默認/終端默認 的是utf8,所以出現不同的結果。(第二次把 系統默認/終端默認 編碼改成了gbk)
      <數據表> 表的編碼是latin1,並且當時的字符集是latin1(set names latin1,要和表編碼一致)。第一次顯示亂碼,第二次顯示正常,他們的區別是 輸出流是gbk,而 系統默認/終端默認 的是utf8,所以出現不同的結果。(第二次把 系統默認/終端默認 編碼改成了gbk)
其實也可以把latin1存儲漢字的表備份出來,文件中要是最新字符集都是有效的,可以用vi可以直接編輯查看。要是出現無效的編碼則也可以用: iconv 來轉碼之后再還原。

cat users.sql | iconv -c -f gbk  > users_qq.sql

 當然,還原之后也是latin1 字符集下查看的。

解析:
       1,Mysql數據庫亂碼:這個可以做網上搜到很多信息,就不細說了。只要知道怎么存就怎么取。(有時候set names utf8並且終端也是utf8 可以直接對gbk編碼的表進行讀取)
       2,Python 編碼問題,涉及到Unicode 的 encode、decode 相互轉換 。(可能做執行中出:“UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)” 的錯誤信息。)

知識點:
     
decode的作用是將其他編碼的字符串轉換成unicode編碼,如str1.decode('gb2312'),表示將gb2312編碼的字符串轉換成unicode編碼。

      encode的作用是將unicode編碼轉換成其他編碼的字符串,如str2.encode('gb2312'),表示將unicode編碼的字符串轉換成gb2312編碼。
      字符串在Python內部的表示是unicode編碼,因此,在做編碼轉換時,通常需要以unicode作為中間編碼,即先將其他編碼的字符串解碼(decode)成unicode,再從unicode編碼(encode)成另一種編碼。
測試:
     
系統默認編碼是UTF8,生成一個文件,用GBK編碼生成中文。

zhoujy@zhoujy:~$ cat gbk.txt 
�ܽ��
>>> f=open('gbk.txt')
>>> d=f.read()
>>> print d
�ܽ��

>>> print d.decode('gbk') #轉換成了unicode
周金義

>>> print d.decode('gbk').encode('utf8')
周金義

 從上面的信息得到:當讀出文本時候還是亂碼,因為cat 默認就是按照文本的編碼讀出,python中的read也是一樣。但做python中默認的是unicode編碼。按照知識點理的說明需要轉換成為unicode才能正常顯示。最后還需要把unicode轉換成系統默認的文件才行。驗證:

>>> f=open('gbk.txt')
>>> a=f.read()
>>> gbk=a.decode('gbk')      /* 將gbk編碼的字符串轉換成unicode編碼*/
>>> gbk
u'\u5468\u91d1\u4e49\n'     /*u開頭的unicode編碼*/
>>> print gbk
周金義                      /*python里面正常顯示,因為默認就是unicode編碼*/   
                  >>> f=open('gbk.txt','w') >>> f.write(gbk)            /*寫文件*/ Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)   /*字符集的問題報錯*/ >>> utf=a.decode('gbk').encode('utf8')  /* 將gbk編碼的字符串轉換成unicode編碼*,再轉換成utf8格式,unicode當成中間編碼*/ >>> utf '\xe5\x91\xa8\xe9\x87\x91\xe4\xb9\x89\n' /*utf8編碼顯示*/ >>> print utf 周金義
>>> f=open('gbk.txt','w') >>> f.write(utf)            /*寫文件*/ >>> zhoujy@zhoujy:~$ cat gbk.txt   /*和第上面的比下,看到正常的文字,這是控制台信息輸出窗口按照ascii編碼輸出utf8編碼的字符串的結果。*/ 周金義

所以存到文件最后以系統默認的編碼存才能存的進去。

open 按照固定字符集打開:

>>> import codecs >>> f=codecs.open('gbk.txt',encoding='gbk')
>>> x=f.read() 
>>> x                                          /* 節省了 decode的步驟 */
u'\u5468\u91d1\u4e49\n'
>>> x.encode('utf8')
'\xe5\x91\xa8\xe9\x87\x91\xe4\xb9\x89\n'

一個函數:isinstance()

>>> a='周金義'
>>> u=u'周金義'
>>> isinstance(a,unicode)  /*判斷是否是unicode編碼*/
False
>>> isinstance(u,unicode)
True

回歸:
      了解了這些之后,那對數據庫表的導出腳本:
conn.character_set_name() 是顯示數據庫的鏈接字符集,可以print出來,例子理顯示為lanti1

#!/usr/bin/python
#encoding: utf-8
#-------------------------------------------------------------------------------
# Name:        latin_to_gbk.py
# Purpose:     latin1 編碼轉傳成 gbk
# Author:      zhoujy
# Created:     2012-10-31
# update:      2012-10-31
#-------------------------------------------------------------------------------

import MySQLdb

conn = MySQLdb.connect(host='localhost',user='root',passwd='123456',db='phpbb')
cursor = conn.cursor()
query = "select username,user_company,user_realname from users limit 20"
#query = "select post_id,post_subject,post_text from posts_text"
cursor.execute(query)
conn.commit()
items = cursor.fetchall()
for line in items:
    a,b,c = line
    a=a.decode('gbk')
    b=b.decode('gbk')
    c=c.decode('gbk')
#    print a.encode('utf-8'),b.encode('utf-8'),c.encode('utf-8') 
    print a,',',b,',',c

結果:

NAME COMPANY RNAME
A the Feed Institute, CAAS tiezhengyuan
B 湖北省武漢市園 小二
C 北京公司 大三
D AB 李逵
E 上海公司 宋江
F 中國公司 花榮
G 我302從 武松
H 上海 潘安
I 中國 劉備

 
轉換:latin1 表轉換到 gbk編碼的表:原來表本身就存在亂碼(不可逆),錯誤了繼續執行:

#!/usr/bin/python
#encoding: utf-8
#-------------------------------------------------------------------------------
# Name:        latin_to_gbk.py
# Purpose:     latin1 編碼表轉傳成gbk編碼表
# Author:      zhoujy
# Created:     2012-10-31
# update:      2012-10-31
#-------------------------------------------------------------------------------

import MySQLdb
MySQLdb.escape_string

def convert_code(s):
#轉換
    if s == None or s == '':
        s = ''
        return MySQLdb.escape_string(str(s).decode('gbk').encode('gbk'))
    else:
        return MySQLdb.escape_string(str(s).decode('gbk').encode('gbk'))

def get_data(conn):
    query ='''
select user_id,username from users
'''
    cursor = conn.cursor()
    cursor.execute(query)
    items = cursor.fetchall()
    return items

def insert_data(tconn,items):
    for line in items:
        a,b = line
#異常處理,錯誤繼續,增加容錯,否則錯誤就退出了。
        try:
            query = '''
insert into php_users(user_id,username)  values(%s,"%s")
''' %(a,convert_code(b))
#            print query
            cursor = tconn.cursor()
            cursor.execute('set names gbk')
            cursor.execute(query)
            tconn.commit()
        except Exception,e:
            print "userId : " + str(a)
            print e
            continue

if __name__ =='__main__':
    conn = MySQLdb.connect(host='192.168.1.10',user='root',passwd='123456',db='bb')
    tconn = MySQLdb.connect(host='192.168.1.20',user='root',passwd='123456',db='cc')
    items = get_data(conn)
    insert_data(tconn,items)

      因此,對於數據庫方面來講,遇到亂碼時,先要核對set names里的三個參數是什么,再看終端使用什么編碼。 都一致的話大部分的情況不會亂碼,不能隨便用alter table convert to 進行轉碼,轉碼的時候一定要先搞明白,字符串str是什么編碼(程序轉換字符存表的的時候使用的是什么),然后通過腳本(python 中 先把 decode成unicode,然后再encode成其他編碼)進行解碼,編碼。

 

關於python 的encode與decode 下面有更多信息:
http://cenalulu.github.io/mysql/mysql-mojibake   值得一看
http://blog.csdn.net/lf8289/article/details/2465196
http://blog.csdn.net/lxdcyh/article/details/4018054
http://www.cnblogs.com/evening/archive/2012/04/19/2457440.html

關於MySQL亂碼:
http://blog.csdn.net/yah99_wolf/article/details/7391089


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM