2010-03-24 77 views
9

我只想使用UTF8。問題是我不知道每個網頁的字符集。我如何檢測它並轉換爲UTF8? http://paulisageek.com/tmp/curl-utf8PHP:將curl_exec輸出轉換爲UTF8

什麼是magic()

<?php 
$url = "http://vkontakte.ru"; 
$ch = curl_init($url); 
$options = array(
    CURLOPT_RETURNTRANSFER => true, 
); 
curl_setopt_array($ch, $options); 
$data = curl_exec($ch); 

// $data = magic($data); 

print $data; 

在看到了嗎?

回答

24

由濃湯和佩卡的建議去,我寫道:curl_exec_utf8

/** The same as curl_exec except tries its best to convert the output to utf8 **/ 
function curl_exec_utf8($ch) { 
    $data = curl_exec($ch); 
    if (!is_string($data)) return $data; 

    unset($charset); 
    $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); 

    /* 1: HTTP Content-Type: header */ 
    preg_match('@([\w/+]+)(;\s*charset=(\S+))[email protected]', $content_type, $matches); 
    if (isset($matches[3])) 
     $charset = $matches[3]; 

    /* 2: <meta> element in the page */ 
    if (!isset($charset)) { 
     preg_match('@<meta\s+http-equiv="Content-Type"\s+content="([\w/]+)(;\s*charset=([^\s"]+))[email protected]', $data, $matches); 
     if (isset($matches[3])) { 
      $charset = $matches[3]; 
      /* In case we want do do further processing downstream: */ 
      $data = preg_replace('@(<meta\s+http-equiv="Content-Type"\s+content="[\w/]+\s*;\s*charset=)([^\s"]+)@i', '$1utf-8', $data, 1); 
     } 
    } 

    /* 3: <xml> element in the page */ 
    if (!isset($charset)) { 
     preg_match('@<\?xml.+encoding="([^\s"]+)@si', $data, $matches); 
     if (isset($matches[1])) { 
      $charset = $matches[1]; 
      /* In case we want do do further processing downstream: */ 
      $data = preg_replace('@(<\?xml.+encoding=")([^\s"]+)@si', '$1utf-8', $data, 1); 
     } 
    } 

    /* 4: PHP's heuristic detection */ 
    if (!isset($charset)) { 
     $encoding = mb_detect_encoding($data); 
     if ($encoding) 
      $charset = $encoding; 
    } 

    /* 5: Default for HTML */ 
    if (!isset($charset)) { 
     if (strstr($content_type, "text/html") === 0) 
      $charset = "ISO 8859-1"; 
    } 

    /* Convert it if it is anything but UTF-8 */ 
    /* You can change "UTF-8" to "UTF-8//IGNORE" to 
     ignore conversion errors and still output something reasonable */ 
    if (isset($charset) && strtoupper($charset) != "UTF-8") 
     $data = iconv($charset, 'UTF-8', $data); 

    return $data; 
} 

的正則表達式大多來自http://nadeausoftware.com/articles/2007/06/php_tip_how_get_web_page_content_type

+1

Ooohh甜美!當我找到時間的時候我會試駕這個。 – 2010-03-25 10:41:12

+0

感謝分享,你救了我的命! :D – 2016-06-20 18:37:01

+0

做得很好,但如果我們在下游進行一些進一步處理,我們希望在我們去的時候修復標籤。我冒昧地更新你的代碼。 – DomQ 2018-01-08 17:35:57

4

轉換很簡單。檢測是困難的部分。你可以嘗試mb_detect_encoding,但這是一個非常不穩定的方法,它實際上是「猜測」內容類型,因爲註釋中的@troelskn高亮最多可以猜測「粗略」差異(是多字節編碼嗎?),但檢測失敗類似字符集的細微差別。

正確的方法是IMO:

  • 解釋任何content-type Meta標籤頁面
  • 解釋任何content-type頭由服務器發送
  • 如果產生了什麼,嘗試「嗅出」編碼使用mb_detect_encoding()
  • 如果這不產生任何結果,回落到一個定義的默認值(也許ISO-8859-1,也許UTF-8)。

