我正在从各种 RSS 提要中读取大量文本并将它们插入到我的数据库中。
当然,提要中使用了几种不同的字符编码,例如 UTF-8 和 ISO 8859-1。
不幸的是,文本的编码有时会出现问题。例子:
“Fußball”中的“ß”在我的数据库中应该如下所示:“Ÿ”。如果是“Ÿ”,则显示正确。有时,“Fußball”中的“ß”在我的数据库中看起来像这样:“ß”。然后它显示错误,当然。在其他情况下,“ß”被保存为“ß” - 所以没有任何变化。然后它也显示错误。
我能做些什么来避免情况 2 和 3?
如何使所有内容都使用相同的编码,最好是 UTF-8?什么时候必须使用 utf8_encode()
,什么时候必须使用 utf8_decode()
(效果很清楚,但什么时候必须使用函数?),什么时候必须对输入不做任何事情?
如何使所有内容都使用相同的编码?也许使用函数 mb_detect_encoding()
?我可以为此编写一个函数吗?所以我的问题是:
如何找出文本使用的编码?如何将其转换为 UTF-8 - 无论旧编码是什么?
这样的功能会起作用吗?
function correct_encoding($text) {
$current_encoding = mb_detect_encoding($text, 'auto');
$text = iconv($current_encoding, 'UTF-8', $text);
return $text;
}
我已经测试过了,但它不起作用。它出什么问题了?
如果将 utf8_encode()
应用于已经是 UTF-8 的字符串,它将返回乱码的 UTF-8 输出。
我做了一个函数来解决所有这些问题。它称为Encoding::toUTF8()
。
您不需要知道字符串的编码是什么。它可以是 Latin1(ISO 8859-1)、Windows-1252 或 UTF-8,或者字符串可以混合使用它们。Encoding::toUTF8()
会将所有内容转换为 UTF-8。
我这样做是因为一项服务向我提供了一个混乱的数据源,将 UTF-8 和 Latin1 混合在同一个字符串中。
用法:
require_once('Encoding.php');
use \ForceUTF8\Encoding; // It's namespaced now.
$utf8_string = Encoding::toUTF8($utf8_or_latin1_or_mixed_string);
$latin1_string = Encoding::toLatin1($utf8_or_latin1_or_mixed_string);
下载:
https://github.com/neitanod/forceutf8
我已经包含了另一个函数 Encoding::fixUFT8()
,它将修复每个看起来乱码的 UTF-8 字符串。
用法:
require_once('Encoding.php');
use \ForceUTF8\Encoding; // It's namespaced now.
$utf8_string = Encoding::fixUTF8($garbled_utf8_string);
例子:
echo Encoding::fixUTF8("Fédération Camerounaise de Football");
echo Encoding::fixUTF8("Fédération Camerounaise de Football");
echo Encoding::fixUTF8("FÃÂédÃÂération Camerounaise de Football");
echo Encoding::fixUTF8("Fédération Camerounaise de Football");
将输出:
Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football
我已将函数 (forceUTF8
) 转换为名为 Encoding
的类上的一系列静态函数。新函数是 Encoding::toUTF8()
。
您首先必须检测使用了什么编码。在解析 RSS 提要(可能通过 HTTP)时,您应该从 Content-Type
HTTP header field 的 charset
参数中读取编码。如果不存在,则从 XML processing instruction 的 encoding
属性读取编码。如果也没有,use UTF-8 as defined in the specification。
这是我可能会做的:
我会使用 cURL 发送和获取响应。这允许您设置特定的标头字段并获取响应标头。获取响应后,您必须解析 HTTP 响应并将其拆分为标头和正文。然后,标头应包含包含 MIME 类型的 Content-Type
标头字段和(希望)带有编码/字符集的 charset
参数。如果没有,我们将分析 XML PI 中是否存在 encoding
属性并从那里获取编码。如果这也缺失,XML 规范定义使用 UTF-8 作为编码。
$url = 'http://www.lr-online.de/storage/rss/rss/sport.xml';
$accept = array(
'type' => array('application/rss+xml', 'application/xml', 'application/rdf+xml', 'text/xml'),
'charset' => array_diff(mb_list_encodings(), array('pass', 'auto', 'wchar', 'byte2be', 'byte2le', 'byte4be', 'byte4le', 'BASE64', 'UUENCODE', 'HTML-ENTITIES', 'Quoted-Printable', '7bit', '8bit'))
);
$header = array(
'Accept: '.implode(', ', $accept['type']),
'Accept-Charset: '.implode(', ', $accept['charset']),
);
$encoding = null;
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
$response = curl_exec($curl);
if (!$response) {
// error fetching the response
} else {
$offset = strpos($response, "\r\n\r\n");
$header = substr($response, 0, $offset);
if (!$header || !preg_match('/^Content-Type:\s+([^;]+)(?:;\s*charset=(.*))?/im', $header, $match)) {
// error parsing the response
} else {
if (!in_array(strtolower($match[1]), array_map('strtolower', $accept['type']))) {
// type not accepted
}
$encoding = trim($match[2], '"\'');
}
if (!$encoding) {
$body = substr($response, $offset + 4);
if (preg_match('/^<\?xml\s+version=(?:"[^"]*"|\'[^\']*\')\s+encoding=("[^"]*"|\'[^\']*\')/s', $body, $match)) {
$encoding = trim($match[1], '"\'');
}
}
if (!$encoding) {
$encoding = 'utf-8';
} else {
if (!in_array($encoding, array_map('strtolower', $accept['charset']))) {
// encoding not accepted
}
if ($encoding != 'utf-8') {
$body = mb_convert_encoding($body, 'utf-8', $encoding);
}
}
$simpleXML = simplexml_load_string($body, null, LIBXML_NOERROR);
if (!$simpleXML) {
// parse error
} else {
echo $simpleXML->asXML();
}
}
charset=
和 encoding=
的任何外观,而不仅仅是在适当的位置。第三,您没有检查声明的编码是否被接受。
检测编码很困难。
mb_detect_encoding
根据您通过的候选人数量进行猜测。在某些编码中,某些字节序列是无效的,因此它可以区分各种候选者。不幸的是,有很多编码,其中相同的字节是有效的(但不同)。在这些情况下,无法确定编码;在这些情况下,您可以实现自己的逻辑来进行猜测。例如,来自日本网站的数据可能更有可能采用日语编码。
只要您只处理西欧语言,要考虑的三种主要编码是 utf-8
、iso-8859-1
和 cp-1252
。由于这些是许多平台的默认设置,因此它们也最有可能被错误地报告。例如。如果人们使用不同的编码,他们可能会坦率地说,否则他们的软件会经常崩溃。因此,一个好的策略是信任提供者,除非编码被报告为这三者之一。您仍然应该使用 mb_check_encoding
仔细检查它是否确实有效(注意 valid 与 being 不同 - 相同的输入可能对许多编码有效)。如果是其中之一,则可以使用 mb_detect_encoding
来区分它们。幸运的是,这是相当确定的;您只需要使用正确的检测序列,即 UTF-8,ISO-8859-1,WINDOWS-1252
。
检测到编码后,您需要将其转换为内部表示(UTF-8
是唯一明智的选择)。函数 utf8_encode
将 ISO-8859-1
转换为 UTF-8
,因此它只能用于该特定输入类型。对于其他编码,请使用 mb_convert_encoding
。
此备忘单列出了一些与 PHP 中的 UTF-8 处理相关的常见注意事项:http://developer.loftdigital.com/blog/php-utf-8-cheatsheet
此函数检测字符串中的多字节字符也可能很有帮助(source):
function detectUTF8($string)
{
return preg_match('%(?:
[\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
|\xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
|\xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
|\xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
|[\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
|\xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
)+%xs',
$string);
}
有点抬头。您说“ß”应在数据库中显示为“Ÿ”。
这可能是因为您使用的是 Latin-1 字符编码的数据库,或者您的 PHP-MySQL 连接设置错误,也就是说,P 认为您的 MySQL 设置为使用 UTF-8,因此它以 UTF-8 发送数据, 但是你的 MySQL 认为 PHP 正在发送编码为 ISO 8859-1 的数据,所以它可能会再次尝试将你发送的数据编码为 UTF-8,从而导致这种麻烦。
看看 mysql_set_charset。它可能会帮助你。
可以在 php.net 上找到实现 isUTF8
函数的真正好方法:
function isUTF8($string) {
return (utf8_encode(utf8_decode($string)) == $string);
}
mb_check_encoding($string, 'UTF-8')
isUTF8('€')
将在 99% 之内。
您的编码看起来像您两次编码为 UTF-8;也就是说,从其他编码,到 UTF-8,然后再到 UTF-8。就好像您有 ISO 8859-1,从 ISO 8859-1 转换为 UTF-8,并将新字符串视为 ISO 8859-1 以再次转换为 UTF-8。
这是您所做的一些伪代码:
$inputstring = getFromUser();
$utf8string = iconv($current_encoding, 'utf-8', $inputstring);
$flawedstring = iconv($current_encoding, 'utf-8', $utf8string);
你应该试试:
使用 mb_detect_encoding() 或任何您喜欢使用的编码(如果它是 UTF-8)检测编码,转换为 ISO 8859-1,最后重复步骤 1,转换回 UTF-8
这是假设在“中间”转换中您使用了 ISO 8859-1。如果您使用的是 Windows-1252,则转换为 Windows-1252 (latin1)。原始源编码并不重要;您在有缺陷的第二次转换中使用的那个是。
这是我对发生的事情的猜测;要获得四个字节来代替一个扩展的 ASCII 字节,您几乎没有什么别的办法。
德语也使用 ISO 8859-2 和 Windows-1250 (Latin-2)。
关于 mb_detect_encoding
和 mb_convert_encoding
的有趣之处在于您建议的编码顺序确实很重要:
// $input is actually UTF-8
mb_detect_encoding($input, "UTF-8", "ISO-8859-9, UTF-8");
// ISO-8859-9 (WRONG!)
mb_detect_encoding($input, "UTF-8", "UTF-8, ISO-8859-9");
// UTF-8 (OK)
因此,您可能希望在指定预期编码时使用特定顺序。不过,请记住,这并非万无一失。
if ($input_is_not_UTF8) $input_is_windows1252 = true;
应该是相当安全的。另请参阅:html.spec.whatwg.org/multipage/…
解决 RSS 提要的字符编码似乎是 complicated。即使是普通的网页也经常忽略或谎报其编码。
因此,您可以尝试使用正确的方法来检测编码,然后回退到某种形式的自动检测(猜测)。
charset
/encoding
声明的用途:描述数据的编码方式。
您需要测试输入的字符集,因为响应可以使用不同的编码进行编码。
我通过使用以下函数进行检测和翻译来强制将所有内容发送到 UTF-8:
function fixRequestCharset()
{
$ref = array(&$_GET, &$_POST, &$_REQUEST);
foreach ($ref as &$var)
{
foreach ($var as $key => $val)
{
$encoding = mb_detect_encoding($var[$key], mb_detect_order(), true);
if (!$encoding)
continue;
if (strcasecmp($encoding, 'UTF-8') != 0)
{
$encoding = iconv($encoding, 'UTF-8', $var[$key]);
if ($encoding === false)
continue;
$var[$key] = $encoding;
}
}
}
}
该例程会将来自远程主机的所有 PHP 变量转换为 UTF-8。
如果无法检测或转换编码,则忽略该值。
您可以根据自己的需要对其进行自定义。
只需在使用变量之前调用它。
echo mb_detect_encoding($str, "auto");
或者
echo mb_detect_encoding($str, "UTF-8, ASCII, ISO-8859-1");
我真的不知道结果是什么,但我建议您只获取一些具有不同编码的提要,然后尝试 mb_detect_encoding
是否有效。
auto 是“ASCII,JIS,UTF-8,EUC-JP,SJIS”的缩写。它返回检测到的字符集,您可以使用它通过 iconv 将字符串转换为 UTF-8。
<?php
function convertToUTF8($str) {
$enc = mb_detect_encoding($str);
if ($enc && $enc != 'UTF-8') {
return iconv($enc, 'UTF-8', $str);
} else {
return $str;
}
}
?>
我没有测试过,所以不能保证。也许还有更简单的方法。
我知道这是一个较老的问题,但我认为有用的答案永远不会受到伤害。我在桌面应用程序、SQLite 和 GET/POST 变量之间的编码存在问题。有些是 UTF-8,有些是 ASCII,当涉及到外来字符时,基本上一切都会搞砸。
这是我的解决方案。它会在处理之前的每个页面加载时清除您的 GET/POST/REQUEST(我省略了 cookie,但您可以根据需要添加它们)。它在标题中运行良好。如果 PHP 无法自动检测到源编码,它会抛出警告,所以这些警告会被 @'s 抑制。
//Convert everything in our vars to UTF-8 for playing nice with the database...
//Use some auto detection here to help us not double-encode...
//Suppress possible warnings with @'s for when encoding cannot be detected
try
{
$process = array(&$_GET, &$_POST, &$_REQUEST);
while (list($key, $val) = each($process)) {
foreach ($val as $k => $v) {
unset($process[$key][$k]);
if (is_array($v)) {
$process[$key][@mb_convert_encoding($k,'UTF-8','auto')] = $v;
$process[] = &$process[$key][@mb_convert_encoding($k,'UTF-8','auto')];
} else {
$process[$key][@mb_convert_encoding($k,'UTF-8','auto')] = @mb_convert_encoding($v,'UTF-8','auto');
}
}
}
unset($process);
}
catch(Exception $ex){}
很简单:当你得到不是 UTF-8 的东西时,你必须把它编码成 UTF-8。
因此,当您获取某个符合 ISO 8859-1 的提要时,请通过 utf8_encode
对其进行解析。
但是,如果您要获取 UTF-8 提要,则无需执行任何操作。
harpax' answer 为我工作。就我而言,这已经足够了:
if (isUTF8($str)) {
echo $str;
}
else
{
echo iconv("ISO-8859-1", "UTF-8//TRANSLIT", $str);
}
我一直在寻找编码的解决方案,这个页面可能是多年搜索的结论!我测试了您提到的一些建议,以下是我的笔记:
这是我的测试字符串:
这是一个“wròng wrìtten”字符串,但我需要 pù 'sòme' 特殊字符才能看到它们,由 fùnctìon 转换!! & 而已!
我执行 INSERT 以将此字符串保存在数据库中设置为 utf8_general_ci
的字段中
我的页面的字符集是 UTF-8。
如果我像这样进行 INSERT,在我的数据库中,我有一些字符可能来自火星......
所以我需要将它们转换成一些“理智的”UTF-8。我尝试了 utf8_encode()
,但外星人字符仍在入侵我的数据库...
所以我尝试使用 8 号发布的函数 forceUTF8
,但在数据库中保存的字符串如下所示:
这是一个“wròng wrìtten”字符串 bùt 我不需要 pù 'sòme' 特殊字符才能看到 thèm,由 fùnctìon 转换! & 而已!
因此,在此页面上收集更多信息并将它们与其他页面上的其他信息合并,我用这个解决方案解决了我的问题:
$finallyIDidIt = mb_convert_encoding(
$string,
mysql_client_encoding($resourceID),
mb_detect_encoding($string)
);
现在在我的数据库中,我的字符串具有正确的编码。
笔记:
唯一需要注意的是函数 mysql_client_encoding
!您需要连接到数据库,因为此函数需要一个资源 ID 作为参数。
但是,我只是在我的 INSERT 之前进行重新编码,所以对我来说这不是问题。
UTF-8
客户端编码?这种方式不需要手动转换
在整理好你的 PHP 脚本之后,不要忘记告诉 MySQL 你正在传递什么字符集并且想要接收什么字符集。
示例:将字符设置为 UTF-8
在拉丁语 1 I/O 会话中将 UTF-8 数据传递到 Latin 1 表会产生那些讨厌的鸟脚。我每隔一天就会在 OsCommerce 家商店看到这种情况。回来和第四似乎是正确的。但是 phpMyAdmin 会显示真相。通过告诉 MySQL 你传递的是什么字符集,它会为你处理 MySQL 数据的转换。
如何恢复现有的乱码 MySQL 数据是另一个问题。 :)
从标头中获取编码并将其转换为 UTF-8。
$post_url = 'http://website.domain';
/// Get headers ///////////////////////////////////////////////
function get_headers_curl($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
$r = curl_exec($ch);
return $r;
}
$the_header = get_headers_curl($post_url);
/// Check for redirect ////////////////////////////////////////
if (preg_match("/Location:/i", $the_header)) {
$arr = explode('Location:', $the_header);
$location = $arr[1];
$location = explode(chr(10), $location);
$location = $location[0];
$the_header = get_headers_curl(trim($location));
}
/// Get charset ///////////////////////////////////////////////
if (preg_match("/charset=/i", $the_header)) {
$arr = explode('charset=', $the_header);
$charset = $arr[1];
$charset = explode(chr(10), $charset);
$charset = $charset[0];
}
///////////////////////////////////////////////////////////////////
// echo $charset;
if($charset && $charset != 'UTF-8') {
$html = iconv($charset, "UTF-8", $html);
}
Ÿ
是 ß
的 Mojibake。在您的数据库中,您可能有以下十六进制值之一(使用 SELECT HEX(col)...
)找出):
DF 如果列是“latin1”,
C39F 如果列是 utf8 -- 或者 -- 它是 latin1,但是“双编码”
C383C5B8 如果双重编码为 utf8 列
你不应该在 PHP 中使用任何编码/解码函数;相反,您应该正确设置数据库及其连接。
如果涉及 MySQL,请参阅:Trouble with UTF-8 characters; what I see is not what I stored
SELECT HEX(col)...
以查看表格中的内容。
尝试不使用“自动”
那是:
mb_detect_encoding($text)
代替:
mb_detect_encoding($text, 'auto')
可在此处找到更多信息:mb_detect_encoding
尝试使用它...所有不是 UTF-8 的文本都将被翻译。
function is_utf8($str) {
return (bool) preg_match('//u', $str);
}
$myString = "Fußball";
if(!is_utf8($myString)){
$myString = utf8_encode($myString);
}
// or 1 line version ;)
$myString = !is_utf8($myString) ? utf8_encode($myString) : trim($myString);
我在 http://deer.org.ua/2009/10/06/1/ 找到了解决方案:
class Encoding
{
/**
* http://deer.org.ua/2009/10/06/1/
* @param $string
* @return null
*/
public static function detect_encoding($string)
{
static $list = ['utf-8', 'windows-1251'];
foreach ($list as $item) {
try {
$sample = iconv($item, $item, $string);
} catch (\Exception $e) {
continue;
}
if (md5($sample) == md5($string)) {
return $item;
}
}
return null;
}
}
$content = file_get_contents($file['tmp_name']);
$encoding = Encoding::detect_encoding($content);
if ($encoding != 'utf-8') {
$result = iconv($encoding, 'utf-8', $content);
} else {
$result = $content;
}
我认为 @ 是一个错误的决定,并对 deer.org.ua 的解决方案进行了一些更改。
当您尝试处理多种语言时,例如日语和韩语,您可能会遇到麻烦。
带有 'auto' 参数的 mb_convert_encoding 效果不佳。设置 mb_detect_order('ASCII,UTF-8,JIS,EUC-JP,SJIS,EUC-KR,UHC') 没有帮助,因为它会错误地检测到 EUC-*。
我得出的结论是,只要输入字符串来自 HTML,它就应该在元元素中使用“字符集”。我使用 Simple HTML DOM Parser 因为它支持无效的 HTML。
下面的代码片段从网页中提取标题元素。如果您想转换整个页面,那么您可能需要删除一些行。
<?php
require_once 'simple_html_dom.php';
echo convert_title_to_utf8(file_get_contents($argv[1])), PHP_EOL;
function convert_title_to_utf8($contents)
{
$dom = str_get_html($contents);
$title = $dom->find('title', 0);
if (empty($title)) {
return null;
}
$title = $title->plaintext;
$metas = $dom->find('meta');
$charset = 'auto';
foreach ($metas as $meta) {
if (!empty($meta->charset)) { // HTML5
$charset = $meta->charset;
} else if (preg_match('@charset=(.+)@', $meta->content, $match)) {
$charset = $match[1];
}
}
if (!in_array(strtolower($charset), array_map('strtolower', mb_list_encodings()))) {
$charset = 'auto';
}
return mb_convert_encoding($title, 'UTF-8', $charset);
}
此版本适用于德语,但您可以修改 $CHARSETS 和 $TESTCHARS。
class CharsetDetector
{
private static $CHARSETS = array(
"ISO_8859-1",
"ISO_8859-15",
"CP850"
);
private static $TESTCHARS = array(
"€",
"ä",
"Ä",
"ö",
"Ö",
"ü",
"Ü",
"ß"
);
public static function convert($string)
{
return self::__iconv($string, self::getCharset($string));
}
public static function getCharset($string)
{
$normalized = self::__normalize($string);
if(!strlen($normalized))
return "UTF-8";
$best = "UTF-8";
$charcountbest = 0;
foreach (self::$CHARSETS as $charset)
{
$str = self::__iconv($normalized, $charset);
$charcount = 0;
$stop = mb_strlen($str, "UTF-8");
for($idx = 0; $idx < $stop; $idx++)
{
$char = mb_substr($str, $idx, 1, "UTF-8");
foreach (self::$TESTCHARS as $testchar)
{
if($char == $testchar)
{
$charcount++;
break;
}
}
}
if($charcount > $charcountbest)
{
$charcountbest = $charcount;
$best = $charset;
}
//echo $text . "<br />";
}
return $best;
}
private static function __normalize($str)
{
$len = strlen($str);
$ret = "";
for($i = 0; $i < $len; $i++)
{
$c = ord($str[$i]);
if ($c > 128) {
if (($c > 247))
$ret .= $str[$i];
elseif
($c > 239) $bytes = 4;
elseif
($c > 223) $bytes = 3;
elseif
($c > 191) $bytes = 2;
else
$ret .= $str[$i];
if (($i + $bytes) > $len)
$ret .= $str[$i];
$ret2 = $str[$i];
while ($bytes > 1)
{
$i++;
$b = ord($str[$i]);
if ($b < 128 || $b > 191)
{
$ret .= $ret2;
$ret2 = "";
$i += $bytes-1;
$bytes = 1;
break;
}
else
$ret2 .= $str[$i];
$bytes--;
}
}
}
return $ret;
}
private static function __iconv($string, $charset)
{
return iconv ($charset, "UTF-8", $string);
}
}
我在使用 phpQuery(ISO-8859-1 而不是 UTF-8)时遇到了同样的问题,这个 hack 帮助了我:
$html = '<?xml version="1.0" encoding="UTF-8" ?>' . $html;
mb_internal_encoding('UTF-8')
、phpQuery::newDocumentHTML($html, 'utf-8')
、mbstring.internal_encoding
和其他操作没有产生任何效果。
对于汉字,一般采用GBK编码。此外,在测试时,投票最多的答案不起作用。这是一个简单的修复程序,使其也能正常工作:
function toUTF8($raw) {
try{
return mb_convert_encoding($raw, "UTF-8", "auto");
}catch(\Exception $e){
return mb_convert_encoding($raw, "UTF-8", "GBK");
}
}
备注:这个解决方案是在 2017 年编写的,应该可以解决当时 PHP 的问题。我没有测试最新的 PHP 是否已经正确理解 auto
。
不定期副业成功案例分享