網頁編碼問題與 PHP 的 urldecode

Emmie Lin
7 min readNov 14, 2020

--

在進行網頁爬蟲時,URL 的問題常常都是碰到一個解一個(對菜鳥來說經驗就是這樣累積的QQ)。而不久前遇到了一個存資料時資料庫報錯的問題:

SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xA4\xBD\xA5D' for column 'url' at row 1

確認資料庫欄位的編碼(utf8mb4_unicode_ci)沒有設錯後,才發現是因為寫入的資料包含非法的 utf8 字元,才會導致這個錯誤。

那麼,為什麼存資料時會產生非法的字元?

原來,之前為了讓 url 這個欄位保持 unique,所以在存入前多做了一個 urldecode() 的動作,為了就是不要讓像是這種帶有中文的網址如 https://example.com/你好https://example.com/%E4%BD%A0%E5%A5%BD 在資料庫中被存成兩筆資料。然而這卻引發了一個我從未遇過的問題,有些網址被 decode 之後,竟然變成了亂碼了!

由於網址是直接從 Google 的搜尋結果中所取得,為了得到 decode 之前的網址,我直接到 Google 搜尋結果找尋其中一個變成亂碼網址的頁面,把該網址用這個線上工具 decode 測試過後,發現編碼方式是 big5!

而該網址使用 urldecode() 後,確實出現了奇怪的字元:

// 後面 query 的字元,為 big5 的編碼方式
urldecode('https://www.i-gamer.net/web/?Tag=%B2r%C3%7E')
=> b"https://www.i-gamer.net/web/?Tag=²rÃ~"

看起來 urldecode() 只能對使用 utf-8 編碼的文字正確解碼。而為什麼會出現不是 utf-8 編碼的網址呢?那就跟網頁本身的編碼設定有關了。

觀察網頁原始碼後發現,這些網頁都帶有這個 meta 標籤: <meta HTTP-EQUIV=”Content-Type” CONTENT=”text/html; charset=big5">,我一開始以為這是代表該網頁編碼為 big5 ,但其實這是個誤解。

這邊先不深入探討 big5 和 utf-8 的差別,簡單來說,兩者都是一種中文編碼方式,而現在大多數的網頁都是採用 utf-8 的編碼方式,但還是偶爾能看到些年代感比較重的網站仍然在用 big5。

為了瞭解不同編碼方式之下的網頁會對頁面中的連結 URL 造成什麼影響,我直接模擬一個 big5 編碼的的網站:

首先建立了兩個頁面, index_big5.htmlindex_big5-2.html ,然後在index_big5.html 當中設置一個連去 index_big5-2.html 的連結,

//index_big5.html<HTML><HEAD><TITLE> meta 標籤的使用:中文網頁 </TITLE><meta HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=big5"></HEAD><BODY><h1>這是一個繁體中文網頁!</h1>(This page uses big5 character set.)<br>charset=big5<a href="./index_big5-2.html?test=這是一段中文">連結</a></BODY></HTML>

兩份檔案都使用 big5 進行編碼。編碼的調整其實 VSCode 就有內建選擇編碼的功能(在右下角):

調整文件的編碼方式為 big5(預設為 utf-8)

否則也可以編輯完成再用指令重新輸出一份 big-5 的版本:

iconv -f UTF-8 -t BIG-5 index.html > index_big5.html

用 PHP Server 這個 VSCode 外掛開啟一個 web server 在 3000 port,然後直接瀏覽網頁會長這樣:

一開始覺得奇怪為什麼明明設定了 meta 標籤,但還是顯示亂碼?這就是上面所提到的誤解,其實這個 meta 標籤並不是用來定義「網頁應該顯示的字集」,而是用來定義「網頁送出的編碼字集」(影響到的是網頁送出的表單當中的文字編碼方式)。那麼真正定義網頁應該顯示哪種字集的設定在哪?其實應該在 Server 回傳的 Response Header 當中定義 Content-Type 才對!當 Server 沒有特別指定這個 Header 時,就會交由瀏覽器決定要使用哪種字集,而 Chrome 預設是 utf-8,所以就會看到上方這個亂碼的頁面。

因為我的 Server 是用 PHP Server 這個外掛開的,懶得研究他要怎麼設定 Response Header,所以就用了一個 Chrome 擴充程式叫做 ModHeader,可直接修改 Response Header。總算再把 Header 的 Content-Type 改成 charset=big5 後,就成功看到正常的頁面了:

頁面字元顯示正常後,接下來把游標移動到連結的位置會發現,瀏覽器下方顯示的連結預覽網址,怎麼那個編碼看起來有點眼熟?接著點擊該網址到 index_big5-2.html 這個頁面,上面的網址列就長得跟一開始看到的網址類似了,也就是中文的部分都是 big5 編碼。

也就是說在點擊連結時,會受到網頁編碼方式影響,瀏覽器隨之對其使用不同方式進行編碼 。由於我測試是把中文帶在 query,若帶在 path 會有不同的效果:

  • path 部分所有瀏覽器預設皆以 UTF-8 編碼送出
  • query 部分當「沒有利用 UrlEncode 編碼」時,一律以「該網頁的編碼」為主送出

而對於 Google 搜尋結果出現非 utf-8 編碼的網址,就要看 Google 爬蟲在爬網站時是如何爬取處理這些頁面了。

透過這次經驗對於網頁編碼又更深一層的認識了~如果文中有任何錯誤或理解不對的地方也歡迎指正!

參考資料

--

--