Я соскабливаю с сайта UTF-8, используя Goutte , который внутренне использует Guzzle. Сайт объявляет метатег UTF-8, таким образом:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
Однако заголовок типа контента:
Content-Type: text/html
и не:
Content-Type: text/html; charset=utf-8
Таким образом, когда я царапаю, Goutte не видит, что это UTF-8, и неправильно считывает данные. Удаленный сайт не находится под моим контролем, поэтому я не могу решить проблему там! Вот набор сценариев для репликации проблемы. Во-первых, скребок:
<?php require_once realpath(__DIR__ . '/..') . '/vendor/goutte/goutte.phar'; $url = 'http://crawler-tests.local/utf-8.php'; use Goutte\Client; $client = new Client(); $crawler = $client->request('get', $url); $text = $crawler->text(); echo 'Whole page: ' . $text . "\n";
Теперь тестовая страница будет размещена на веб-сервере:
<?php // Correct #header('Content-Type: text/html; charset=utf-8'); // Incorrect header('Content-Type: text/html'); ?> <!DOCTYPE html> <html> <head> <title>UTF-8 test</title> <meta charset="utf-8" /> </head> <body> <p>When the Content-Header header is incomplete, the pound sign breaks: £15,216</p> </body> </html>
Вот результат теста Goutte:
Целая страница: тест UTF-8 Когда заголовок Content-Header является неполным, знак фунта прерывается: £ 15,216
Как вы можете видеть из комментариев в последнем скрипте, правильно объявляя набор символов в заголовке, он исправляет все. Я охотился в Goutte, чтобы увидеть, есть ли что-то похожее на то, что это заставит набор символов, но безрезультатно. Есть идеи?
Проблема на самом деле связана с symfony / browser-kit и symfony / domcrawler. Client
browserkit не проверяет метатеги HTML, чтобы определить только кодировку, только заголовок содержимого. Когда тело ответа передается domcrawler, оно рассматривается как кодировка по умолчанию ISO-8859-1 . Изучив метатеги, решение должно быть возвращено, а DomDocument перестроен, но этого никогда не произойдет.
$crawler->text()
является обертка $crawler->text()
с помощью utf8_decode()
:
$text = utf8_decode($crawler->text());
Это работает, если вход UTF-8. Я полагаю, что для других кодировок нечто подобное может быть достигнуто с помощью iconv()
или так. Однако вы должны помнить об этом каждый раз, когда вы вызываете text()
.
Более общий подход заключается в том, чтобы заставить Domcrawler полагать, что он имеет дело с UTF-8. С этой целью я придумал плагин Guzzle, который перезаписывает (или добавляет) кодировку в заголовке ответа типа содержимого. Вы можете найти его на странице https://gist.github.com/pschultz/6554265 . Использование выглядит так:
<?php use Goutte\Client; $plugin = new ForceCharsetPlugin(); $plugin->setForcedCharset('utf-8'); $client = new Client(); $client->getClient()->addSubscriber($plugin); $crawler = $client->request('get', $url); echo $crawler->text();
Кажется, у меня были две ошибки, одна из которых была идентифицирована в ответ Питера. Другой способ, которым я отдельно использую класс Symfony Crawler для изучения фрагментов HTML.
Я делал это (для разбора HTML для строки таблицы):
$subCrawler = new Crawler($rowHtml);
Тем не менее, добавление HTML через конструктор не указывает способ, которым может быть задан набор символов, и я полагаю, что ISO-8859-1 снова является значением по умолчанию.
Просто используя addHtmlContent
это правильно; второй параметр указывает набор символов, и по умолчанию он имеет значение UTF-8, если он не указан.
$subCrawler = new Crawler(); $subCrawler->addHtmlContent($rowHtml);
Crawler
пытается обнаружить кодировку из <meta charset
но часто ее не хватает, а затем Crawler
использует кодировку по умолчанию (ISO-8859-1) – это источник проблемы, описанный в этом потоке.
Когда мы передаем контент Crawler
через конструктор, мы пропускаем заголовок Content-Type
который обычно содержит кодировку.
Вот как мы можем справиться с этим:
$crawler = new Crawler(); $crawler->addContent( $response->getBody()->getContents(), $response->getHeaderLine('Content-Type') );
С помощью этого решения мы используем правильную кодировку из ответа сервера и не привязываем наше решение к какой-либо одной кодировке, и, конечно же, после этого нам не нужно декодировать каждую полученную строку от Crawler
(используя utf8_decode()
или как-то иначе).