與@Gumbo的答案中的指南不同,我個人認爲Meta標籤應該優先於服務器標題,因爲我非常肯定如果存在Meta標記,那是一個更可靠的實際編碼的頁面比服務器設置一些網站運營商甚至不知道如何改變。但是,正確的方式似乎是將優先級較高的內容類型標頭對待。

對於前者,我認爲你可以使用get_meta_tags()。後者你應該已經從捲曲中獲得,你只需要解析它。 Here是如何系統地處理由cURL提供的響應頭的完整示例。

轉換將被使用iconv

$new_content = iconv("incoming-charset", "utf-8", $content); 
+0

沒有其他人都這樣做嗎?我不能成爲第一個遇到這個問題的人。沒有現有的代碼來檢測這個好嗎? – 2010-03-24 19:57:05

+0

@保羅很好的問題!應該有一個圖書館,但我不知道。如果沒有其他的東西出現,你最好的選擇可能是看PHP「瀏覽器模擬器」類,無論這些類是否有這個實現。 – 2010-03-24 19:58:56

+0

http標題應該可能比元標記有更高的優先級。 – troelskn 2010-03-25 10:32:13

0

有一個定義的順序how to specify the character encoding in HTML

[...]確定文檔的字符編碼時符合用戶代理必須遵守下列優先級(從最高優先級到最低):

  1. HTTP「charset」par ameter「內容類型」字段中。
  2. A META聲明將「http-equiv」設置爲「Content-Type」併爲「charset」設置一個值。
  3. 在指定外部資源的元素上設置的charset屬性。

如果沒有字符編碼聲明存在,HTTP defines ISO 8859-1 as default character encoding。您也可以將其用作HTML的默認字符編碼,或者只是拒絕處理響應。

對於XHTML你還具有XML declaration as source for the encoding

在XML文檔中,該文檔的字符編碼在XML聲明中指定(例如,<?xml version="1.0" encoding="EUC-JP"?>)。爲了可移植地呈現具有特定字符編碼的文檔,最好的方法是確保Web服務器提供正確的標題。如果這是不可能的,那麼希望明確設置其字符編碼的文檔必須包括XML聲明和編碼聲明以及http-equiv語句(例如<meta http-equiv="Content-type" content="text/html; charset=EUC-JP" />)。在符合XHTML的用戶代理中,XML聲明的編碼聲明的值優先。

如果沒有字符編碼聲明,XML defines UTF-8 and UTF-16 as default character encoding

除非的編碼是通過更高級別的協議確定的,它也是一個致命的錯誤,如果XML實體不包含編碼聲明,其內容爲不合法的UTF-8或UTF-16。

所以,總結一下,順序是:

  1. 在 「內容類型」 字段中的HTTP 「字符集」 參數。
  2. XML聲明與encoding屬性。
  3. A META聲明將「http-equiv」設置爲「Content-Type」併爲「charset」設置一個值。

如果不存在字符編碼聲明,則可以將ISO 8859-1作爲HTML的默認編碼,並且必須將UTF-8或UTF-16作爲XHTML的默認編碼。

+0

很好。有這個協議的庫嗎?我想一起做curl和字符轉換,並且UTF8剛剛返回 – 2010-03-24 20:18:43

+0

@Paul Tarjan:你可以用'curl_getinfo'來設置* Content-Type *頭字段。 – Gumbo 2010-03-24 20:38:24

+0

我把你的建議放在一個函數中,它看起來如何? – 2010-03-26 03:48:24

1

我非常高興找到這個答案,但發現有一個在<meta>標籤檢測的一個缺陷。它似乎沒有匹配任何內容類型的標籤,但它尚未配備新的HTML5樣式標籤:<meta charset="UTF-8">。所以我寫了這個,希望它可以幫助你們,並再次感謝這個出色的解決方案!

/* 2: <meta> element in the page */ 
if (!isset($charset)) { 
    preg_match('/<[\s]*meta[^>]*charset="?([^\s"]+)\s?"/i', $data, $matches); 

    if (isset($matches[1])) { 
     $charset = $matches[1]; 
    } 
} 

(附註:我無法弄清楚如何張貼此作爲一個評論,因爲這顯然不是一個完整的答案。)