2010-03-24 35 views
9

Tôi chỉ muốn làm việc với UTF8. Vấn đề là tôi không biết bảng mã của mọi trang web. Làm thế nào tôi có thể phát hiện nó và chuyển đổi sang UTF8?PHP: Chuyển đổi đầu ra curl_exec thành UTF8

<?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; 

Xem này tại địa chỉ: http://paulisageek.com/tmp/curl-utf8

magic() là gì?

Trả lời

24

Đi theo lời khuyên Gumbo và Pekka, tôi đã viết 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; 
} 

Các regexes chủ yếu là từ http://nadeausoftware.com/articles/2007/06/php_tip_how_get_web_page_content_type

+1

Ooohh ngọt ngào! Tôi sẽ kiểm tra ổ đĩa này khi tôi tìm thấy thời gian. –

+0

Cảm ơn bạn đã chia sẻ, bạn đã cứu mạng tôi! : D –

+0

Thực hiện tốt, nhưng trong trường hợp chúng tôi thực hiện một số quy trình xử lý tiếp theo, chúng tôi muốn sửa các thẻ khi chúng tôi thực hiện. Tôi đã tự do cập nhật mã của bạn với ý nghĩ đó. – DomQ

4

Việc chuyển đổi rất dễ dàng. Phát hiện là phần khó khăn. Bạn có thể thử mb_detect_encoding nhưng đó là một phương pháp rất run rẩy, nghĩa là "đoán" loại nội dung và @troelskn nổi bật trong nhận xét có thể đoán sự khác biệt "thô" ở mức tốt nhất (Có phải mã hóa nhiều byte không?) Nhưng không phát hiện được sắc thái của các bộ ký tự tương tự.

Cách thích hợp sẽ là IMO:

  • Giải thích bất kỳ content-type thẻ Meta trong trang
  • Giải thích bất kỳ content-type tiêu đề gửi bởi máy chủ
  • Nếu điều đó mang lại gì cả, cố gắng "sniff" các mã hóa bằng cách sử dụng mb_detect_encoding()
  • Nếu điều đó không có gì, hãy quay trở lại mặc định đã xác định (có thể là ISO-8859-1, có thể là UTF-8).

Khác với hướng dẫn trong câu trả lời của @ Gumbo, cá nhân tôi nghĩ rằng thẻ Meta nên được ưu tiên hơn tiêu đề máy chủ vì tôi chắc chắn rằng nếu thẻ Meta hiện diện, đó là chỉ báo đáng tin cậy hơn mã hóa thực tế của trang so với cài đặt máy chủ mà một số nhà khai thác trang web thậm chí không biết cách thay đổi. Tuy nhiên, cách chính xác dường như là để xử lý các tiêu đề loại nội dung có mức độ ưu tiên cao hơn.

Đối với trước đây, tôi nghĩ bạn có thể sử dụng get_meta_tags(). Sau này bạn sẽ nhận được từ curl đã có, bạn sẽ chỉ phải phân tích nó. Here là một ví dụ đầy đủ về cách xử lý các tiêu đề phản hồi được phục vụ bởi cURL.

Việc chuyển đổi sau đó sẽ được sử dụng iconv:

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

làm không phải là người khác phải làm điều này? Tôi không thể là người đầu tiên chạy qua vấn đề này. Không có mã hiện tại để phát hiện tốt này? –

+0

@Paul câu hỏi rất hay! Phải có một thư viện, nhưng tôi không biết gì cả.Nếu không có gì khác đi lên, đặt cược tốt nhất của bạn có thể xem xét các lớp học "Trình mô phỏng trình duyệt" PHP, cho dù bất kỳ cái nào trong số này có được triển khai tốt hay không. –

+0

tiêu đề http có lẽ nên được ưu tiên cao hơn thẻ meta. – troelskn

0

Có một trật tự xác định how to specify the character encoding in HTML:

[...] phù hợp với đại lý người dùng phải tuân thủ các ưu tiên sau khi xác định mã hóa ký tự của một tài liệu (từ mức độ ưu tiên cao nhất đến thấp nhất):

  1. Một bộ ký tự HTTP "charset" ameter trong trường "Loại nội dung".
  2. Tuyên bố META với "http-equiv" được đặt thành "Loại nội dung" và giá trị được đặt cho "bộ ký tự".
  3. Thuộc tính charset được đặt trên một yếu tố chỉ định tài nguyên bên ngoài.

Nếu không có khai báo mã hóa ký tự, HTTP defines ISO 8859-1 as default character encoding. Bạn có thể sử dụng nó làm mã hóa ký tự mặc định cho HTML quá hoặc đơn giản là từ chối xử lý phản hồi.

Đối với XHTML bạn bổ sung có XML declaration as source for the encoding:

Trong một tài liệu XML, mã hóa ký tự của tài liệu được xác định trên khai báo XML (ví dụ, <?xml version="1.0" encoding="EUC-JP"?>). Để trình bày một cách rõ ràng các tài liệu có mã hóa ký tự cụ thể, cách tiếp cận tốt nhất là đảm bảo rằng máy chủ web cung cấp các tiêu đề chính xác. Nếu điều này là không thể, một tài liệu muốn đặt mã hóa ký tự của nó một cách rõ ràng phải bao gồm cả khai báo XML khai báo mã hóa và tuyên bố http-equiv meta http (tương đương, <meta http-equiv="Content-type" content="text/html; charset=EUC-JP" />). Trong các tác nhân người dùng phù hợp XHTML, giá trị của khai báo mã hóa của khai báo XML được ưu tiên.

Nếu không có tuyên bố nhân vật mã hóa, XML defines UTF-8 and UTF-16 as default character encoding:

Trừ một mã hóa được xác định bởi một giao thức cấp cao hơn, nó cũng là một lỗi nghiêm trọng nếu một thực thể XML chứa không khai mã hóa và nội dung của nó là không hợp pháp UTF-8 hoặc UTF-16.

Vì vậy, để tổng hợp, thứ tự là:

  1. Một HTTP "charset" tham số trong một lĩnh vực "Content-Type".
  2. Khai báo XML với thuộc tính encoding.
  3. Tuyên bố META với "http-equiv" được đặt thành "Loại nội dung" và giá trị được đặt cho "bộ ký tự".

Nếu không có khai báo mã hóa ký tự, bạn có thể giả định ISO 8859-1 làm mã hóa mặc định cho HTML và phải giả sử UTF-8 hoặc UTF-16 làm mã hóa mặc định cho XHTML.

+0

Tuyệt vời. Có thư viện nào cho giao thức này không? Tôi muốn làm curl và chuyển đổi ký tự với nhau và có UTF8 chỉ trả lại –

+0

@Paul Tarjan: Bạn có thể trường tiêu đề * Content-Type * với 'curl_getinfo'. – Gumbo

+0

Tôi đưa lời khuyên của bạn vào một chức năng, nó trông như thế nào? –

1

Tôi đã vô cùng hạnh phúc để tìm câu trả lời này, nhưng nhận thấy có một lỗ hổng trong việc phát hiện <meta> tag . Nó đơn giản dường như không khớp với bất kỳ thẻ kiểu nội dung nào và nó chưa được trang bị cho các thẻ kiểu HTML5 mới: <meta charset="UTF-8">. Vì vậy, tôi đã viết điều này, hy vọng nó sẽ giúp các bạn, và cảm ơn một lần nữa cho giải pháp tuyệt vời này!

/* 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]; 
    } 
} 

(T.B. tôi không thể tìm ra cách để đăng bài này như một lời nhận xét, vì nó rõ ràng không phải là một câu trả lời đầy đủ.)

Các vấn đề liên quan