Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
開發(fā)過程中,經(jīng)常遇到中文亂碼問題,以前總是解決就好,并沒有對該問題總結(jié)一下,現(xiàn)在來總結(jié)一下開發(fā)過程中常見的中文亂碼問題。
一、有必要了解一些基本的編碼知識:
這篇字符編碼筆記是必讀的
jsp的三次編碼
第一階段:JVM將.jsp文件編譯為.java文件。JVM先讀取pageEncoding的值,根據(jù)該值去讀取.jsp文件,然后由指定的編碼方案生成UTF-8的.java文件。
第二階段:JVM將.java文件轉(zhuǎn)換為.class文件,從UTF-8至UTF-8。這個過程就與任何編碼的設(shè)置都沒有關(guān)系了,經(jīng)過這個階段后.java文件就轉(zhuǎn)換成了統(tǒng)一的UTF-8編碼的.class文件了。
第三階段:服務(wù)器將處理的結(jié)果返回給瀏覽器,這個階段則依靠contentType的charset,如果設(shè)置了charset則瀏覽器就會使用指定的編碼格式進行解碼,否則采用默認的ISO-8859-1編碼格式進行解碼處理。
jsp中的編碼設(shè)置
pageEncoding:<%@ page pageEncoding=”UTF-8”%>
上文中第一階段,使用該值去讀取jsp文件,為避免中文亂碼,跟jsp文件編碼一致;對服務(wù)器響應(yīng)進行重新編碼,即jsp的輸出流在瀏覽器中顯示的編碼(不是主要作用)。
contentType: <%@ page contentType=”text/html;charset=UTF-8”%>
使用該值對服務(wù)器響應(yīng)進行重新編碼,即jsp的輸出流在瀏覽器中顯示的編碼;對表單get和post請求數(shù)據(jù)編碼;上文中第一階段,使用該值去讀取jsp文件(不是主要作用)。
< META http-equiv=”Content-Type” content=”text/html;charset=UTF-8”>
網(wǎng)頁的編碼信息 ,說明頁面制作所使用的編碼。
request.setCharacterEncoding()
可用在servlet和jsp頁面中,作用是設(shè)置對客戶端請求進行重新編碼的編碼,即post方式提交的數(shù)據(jù)進行編碼。
response.setCharacterEncoding()
與<%@ page contentType=”text/html;charset=UTF-8”%>一樣。
response.setContentType()
與<%@ page contentType=”text/html;charset=UTF-8”%>一樣。
response.setHeader(“Content-Type”,”text/html;charset=UTF-8”)
與< META http-equiv=”Content-Type” content=”text/html; charset=UTF-8”>一樣。
注意:上文1,2,3中有部分功能是一樣的,是有優(yōu)先級的,在讀取jsp文件時,1>2;在對服務(wù)器響應(yīng)進行編碼的時候,2>1>3,一般情況下,1,2都寫。
http請求默認以”ISO-8859-1”的編碼來傳送URL的。
二、中文亂碼的幾種情況及最簡單的解決方案:
pageEncoding設(shè)置錯誤
pageEncoding設(shè)置為jsp文件的編碼類型。
查詢字符串包含中文
中文的編碼方式取決于瀏覽器,chrome為UTF-8,IE為GB2312,這是由于瀏覽器并沒有遵循URI編碼規(guī)范。有兩種解決方法:
開發(fā)過程中,將查詢字符串提前編碼,
如:http://www.baidu.com/demo?demo=%D6%D0%B9%FA (UTF-8編碼)
在Servlet的doGet()方法中添加
1 | String value = new String(request.getParameter("parameterName").getBytes("ISO-8859-1"),"瀏覽器的編碼方式"); |
表單中的get和post數(shù)據(jù)包含中文
中文的編碼方式取決于上文的contentType中的charset,有兩種解決辦法:
在Servlet的doPost()方法中添加request.setCharacterEncoding(“charset的值”);(僅對post有用)
在Servlet的doPost()方法中添加
1 | String value = new String(request.getParameter("parameterName").getBytes("ISO-8859-1"),"charset的值"); |
三、原理
我們通過上面的方法可以解決亂碼問題,下面講講原理:
客戶端發(fā)到服務(wù)器的數(shù)據(jù)需要在客戶端進行編碼,類似于:String parameterName = "中國".getBytes("UTF-8")
然后將編碼后的數(shù)據(jù)發(fā)到服務(wù)器。
客戶端接受數(shù)據(jù),request.getParameter(“”)的作用就是對接收到的數(shù)據(jù)進行解碼,默認使用ISO-8859-1進行解碼,可以使用request.setCharacterEncoding(“”)進行設(shè)置,但僅對post有用。假如我們使用默認的ISO-8859-1,肯定亂碼,因為編碼跟解碼不一致,那此時怎么辦呢,引出了上文中的兩種解決方案:使用request.setCharacterEncoding(“”)改變request.getParameter(“”)的解碼方式或者new String(request.getParameter("parameterName").getBytes("ISO-8859-1"),"charset的值")
將request.getParameter(“”)解碼的數(shù)據(jù)重新編碼再解碼。
四、其他
在jsp中的頁面使用response.setContentType()等設(shè)置字符集會破壞jsp容器自身的頁面編碼,會引起html中字符亂碼,腳本不會亂,所以不建議設(shè)置。在開發(fā)中多采用page指令設(shè)置字符集。
1234567891011121314151617 | <% response.setContentType("text/html;charset=UTF-8"); String str = new String("你好".getBytes("iso-8859-1"),"utf-8");%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Insert title here</title></head><body><p>你好</p><br><%=str %></body></html> |
網(wǎng)頁測試
開發(fā)一個完整的web項目時,總是會遇到各種各樣的中文亂碼問題,例如頁面顯示亂碼,表單提交亂碼,數(shù)據(jù)庫存儲亂碼等等,雖然目前也能找到各種各樣的解決方案,但是大部分都沒有總結(jié)全面。這里我就根據(jù)自己以前的總結(jié)以及查找的一些資料進行整理,寫出這篇文章。
(1)準備知識
凡是會出現(xiàn)亂碼的地方都是由于編碼的問題產(chǎn)生的,頁面默認編碼為ISO-8859-1,簡體中文編碼為GB2312,中文漢字集(簡體與繁體)編碼為GBK,國際編碼為UTF-8。由于UTF-8編碼支持的語言類型最廣,所以在這里建議凡是用到字符編碼的地方都使用UTF-8格式。接下來看看所有可能出現(xiàn)亂碼場景的解決方案。
(2)頁面顯示亂碼
html頁面
在頁面的<head>標簽內(nèi)添加<meta>標簽,內(nèi)容如下
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
或者
<meta charset="utf-8">
jsp頁面
在jsp頁面的頂部加上以下代碼,重點是charset=UTF-8和pageEncoding="UTF-8"
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
同時在<head>標簽下添加<meta>標簽,代碼如下
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
為了讓所有新建的jsp頁面默認為utf-8編碼,可以在相應(yīng)的IDE下設(shè)置。以eclipse為例,window > Prefrences > Web > JSP Files > Encoding處改成支持UTF-8格式的選項。
jsp頁面編碼設(shè)置
(3)客戶端和服務(wù)器端傳輸亂碼
tomcat配置
在tomcat安裝目錄下 > conf > server.xml,在server.xml文件中找到Connector port="8080"所在的標簽,在標簽內(nèi)部添加URIEncoding="utf-8",添加后為
<Connector port="8080"
protocol="HTTP/1.1"
maxThreads="150"
connectionTimeout="200000"
redirecPort="8443"
URIEncoding="utf-8"/>
Request請求
例如以下請求方式
<a href="/myProject/displayServlet?username=張三&password=123">顯示用戶名和密碼</a>
這種情況下,可以看出來該請求是GET請求,在接收請求時如果出現(xiàn)亂碼,需要使用以下代碼
username= new String(username.getBytes("ISO-8859-1"),"UTF-8")
在表單提交的時候,如果是POST請求,與GET請求不一樣,添加的是以下代碼
request.setCharacterEncoding("utf-8")
注意:request.setCharacterEncoding("utf-8")只在POST請求下生效
Response響應(yīng)
在servlet中利用response進行輸出時,如果出現(xiàn)亂碼,需要添加以下代碼
response.setContentType("text/html;charset=utf-8");
response.setCharacterEncoding("UTF-8")
struts.xml
如果使用了Struts2框架,在strust.xml文件中添加如下代碼
<constant name="struts.i18n.encoding" value="UTF-8"></constant>
Filter
還有一種最保險的方法是添加自定義編碼過濾器,或者使用spring自帶的過濾器,在web.xml中添加以下代碼
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
(4)數(shù)據(jù)庫插入亂碼
在服務(wù)器獲取到正確數(shù)據(jù)后,插入到數(shù)據(jù)庫卻出現(xiàn)亂碼,很有可能是數(shù)據(jù)庫的編碼問題,按照以下方法來做可以避免這種問題
安裝
在數(shù)據(jù)庫安裝過程中會選擇編碼方式,此時選擇utf-8格式
創(chuàng)建數(shù)據(jù)庫
在創(chuàng)建數(shù)據(jù)庫的過程中,按照以下例子
CREATE DATABASE `share` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
創(chuàng)建表
在創(chuàng)建表生成的SQL后面加上
ENGINE=InnoDB DEFAULT CHARSET=utf8;
數(shù)據(jù)庫連接
在使用hibernate或者spring連接數(shù)據(jù)庫時,按照以下例子
<property name="connection.url">
<![CDATA[jdbc:mysql://localhost:3306/testDB?useUnicode=true&characterEncoding=utf8]]>
</property>
(5)總結(jié)
在完成上述的講解后,基本不會出現(xiàn)亂碼的情形,大家也可以嘗試下。如果出現(xiàn)了亂碼的情況,也不要著急,先冷靜分析,亂碼是出現(xiàn)在哪一個環(huán)節(jié),然后按照我講的幾部分去找對應(yīng)的解決辦法。
同時也是教大家一種問問題的方式,先自己分析問題出在哪里,直接問別人“我的項目中出現(xiàn)了中文亂碼,你能幫我看一下么?”和“數(shù)據(jù)返回頁面的時候出現(xiàn)亂碼了,你能幫我看一下么?”你覺得別人會喜歡哪種提問方式呢?
者 | 丁彥軍
責編 | 仲培藝
近日,有位粉絲向我請教,在爬取某網(wǎng)站時,網(wǎng)頁的源代碼出現(xiàn)了中文亂碼問題,本文就將與大家一起總結(jié)下關(guān)于網(wǎng)絡(luò)爬蟲的亂碼處理。注意,這里不僅是中文亂碼,還包括一些如日文、韓文 、俄文、藏文之類的亂碼處理,因為他們的解決方式是一致的,故在此統(tǒng)一說明。
亂碼問題的出現(xiàn)
就以爬取 51job 網(wǎng)站舉例,講講為何會出現(xiàn)“亂碼”問題,如何解決它以及其背后的機制。
代碼示例:
import requests url = "http://search.51job.com" res = requests.get(url) print(res.text)
顯示結(jié)果:
打印 res.text 時,發(fā)現(xiàn)了什么?中文亂碼?。?!不過發(fā)現(xiàn),網(wǎng)頁的字符集類型采用的是 GBK 編碼格式。
我們知道 Requests 會基于 HTTP 頭部對響應(yīng)的編碼作出有根據(jù)的推測。當你訪問 r.text 之時,Requests 會使用其推測的文本編碼。你可以找出 Requests 使用了什么編碼,并且能夠使用 r.encoding 屬性來改變它。
接下來,我們一起通過 Resquests 的一些用法,來看看 Requests 會基于 HTTP 頭部對響應(yīng)的編碼方式。
print(res.encoding) #查看網(wǎng)頁返回的字符集類型 print(res.apparent_encoding) #自動判斷字符集類型
輸出結(jié)果為:
可以發(fā)現(xiàn) Requests 推測的文本編碼(也就是網(wǎng)頁返回即爬取下來后的編碼轉(zhuǎn)換)與源網(wǎng)頁編碼不一致,由此可知其正是導致亂碼原因。
亂碼背后的奧秘
當源網(wǎng)頁編碼和爬取下來后的編碼轉(zhuǎn)換不一致時,如源網(wǎng)頁為 GBK 編碼的字節(jié)流,而我們抓取下后程序直接使用 UTF-8 進行編碼并輸出到存儲文件中,這必然會引起亂碼,即當源網(wǎng)頁編碼和抓取下來后程序直接使用處理編碼一致時,則不會出現(xiàn)亂碼,此時再進行統(tǒng)一的字符編碼也就不會出現(xiàn)亂碼了。最終爬取的所有網(wǎng)頁無論何種編碼格式,都轉(zhuǎn)化為 UTF-8 格式進行存儲。
注意:區(qū)分源網(wǎng)編碼 A-GBK、程序直接使用的編碼 B-ISO-8859-1、統(tǒng)一轉(zhuǎn)換字符的編碼 C-UTF-8。
在此,我們拓展講講 Unicode、ISO-8859-1、GBK2312、GBK、UTF-8 等之間的區(qū)別聯(lián)系,大概如下:
最早的編碼是 ISO8859-1,和 ASCII 編碼相似。但為了方便表示各種各樣的語言,逐漸出現(xiàn)了很多標準編碼。ISO8859-1 屬于單字節(jié)編碼,最多能表示的字符范圍是 0-255,應(yīng)用于英文系列。很明顯,ISO8859-1 編碼表示的字符范圍很窄,無法表示中文字符。
1981 年中國人民通過對 ASCII 編碼的中文擴充改造,產(chǎn)生了 GB2312 編碼,可以表示 6000 多個常用漢字。但漢字實在是太多了,包括繁體和各種字符,于是產(chǎn)生了 GBK 編碼,它包括了 GB2312 中的編碼,同時擴充了很多。中國又是個多民族國家,各個民族幾乎都有自己獨立的語言系統(tǒng),為了表示那些字符,繼續(xù)把 GBK 編碼擴充為 GB18030 編碼。每個國家都像中國一樣,把自己的語言編碼,于是出現(xiàn)了各種各樣的編碼,如果你不安裝相應(yīng)的編碼,就無法解釋相應(yīng)編碼想表達的內(nèi)容。終于,有個叫 ISO 的組織看不下去了。他們一起創(chuàng)造了一種編碼 Unicode,這種編碼非常大,大到可以容納世界上任何一個文字和標志。所以只要電腦上有 Unicode 這種編碼系統(tǒng),無論是全球哪種文字,只需要保存文件的時候,保存成 Unicode 編碼就可以被其他電腦正常解釋。Unicode 在網(wǎng)絡(luò)傳輸中,出現(xiàn)了兩個標準 UTF-8 和 UTF-16,分別每次傳輸 8 個位和 16 個位。于是就會有人產(chǎn)生疑問,UTF-8 既然能保存那么多文字、符號,為什么國內(nèi)還有這么多使用 GBK 等編碼的人?因為 UTF-8 等編碼體積比較大,占電腦空間比較多,如果面向的使用人群絕大部分都是中國人,用 GBK 等編碼也可以。
也可以這樣來理解:字符串是由字符構(gòu)成,字符在計算機硬件中通過二進制形式存儲,這種二進制形式就是編碼。如果直接使用 “字符串??字符??二進制表示(編碼)” ,會增加不同類型編碼之間轉(zhuǎn)換的復(fù)雜性。所以引入了一個抽象層,“字符串??字符??與存儲無關(guān)的表示??二進制表示(編碼)” ,這樣,可以用一種與存儲無關(guān)的形式表示字符,不同的編碼之間轉(zhuǎn)換時可以先轉(zhuǎn)換到這個抽象層,然后再轉(zhuǎn)換為其他編碼形式。在這里,Unicode 就是 “與存儲無關(guān)的表示”,UTF-8 就是 “二進制表示”。
亂碼的解決方法
根據(jù)原因來找解決方法,就非常簡單了。
方法一:直接指定 res.encoding
import requests url = "http://search.51job.com" res = requests.get(url) res.encoding = "gbk" html = res.text print(html)
方法二:通過 res.apparent_encoding 屬性指定
import requests url = "http://search.51job.com" res = requests.get(url) res.encoding = res.apparent_encoding html = res.text print(html)
方法三:通過編碼、解碼的方式
import requests url = "http://search.51job.com" res = requests.get(url) html = res.text.encode('iso-8859-1').decode('gbk') print(html)
輸出結(jié)果:
基本思路三步走:確定源網(wǎng)頁的編碼 A---GBK、程序通過編碼 B---ISO-8859-1 對源網(wǎng)頁數(shù)據(jù)還原、統(tǒng)一轉(zhuǎn)換字符的編碼 C-UTF-8。至于為啥出現(xiàn)統(tǒng)一轉(zhuǎn)碼這一步呢? 網(wǎng)絡(luò)爬蟲系統(tǒng)數(shù)據(jù)來源很多,不可能使用數(shù)據(jù)時,再轉(zhuǎn)化為其原始的數(shù)據(jù),這樣做是很廢事的。所以一般的爬蟲系統(tǒng)都要對抓取下來的結(jié)果進行統(tǒng)一編碼,從而在使用時做到一致對外,方便使用。
比如如果我們想講網(wǎng)頁數(shù)據(jù)保存下來,則會將起轉(zhuǎn)為 UTF-8,代碼如下:
with open("a.txt",'w',encoding='utf-8') as f: f.write(html)
總結(jié)
關(guān)于網(wǎng)絡(luò)爬蟲亂碼問題,這里不僅給出了一個解決方案,還深入到其中的原理,由此問題引申出很多有意思的問題,如 UTF-8、GBK、GB2312 的編碼方式怎樣的?為什么這樣轉(zhuǎn)化就可以解決問題?
最后,多動腦,多思考,多總結(jié),致每一位碼農(nóng)!
本文為作者投稿,版權(quán)歸其所有。
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。