В этой статье мы рассмотрим один из примеров написания скрипта для парсинга HTML-страниц с использованием XPath на примере сайта bing.com.
Сперва определимся с тем, что такое XPath и зачем оно нужно, если есть регулярные выражения?
XPath (XML Path Language) — это язык запросов к элементам XML-подобного документа (далее для краткости просто XML).
XPath призван реализовать навигацию по DOM в XML.
Regexp — формальный язык поиска и осуществления манипуляций с подстроками в тексте, основанный на использовании метасимволов.
По сути это строка-образец (шаблон), состоящая из символов и метасимволов и задающая правило поиска.
Итак, главная разница в том, что XPath специализируется на XML, а Regexp — на любом виде текста.
В: Зачем использовать XPath, если есть regexp, в котором можно сделать тоже самое?
О: Простота поддержки.
Синтаксис у regexp такой, что уже через неделю может быть проще всё переписать, чем вносить изменения,
а с XPath можно спокойно работать. И синтаксис у xpath довольно компактный,xml’ё-фобы могут быть спокойны.
Простой пример для вдохновения — получим значение атрибута «href» у, например, тега «a».
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<a href="https://ya.ru">Yohoho!</a> Regexp: <a.*?href=("|')(.*?)("|').*?> XPath: "string(//a/@href)" XPath + PHP: $dom = new DOMDocument; // "@" Это, конечно, плохо. Но HTML не обязан быть // валидным, в отличие от XML. @$dom->loadHTML("<a href=\"https://ya.ru\">Yohoho!</a>"); $xpath = new DOMXpath($dom); $res = $xpath->query("//a"); echo $res->item(0)->getAttribute("href") . PHP_EOL; |
Быстро (несколько небольших страниц) пробежаться по основам XPath можно в туториале от
Как использовать XPath в PHP можно почитать в документации на
И в небольшом тутораильчике от
Теперь определимся с необходимым функционалом скрипта:
* Возможность указывать произвольный поисковый запрос
* Парсим только первую страницу поисковой выдачи
* Из поисковой выдачи нам нужно:
* заголовок
* ссылка
* номер в выдаче
Исходя из нашего ТЗ составляем примерный алгоритм работы скрипта:
1) Заходим на bing.com
2) Вводим поисковую фразу
3) Получаем со страницы необходимый результат
Приступим к написанию парсера поисковой выдачи http://bing.com.
Для начала, создадим базовый каркас скрипта.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// coding: windows-1251 // Настройка HumanEmulator // ----------------------------------------------- // Где запущен XHE $xhe_host = "127.0.0.1:7010"; // HumanEmulator lib require "../../Templates/xweb_human_emulator.php"; // Our tools require "tools/functions.php"; // Настройки скрипта // ----------------------------------------------- // Скрипт // ----------------------------------------------- // Quit $app->quit(); |
В настройки добавим переменную для хранения поискового запроса.
1 2 |
// Поисковый запрос $text = "ХуманЭмулятор"; |
Заходим на сайт.
1 2 3 |
// Базовый URL $base_url = "https://www.bing.com/?setlang=en"; $browser->navigate($base_url); |
Вводим поисковую фразу.
1 2 3 4 |
$input->set_value_by_attribute("name", "q", true, $text); sleep(1); $element->click_by_attribute("type", "submit"); sleep(5); |
Сохраним в переменную содержимое страницы.
1 2 |
// Получаем содержимое страницы $content = $webpage->get_body(); |
Настроим xpath-объект:
1 2 3 |
$dom = new DOMDocument; @$dom->loadHTML($content); $xpath = new DOMXpath($dom); |
Теперь у объекта $xpath есть метод «query» в который мы будем передавать наше xpath-выражение.
Давайте начнём создавать xpath-выражение.
Открыв исходный код страницы с результатами поисковой выдачи увидим, что сами результаты находятся внутри тега «li».
1 2 3 |
<li class='b_algo'> <!-- ... --> </li> |
Т.о. наше xpath-выражение выберет со страницы все поисковые результаты.
1 |
$results = $xpath->query("//li[@class=\"b_algo\"]"); |
На одной странице у нас должно быть 1 или больше результатов, проверим себя:
1 2 3 4 5 6 7 8 9 10 11 |
if($results === false) { echo "С нашим xpath-выражением что-то не так." . PHP_EOL; $app->quit(); } elseif($results->length === 0) { echo "Поисковый запрос '{$text}' не принёс результатов." . PHP_EOL: $app->quit(); } echo "Нашли {$results->length} совпадений." . PHP_EOL; |
Здесь стоит обратить внимание на ветку if, где мы сравниваем кол-во результатов xpath-поиска с нулём.
Если наше xpath-выражение ничего не нашло, то это может означать две вещи:
* Bing действительно ничего не нашёл.
* Bing что-то нашёл, но поменял вёрстку на странице, и наше xpath-выражение необходимо исправлять.
2-й пункт достаточно коварный, в таких случаях, когда xpath-выражение ничего не находит необходимо дополнительно
сверятся, чтобы удостоверится, что xpath-выражение не устарело (хотя и это не даст 100% гарантий).
В нашем случае будем сверяться с тем, что Bing пишет кол-во найденных результатов.
1 |
<span class="sb_count" data-bm="4">14 results</span> |
А если результатов по поисковому запросу нет, то:
1 2 3 4 |
<li class="b_no" data-bm="4"> <h1>No results found for ...</h1> <!-- ... --> </li> |
Т.о. мы получаем такую конструкцию проверки:
— Если xpath-запрос ничего не нашёл и поисковый запрос ничего не нашёл, то на странице будет html-код с «No results found».
— Если xpath-запрос ничего не нашёл, а поисковый запрос что-то нашёл, то на странице будет html-код с «N results».
Обновим проверку результата xpath-запроса.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
if($results === false) { echo "С нашим xpath-выражением что-то не так." . PHP_EOL; $app->quit(); } elseif($results->length === 0) { // Если bing ничего не нашёл $check_results1 = $xpath->query("//li[@class=\"b_no\"]"); // Если bing что-то нашёл $check_results2 = $xpath->query("//span[@class=\"sb_count\"]"); if($check_results1 === false or $check_results2 === false) { echo "С нашим xpath-выражением что-то не так." . PHP_EOL; $app->quit(); } if($check_results1->length > 0 and $check_results2->length === 0) { echo "Поисковый запрос '{$text}' не принёс результатов." . PHP_EOL: $app->quit(); } else { echo "С нашим xpath-выражением что-то не так." . PHP_EOL; $app->quit(); } } else { echo "Нашли {$results->length} совпадений для '{$text}' на 1-й странице поисковой выдачи." . PHP_EOL; } |
Если всё хорошо и что-то нашлось, то у нас в $results будет N сущностей с результатами отработки xpath-запроса.
Каждая сущность будет содержать такой HTML-код
1 2 3 4 5 6 7 8 9 |
<div class="b_title"> <h2> <a h="ID=SERP,5082.1" href="http://www.armadaboard.com/topic25915.html"> Для чего нужен <strong>Хуман Эмулятор</strong>? > … </a> </h2> <!-- // ... --> </div> |
Теперь можно приступить непосредственно к выборке интересующих нас данных.
1 2 3 4 |
foreach($results as $num => $item) { // В $item у нас сущность с одним результатом из поисковой выдачи } |
Получаем номер в поисковой выдаче.
1 |
echo "Номер в поисковой выдаче: " . ($num + 1) . PHP_EOL; |
Получаем заголовок.
1 2 3 4 5 6 7 8 |
$title = $xpath->query("div[contains(@class, \"title\")]/h2/a", $item); if($title === false or $title->length !== 1) { echo "С нашим xpath-выражением что-то не так." . PHP_EOL; $app->quit(); } $title = utf8_decode($title->item(0)->textContent); echo "Заголовок: '{$title}'" . PHP_EOL; |
В данном случае мы в метод «query» передали вторым параметром текущий $item и в xpath-запросе не указывали «//» (т.е. искать сначала страницы).
2-й параметр означает контекст поиска для xpath-запроса, т.е. искать будем не по всей странице, а только по маленькому html-кусочку из $item.
И, наконец-то, получаем ссылку.
1 2 3 4 5 6 7 8 |
$link = $xpath->query("div[contains(@class, \"title\")]/h2/a", $item); if($link === false or $link->length !== 1) { echo "С нашим xpath-выражением что-то не так." . PHP_EOL; $app->quit(); } $link = $link->item(0)->getAttribute("href"); echo "Ссылка: '{$link}'" . PHP_EOL; |
Вообще-то, ссылку можно было получить и без совершения дополнительного xpath-запроса, использовав результат из поиска заголовка.
Но оставим так, для наглядности.
1 2 3 4 5 6 7 8 9 10 11 12 |
// Получаем заголовок и ссылку // $title_link = $xpath->query("div[contains(@class, \"title\")]/h2/a", $item); if($title_link === false or $title_link->length !== 1) { echo "С нашим xpath-выражением что-то не так." . PHP_EOL; $app->quit(); } $title = utf8_decode($title_link->item(0)->textContent); echo "Заголовок: '{$title}'" . PHP_EOL; $link = $title_link->item(0)->getAttribute("href"); echo "Ссылка: '{$link}'" . PHP_EOL; |
Полезные ссылки:
Скрипт написан 28.04.2015 в Human Emulator 4.9.18 Advanced.