Как написать парсер HTML на PHP

Много разных задач сводятся к парсинку HTML. И не только сбор контента для ГС. Вот мне например понадобилось разобрать HTML собственного контента, чтобы культурненько облагородить. Ну например внешние ссылочки спрятать.

Сначала я подумал – фигня. Найти в тексте строчку “<a href=http://” и получи себе внешнюю ссылочку. Но ведь в контенте теги и атрибуты могут писаться с заглавной буквы. И ещё между A и HREF может стоять произвольное количество пробелов. Но самое главное, между A и HREF могут быть разные всякие другие слова. Короче выхода нет, кроме как строить нормальное дерево DOM и его анализировать. То бишь парсить.

Но оказывается библиотека парсера давно есть и встроена в PHP. Осталось только попользоваться!

Расписывать не буду. Мануал превзойти тяжело. А примерчик для направления – самый раз будет.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Содаём объект
$dom = new domDocument;
$dom->strictErrorChecking=false;
$dom->recover = true;
 
// скармливаем объекту готовую строку
$dom->loadHTML($strHTML);
 
//или удобнее будет файл?
$dom->loadHTMLFile($htmlFile);
 
//И всё! Можно вытащить любой аттрибут любого элемента!
foreach($dom->getElementsByTagName('img') as $img_dom)
$img_src= html_entity_decode($img_dom->getAttribute('src'));
 
// А вот можно переделать ссылку на свой хитрый редирект
$link_dom->setAttribute('href','/link/redirect/'.$link->id);
}
В результате можно вывести код, который получился в результате.
echo $dom->saveHTML();

Пара слов про кириллицу
=======================
Раз уж вы читаете этот сайт на русском языке, то и парсить вероятно будете тоже русскоязычный сайт. А вот с ними не всё так просто.

В документации написано, что класс делает utf_encode, но ситуация ещё сложнее. Вот пример

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$dom = new domDocument('1.0', 'UTF-8');
$dom->loadHTML("<p>Германия, Великобритания и Франция</p>");
 
// Тут работает
foreach($dom->getElementsByTagName('p') as $p_dom)
echo utf8_decode ($p_dom->nodeValue);
 
// А тут фигня
echo utf8_decode($dom->saveHTML());
 
// Тут по-лучше, но всё равно фигня
echo html_entity_decode($dom->saveHTML());
?>

Вот такой получаю результат:
Screenshot_2

По этому примеру понятно, что преобразование похоже на htmlentities, но не совсем оно.

В общем чтобы не мучиться я решил зашифровать кириллицу в url_encode. Потом делаешь url_decode – и кириллица работает. Но есть проблема – знаки тегов тоже конвертируются, и это мешает парсить HTML. Я решил проблему путём написания простенькой функции. Теперь код выглядит так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
 
function my_urlencode($str)
{
// То же urlencode, но только кириллицы
$tr = array( '%3C' =>'<', '%3E' =>'>', '%3D' =>'=',
'+' =>' ', '%22' =>'"', '%27' =>'\'',
'%21' =>'!','%2F' =>'/','%3A' =>':'
);
$str=strtr(urlencode($str),$tr);
return $str;
}
 
$dom = new domDocument('1.0', 'UTF-8');
$dom->loadHTML(my_urlencode("<p>Германия, Великобритания и Франция</p>"));
 
// Тут работает
foreach($dom->getElementsByTagName('p') as $p_dom)
echo urldecode($p_dom->nodeValue);
 
// И так работает
echo urldecode($dom->saveHTML());
 
?>

Теперь точно работает.