2010-03-24 76 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]; 
    } 
} 

(附注:我无法弄清楚如何张贴此作为一个评论,因为这显然不是一个完整的答案。)