python編碼及requests亂碼問題


1.字符編碼簡介

1.1. ASCII
ASCII(American Standard Code for Information Interchange),是一種單字節的編碼。計算機世界里一開始只有英文,而單字節可以表示256個不同的字符,可以表示所有的英文字符和許多的控制符號。不過ASCII只用到了其中的一半(\x80以下),這也是MBCS得以實現的基礎。

1.2. MBCS
然而計算機世界里很快就有了其他語言,單字節的ASCII已無法滿足需求。后來每個語言就制定了一套自己的編碼,由於單字節能表示的字符太少,而且同時也需要與ASCII編碼保持兼容,所以這些編碼紛紛使用了多字節來表示字符,如GBxxx、BIGxxx等等,他們的規則是,如果第一個字節是\x80以下,則仍然表示ASCII字符;而如果是\x80以上,則跟下一個字節一起(共兩個字節)表示一個字符,然后跳過下一個字節,繼續往下判斷。

這里,IBM發明了一個叫Code Page的概念,將這些編碼都收入囊中並分配頁碼,GBK是第936頁,也就是CP936。所以,也可以使用CP936表示GBK。

MBCS(Multi-Byte Character Set)是這些編碼的統稱。目前為止大家都是用了雙字節,所以有時候也叫做DBCS(Double-Byte Character Set)。必須明確的是,MBCS並不是某一種特定的編碼,Windows里根據你設定的區域不同,MBCS指代不同的編碼,而Linux里無法使用MBCS作為編碼。在Windows中你看不到MBCS這幾個字符,因為微軟為了更加洋氣,使用了ANSI來嚇唬人,記事本的另存為對話框里編碼ANSI就是MBCS。同時,在簡體中文Windows默認的區域設定里,指代GBK。

1.3. Unicode
后來,有人開始覺得太多編碼導致世界變得過於復雜了,讓人腦袋疼,於是大家坐在一起拍腦袋想出來一個方法:所有語言的字符都用同一種字符集來表示,這就是Unicode。

最初的Unicode標准UCS-2使用兩個字節表示一個字符,所以你常常可以聽到Unicode使用兩個字節表示一個字符的說法。但過了不久有人覺得256*256太少了,還是不夠用,於是出現了UCS-4標准,它使用4個字節表示一個字符,不過我們用的最多的仍然是UCS-2。

UCS(Unicode Character Set)還僅僅是字符對應碼位的一張表而已,比如"漢"這個字的碼位是6C49。字符具體如何傳輸和儲存則是由UTF(UCS Transformation Format)來負責。

一開始這事很簡單,直接使用UCS的碼位來保存,這就是UTF-16,比如,"漢"直接使用\x6C\x49保存(UTF-16-BE),或是倒過來使用\x49\x6C保存(UTF-16-LE)。但用着用着美國人覺得自己吃了大虧,以前英文字母只需要一個字節就能保存了,現在大鍋飯一吃變成了兩個字節,空間消耗大了一倍……於是UTF-8橫空出世。

UTF-8是一種很別扭的編碼,具體表現在他是變長的,並且兼容ASCII,ASCII字符使用1字節表示。然而這里省了的必定是從別的地方摳出來的,你肯定也聽說過UTF-8里中文字符使用3個字節來保存吧?4個字節保存的字符更是在淚奔……(具體UCS-2是怎么變成UTF-8的請自行搜索)

2.python2.x 3.x編碼問題

  1. 源碼編碼方式(.py文件的字符集),
  2. 執行編碼方式(程序運行加載到內存的編碼方式)
    python2.x默認執行編碼方式為ascii 所以我們需要再py文件頭加入一行代碼來指定一個確定的執行編碼方式(內存中加載的字符串使用的就是執行編碼方式)
# -*- coding:utf-8 -*-

python3.x默認執行編碼方式為utf-8所以無需增加此行代碼

**無論是python2.7還是python3.6,執行編碼方式和源碼編碼方式必須一致,雖然文件開頭        # --coding:xxx --用於指定執行編碼方式,但是也會決定python解釋器讀取源文件時的編碼方式,如果源文件是gbk格式,但是開頭xxx為utf-8,那么python解釋器將會以utf-8編碼讀取gbk格式的文件,嚴重時將會導致錯誤,所以,一定要保證xxx和源文件的編碼方式一致,即執行編碼方式和源碼編碼方式一致。

py2中python中任意兩種不同字符編碼的轉換都是以unicode為基石
image.png

python2.x和python3.x中bytes、str和unicode
image.png

引用鏈接:https://blog.csdn.net/xiaoyink/java/article/details/80850448

3.requests獲取網頁亂碼問題

運行環境python3.6
例如:

# -*- coding: utf-8 -*
import requests
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
            'Accept-Language':'zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7,ko;q=0.6'}

def main(url):
    res = requests.get(url,headers=headers)
    print(res.text)

if __name__ == '__main__':
    main('https://stock.hexun.com/2019-12-16/199691973.html?from=rss')

打印的結果html中文亂碼:
image.png

原因:
requests會從服務器返回的響應頭的 Content-Type 去獲取字符集編碼,如果content-type有charset字段那么requests才能正確識別編碼,否則就使用默認的 ISO-8859-1. 一般那些不規范的頁面往往有這樣的問題.
res.encoding可查看requests識別的編碼

res = requests.get(url,headers=headers)
print(res.encoding)
print(res.headers)
#ISO-8859-1
#{'Server': 'nginx', 'Date': 'Tue, 21 Apr 2020 02:14:57 GMT', 'Content-Type': 'text/html', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'Expires': 'Tue, 21 Apr 2020 02:15:57 GMT', 'Cache-Control': 'max-age=60', 'X-UA-Compatible': 'IE=EmulateIE7', 'Content-Encoding': 'gzip'}

如上打印的encoding和headers結果都驗證了上面的說法。
修改該問題有2中方法:

  1. 強制修改res.encoding的編碼類型
    requests的返回結果對象里有個apparent_encoding函數, apparent_encoding通過調用chardet.detect()來識別文本編碼. 但是需要注意的是,這有些消耗計算資源.
    因為在源碼中是用的content進行識別
@property
def apparent_encoding(self):
    """使用chardet來計算編碼"""
    return chardet.detect(self.content)['encoding']

chardet.detect可用於識別文本編碼

>>> data = '離離原上草,一歲一枯榮'.encode('gbk')
>>> chardet.detect(data)
{'encoding': 'GB2312', 'confidence': 0.7407407407407407, 'language': 'Chinese'}

>>> data = '離離原上草,一歲一枯榮'.encode('utf-8')
>>> chardet.detect(data)
{'encoding': 'utf-8', 'confidence': 0.99, 'language': ''}

confidence表示識別概率
  1. 采用res.content作為文本結果
    requests在獲取網絡資源后,我們可以通過兩種模式查看內容。 一個是r.text,另一個是r.content,那他們之間有什么區別呢?
    分析requests的源代碼發現,r.text返回的是處理過的Unicode型的數據,而使用r.content返回的是bytes型的原始數據。也就是說,r.content相對於r.text來說節省了計算資源,r.content是把內容bytes返回. 而r.text是decode成Unicode. 如果headers沒有charset字符集的化,text()會調用chardet來計算字符集,這又是消耗cpu的事情.

所以直接將bytes的content傳給BeautifulSoup進行解析便不用考慮編碼問題,因為BeautifulSoup接收bytes或str的網頁都會返回str的數據用於解析

soup = BeautifulSoup(text,'lxml')

引用文章:http://xiaorui.cc/archives/2786


免責聲明!

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



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