Задача спарсить и обработать необходимую информацию со стороннего сайта встает перед веб-разработчиком довольно часто и по самым разнообразным причинам: таким образом можно заполнять свой проект контентом, динамически подгружать какую-то информацию и так далее.
В таких случаях перед программистом встает вопрос: какую из десятков библиотек выбрать? В этой статье мы постарались рассмотреть самые популярные варианты и выбрать из них лучший.
Регулярные выражения
Даже не смотря на то, что «регулярки» - это первое, что приходит на ум, использовать их для настоящих проектов не стоит.
Да, с простыми задачами регулярные выражения справляются лучше всех, но его использование значительно затрудняется, когда нужно спарсить большой и сложный кусок HTML-кода, который, к тому же, не всегда соответствует какому-то определенному шаблону и вообще может содержать синтаксические ошибки.
Вместо «допиливания» своего регулярного выражения при каждом малейшем изменении кода рекомендуем использовать инструменты ниже - это и проще, и удобнее, и надежнее.
XPath и DOM
htmlSQL
Если вы не используете PHP, то можете ознакомится с кратким списком похожих инструментов для других языков программирования.
Решил привести статью в актуальный вид. Ранее на данной странице был представлен универсальный парсер HTML страниц на PHP. Но прошло уже более 4 лет, я наработал больше опыта в области разработки парсеров. И решил выложить новый пример PHP парсера с детальным разбором алгоритма работы.
Парсер прошлой версии носил гордое звание универсального, но это было весьма условное обозначение. Скрипт имел много ограничений, для его полноценного использования требовались знания в регулярных выражениях на PHP и JS.
Я подумал, подумал и решил, что более универсальным решением будет показать пример парсера на PHP и рассказать, как он работает. Так программисты, которые ранее не писали парсеров смогут решить свои задачи. А заказчики смогут понять возможности PHP в области парсинга сайтов и что реально можно требовать от программистов.
Парсер — это программа, которая анализирует входные текстовые данные, извлекает нужную информацию и на основе полученных данных выдаёт результат в заданном формате.
Общий алгоритм PHP парсинга предполагает, что ваш скрипт делает запрос по заданному адресу, получает ответ от сервера в виде HTML страницы, либо в каком-то другом текстовом формате, например CSV, JSON, XML. Далее полученная информация анализируется, из неё извлекаются (парсятся) нужные данные, на основе которых формируется результат. Полученные данные можно вывести на экран, либо записать в файл или БД.
Пример простого PHP парсера html контента
Предположим нам нужно спарсить цену на товары на сайте gearbest.com. Скрипт считывает заданную страницу, потом посредством регулярных выражений анализирует её контент и выделяет нужные нам куски HTML кода. Далее полученный результат выводится на экран.
/Us"; $buffer = array(); preg_match($regexp, $page, $buffer); $res_arr["price_list"]["currency"] = $buffer; $res_arr["error"] = ""; } else { $res_arr["price"] = 0; $res_arr["currency"] = "nodata"; $res_arr["error"] = "Ошибка загрузки страницы"; } return $res_arr; } /* --- 1.4 --- Вывод данных в HTML */ /* --- 1.4.1 --- Вывод полученых цен */ function price_list_html($price_list) { echo "
Цена: " . $price_list["price"] . " " . $price_list["currency"] . "
"; } /* --- 1.4.2 --- Вывод ошибок */ function error_list_html($error) { if (!empty($error)) { echo "Во время обработки запроса произошли следующие ошибки:
\n"; echo "- \n";
foreach($error as $error_row) {
echo "
- " . $error_row . " \n"; } echo "
Статус: FAIL
\n"; } else { echo "Статус: OK
\n"; } } /* --- 1.4.3 --- Вывод ошибок загрузки страниц */ function error_page_list_html($error_page) { if (!empty($error_page)) { echo "- \n";
foreach($error_page as $error_row) {
echo "
- [" . $error_row . "] " . $error_row . " - " . $error_row . " \n"; } echo "
Парсер цены товара на Gearbest.com
index.php — основной файл PHP скрипта парсера. Код парсера актуален на момент публикации. Со временем HTML код сайта источника может меняться и регулярные выражения уже не будут к нему подходить.
Существуют разные способы установки скрипта. Я работал с ним из-под XAMPP. Но можно парсер запускать прямо с . Просто заливаете файл index.php к себе на сайт в какую-либо папку и обращаетесь к нему через адресную строку браузера. Предположим, что вы закинули скрипт в папку my-parser в корневой директории вашего хостинга. Тогда в адресной строке нужно набрать URL: http://вашдомен.ru/my-parser/ .
Скриншот главной страницы парсера цены с сайта gearbest.com:
1. На главной странице парсера мы должны ввести адрес страницы товара. После нажатия на кнопку «Старт» страница перезагружается, отправляются данные формы на сервер и PHP скрипт делает запрос по заданному адресу с помощью библиотеки cURL.
За это действие отвечает функция curl_get_contents()
, которая является аналогом стандартной PHP функции file_get_contents()
, но с расширенным на основе cURL функционалом.
cURL
— это расширение для PHP, которое обеспечивает поддержку библиотеки функций libcurl. Данный набор функций позволяет формировать POST и PUT запросы, скачивать файлы. Поддерживаются различные протоколы http, https, ftp и пр. Можно использовать прокси-серверы, cookies и аутентификацию пользователей. В общем, отличный инструмент для имитации действий пользователя в браузере.
cURL штука для разработки HTML парсеров очень полезная, и в одной из я более подробно расскажу о приёмах работы с ней для целей парсинга.
Обратите внимание, что скрипт видит страницу в текстовом формате и анализировать предстоит именно её HTML код.
3. На следующем этапе формируется и выводится на экран результат, полученный на основе данных после парсинга. В нашем случае это валюта и цена товара.
Таким образом, для успешной разработки парсеров на PHP программисту нужно уметь работать с библиотекой функций cURL и регулярными выражениями PHP.
Как парсить зашифрованные данные
В некоторых случаях сервера отдают HTML страницы в сжатом или защифрованном виде, например Accept-Encoding: gzip. При этом смена поддерживаемых форматов сжатия в запросе может не влиять на формат ответа.
В таких случаях нужно расшифровать ответ, например, стандартной PHP функцией gzdecode(). И дальше можно будет работать по старой схеме.
Данные, заширфованные по алгоритму base64 можно расшифровать функцией base64_encode() .
PHP парсер HTML сайта бесплатно
Собственно ответ на вопрос, где взять PHP парсер сайтов бесплатно, простой — напишите его сами. Базовый алгоритм работы парсеров я выше разобрал в деталях.
Можно поискать уже готовые, написанные ранее кем-то решения вашей задачи. Но сделать парсер, который бы на автомате подходил ко всем вариантам, наверное, невозможно. Под каждый тип задач нужно разрабатывать свой конкретный продукт.
А для тех, кто не хочет париться с регулярными выражениями и настройками парсера, я готов провести его доработку за вас, но, конечно же, это будет стоить денег:-).
Итоговая стоимость услуг разработки определяется после получения конкретного технического задания. Цена устанавливается строго перед началом выполнения работы, в ходе рабочего процесса финансовые условия не изменяются. Работаю по 100% предоплате . Минимальный заказ составляет 2000 рублей .
Часто цена парсера выходит довольно высокой, а связано это с тем, что разработка получается уникальной, заточенной под конкретного заказчика. Разные бывают варианты входных дынных, требуется индивидуальный формат результата на выходе.
В этом случае формально тоже можно получить парсер для себя бесплатно. Например, цена разработки парсера получилась 9 000 рублей. Вы ищете 9 человек с аналогичной проблемой и собираете с них по 1000 рублей, заказываете разработку парсера. Потом делаете 10 копий, 1 себе и 9 отдаёте вашим знакомым.
В следующем цикле статей я покажу примеры реализации более сложных парсеров , и т.п.
В общем, дорогие мои читатели, чем смог тем помог, читайте, учитесь и не забывайте ставить ссылки на блог .
Чтобы написать хороший и работоспособный скрипт для парсинга контента нужно потратить немало времени. А подходить к сайту-донору, в большинстве случаев, стоит индивидуально, так как есть масса нюансов, которые могут усложнить решение нашей задачи. Сегодня мы рассмотрим и реализуем скрипт парсера при помощи CURL, а для примера получим категории и товары одного из популярных магазинов.
Если вы попали на эту статью из поиска, то перед вами, наверняка, стоит конкретная задача и вы еще не задумывались над тем, для чего ещё вам может пригодится парсер. Поэтому, перед тем как вдаваться в теорию и непосредственно в код, предлагаю прочесть предыдущею статью – , где был рассмотрен один из простых вариантов, да и я буду периодически ссылаться на неё.
Работать мы будем с CURL, но для начала давайте разберёмся, что эта аббревиатура обозначает. CURL – это программа командной строки, позволяющая нам общаться с серверами используя для этого различные протоколы, в нашем случаи HTTP и HTTPS. Для работы с CURL в PHP есть библиотека libcurl, функции которой мы и будем использовать для отправки запросов и получения ответов от сервера.
Как можно увидеть из скриншота все категории находятся в ненумерованном списке, а подкатегории:
Внутри отельного элемента списка в таком же ненумерованном. Структура несложная, осталось только её получить. Товары мы возьмем из раздела «Все телефоны»:
На странице получается 24 товара, у каждого мы вытянем: картинку, название, ссылку на товар, характеристики и цену.
Пишем скрипт парсера
Если вы уже прочли предыдущею статью, то из неё можно было подчеркнуть, что процесс и скрипт парсинга сайта состоит из двух частей:
- Нужно получить HTML код страницы, которой нам необходим;
- Разбор полученного кода с сохранением данных и дальнейшей обработки их (как и в первой статье по парсингу мы будем использовать phpQuery, в ней же вы найдете, как установить её через composer).
Для решения первого пункта мы напишем простой класс с одним статическим методом, который будет оберткой над CURL. Так код можно будет использовать в дальнейшем и, если необходимо, модифицировать его. Первое, с чем нам нужно определиться - как будет называться класс и метод и какие будут у него обязательные параметры:
Class Parser{ public static function getPage($params = ){ if($params){ if(!empty($params["url"])){ $url = $params["url"]; // Остальной код пишем тут } } return false; } }
Основной метод, который у нас будет – это getPage() и у него всего один обязательный параметр URL страницы, которой мы будем парсить. Что ещё будет уметь наш замечательный метод, и какие значения мы будем обрабатывать в нем:
- $useragent – нам важно иметь возможность устанавливать заголовок User-Agent, так мы сможем сделать наши обращения к серверу похожими на обращения из браузера;
- $timeout – будет отвечать за время выполнения запроса на сервер;
- $connecttimeout – так же важно указывать время ожидания соединения;
- $head – если нам потребуется проверить только заголовки, которые отдаёт сервер на наш запрос этот параметр нам просто будет необходим;
- $cookie_file – тут всё просто: файл, в который будут записывать куки нашего донора контента и при обращении передаваться;
- $cookie_session – иногда может быть необходимо, запрещать передачу сессионных кук;
- $proxy_ip – параметр говорящий, IP прокси-сервера, мы сегодня спарсим пару страниц, но если необходимо несколько тысяч, то без проксей никак;
- $proxy_port – соответственно порт прокси-сервера;
- $proxy_type – тип прокси CURLPROXY_HTTP, CURLPROXY_SOCKS4, CURLPROXY_SOCKS5, CURLPROXY_SOCKS4A или CURLPROXY_SOCKS5_HOSTNAME;
- $headers – выше мы указали параметр, отвечающий за заголовок User-Agent, но иногда нужно передать помимо его и другие, для это нам потребуется массив заголовков;
- $post – для отправки POST запроса.
Конечно, обрабатываемых значений много и не всё мы будем использовать для нашей сегодняшней задачи, но разобрать их стоит, так как при парсинге больше одной страницы многое выше описанное пригодится. И так добавим их в наш скрипт:
$useragent = !empty($params["useragent"]) ? $params["useragent"] : "Mozilla/5.0 (Windows NT 6.3; W…) Gecko/20100101 Firefox/57.0"; $timeout = !empty($params["timeout"]) ? $params["timeout"] : 5; $connecttimeout = !empty($params["connecttimeout"]) ? $params["connecttimeout"] : 5; $head = !empty($params["head"]) ? $params["head"] : false; $cookie_file = !empty($params["cookie"]["file"]) ? $params["cookie"]["file"] : false; $cookie_session = !empty($params["cookie"]["session"]) ? $params["cookie"]["session"] : false; $proxy_ip = !empty($params["proxy"]["ip"]) ? $params["proxy"]["ip"] : false; $proxy_port = !empty($params["proxy"]["port"]) ? $params["proxy"]["port"] : false; $proxy_type = !empty($params["proxy"]["type"]) ? $params["proxy"]["type"] : false; $headers = !empty($params["headers"]) ? $params["headers"] : false; $post = !empty($params["post"]) ? $params["post"] : false;
Как видите, у всех параметров есть значения по умолчанию. Двигаемся дальше и следующей строчкой напишем кусок кода, который будет очищать файл с куками при запросе:
If($cookie_file){ file_put_contents(__DIR__."/".$cookie_file, ""); }
Так мы обезопасим себя от ситуации, когда по какой-либо причине не создался файл.
Для работы с CURL нам необходимо вначале инициализировать сеанс, а по завершению работы его закрыть, также при работе важно учесть возможные ошибки, которые наверняка появятся, а при успешном получении ответа вернуть результат, сделаем мы это таким образам:
$ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); // Далее продолжаем кодить тут curl_setopt($ch, CURLINFO_HEADER_OUT, true); $content = curl_exec($ch); $info = curl_getinfo($ch); $error = false; if($content === false){ $data = false; $error["message"] = curl_error($ch); $error["code"] = self::$error_codes[ curl_errno($ch) ]; }else{ $data["content"] = $content; $data["info"] = $info; } curl_close($ch); return [ "data" => $data, "error" => $error ];
Первое, что вы могли заметить – это статическое свойство $error_codes, к которому мы обращаемся, но при этом его ещё не описали. Это массив с расшифровкой кодов функции curl_errno(), давайте его добавим, а потом разберем, что происходит выше.
Private static $error_codes = [ "CURLE_UNSUPPORTED_PROTOCOL", "CURLE_FAILED_INIT", // Тут более 60 элементов, в архиве вы найдете весь список "CURLE_FTP_BAD_FILE_LIST", "CURLE_CHUNK_FAILED" ];
После того, как мы инициализировали соединения через функцию curl_setopt(), установим несколько параметров для текущего сеанса:
- CURLOPT_URL – первый и обязательный - это адрес, на который мы обращаемся;
- CURLINFO_HEADER_OUT –массив с информацией о текущем соединении.
Используя функцию curl_exec(), мы осуществляем непосредственно запрос при помощи CURL, а результат сохраняем в переменную $content, по умолчанию после успешной отработки результат отобразиться на экране, а в $content упадет true. Отследить попутную информацию при запросе нам поможет функция curl_getinfo(). Также важно, если произойдет ошибка - результат общения будет false, поэтому, ниже по коду мы используем строгое равенство с учетом типов. Осталось рассмотреть ещё две функции это curl_error() – вернёт сообщение об ошибке, и curl_errno() – код ошибки. Результатом работы метода getPage() будет массив, а чтобы его увидеть давайте им воспользуемся, а для теста сделаем запрос на сервис httpbin для получения своего IP.
Кстати очень удобный сервис, позволяющий отладить обращения к серверу. Так как, например, для того что бы узнать свой IP или заголовки отправляемые через CURL, нам бы пришлось бы писать костыль.$html = Parser::getPage([ "url" => "http://httpbin.org/ip" ]);
Если вывести на экран, то у вас должна быть похожая картина:
Если произойдет ошибка, то результат будет выглядеть так:
При успешном запросе мы получаем заполненную ячейку массива data с контентом и информацией о запросе, при ошибке заполняется ячейка error. Из первого скриншота вы могли заметить первую неприятность, о которой я выше писал контент сохранился не в переменную, а отрисовался на странице. Чтобы решить это, нам нужно добавить ещё один параметр сеанса CURLOPT_RETURNTRANSFER.
Curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
Обращаясь к страницам, мы можем обнаружить, что они осуществляют редирект на другие, чтобы получить конечный результат добавляем:
Curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
Теперь можно увидеть более приятную картину:
Curl_setopt($ch, CURLOPT_USERAGENT, $useragent); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connecttimeout);
Для того, чтобы получить заголовки ответа, нам потребуется добавить следующий код:
If($head){ curl_setopt($ch, CURLOPT_HEADER, true); curl_setopt($ch, CURLOPT_NOBODY, true); }
Мы отключили вывод тела документа и включили вывод шапки в результате:
If(strpos($url, "https") !== false){ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); }
Уже получается весьма неплохой скрипт парсера контента, мы добрались до кук и тут стоит отметить - частая проблема, когда они не сохраняются. Одной из основных причин может быть указание относительного пути, поэтому нам стоит это учесть и написать следующие строки:
If($cookie_file){ curl_setopt($ch, CURLOPT_COOKIEJAR, __DIR__."/".$cookie_file); curl_setopt($ch, CURLOPT_COOKIEFILE, __DIR__."/".$cookie_file); if($cookie_session){ curl_setopt($ch, CURLOPT_COOKIESESSION, true); } }
Предлагаю проверить, а для этого я попробую вытянуть куки со своего сайта:
If($proxy_ip && $proxy_port && $proxy_type){ curl_setopt($ch, CURLOPT_PROXY, $proxy_ip.":".$proxy_port); curl_setopt($ch, CURLOPT_PROXYTYPE, $proxy_type); } if($headers){ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); } if($post){ curl_setopt($ch, CURLOPT_POSTFIELDS, $post); }
Это малая доля параметров, с которыми можно работать, все остальные находятся в официальной документации PHP . Вот мы завершили с нашей оберткой, и пришло время, что-нибудь спарсить!
Парсим категории и товары с сайта
Теперь, при помощи нашего класса Parser, мы можем сделать запрос и получить страницу с контентом. Давайте и поступим:
$html = Parser::getPage([ "url" => "https://www.svyaznoy.ru/catalog" ]);
Следующим шагом разбираем пришедший ответ и сохраняем название и ссылку категории в результирующий массив:
If(!empty($html["data"])){ $content = $html["data"]["content"]; phpQuery::newDocument($content); $categories = pq(".b-category-menu")->find(".b-category-menu__link"); $tmp = ; foreach($categories as $key => $category){ $category = pq($category); $tmp[$key] = [ "text" => trim($category->text()), "url" => trim($category->attr("href")) ]; $submenu = $category->next(".b-category-submenu")->find(".b-category-submenu__link"); foreach($submenu as $submen){ $submen = pq($submen); $tmp[$key]["submenu"] = [ "text" => trim($submen->text()), "url" => trim($submen->attr("href")) ]; } } phpQuery::unloadDocuments(); }
Чуть более подробно работу с phpQuery я разобрал в первой статье по парсингу контента. Если вкратце, то мы пробегаемся по DOM дереву и вытягиваем нужные нам данные, их я решил протримить, чтобы убрать лишние пробелы. А теперь выведем категории на экран:
-
" target="_blank">
-
if(!empty($value["submenu"])): ?>
- " target="_blank"> endif; ?>
$html = Parser::getPage([ "url" => "https://www.svyaznoy.ru/catalog/phone/224", "timeout" => 10 ]);
Получаем страницу, тут я увеличил время соединения, так как 5 секунд не хватило, и разбираем её, парся необходимый контент:
If(!empty($html["data"])){ $content = $html["data"]["content"]; phpQuery::newDocument($content); $products = pq(".b-listing__generated-container")->find(".b-product-block .b-product-block__content"); $tmp = ; foreach($products as $key => $product){ $product = pq($product); $tmp = [ "name" => trim($product->find(".b-product-block__name")->text()), "image" => trim($product->find(".b-product-block__image img")->attr("data-original")), "price" => trim($product->find(".b-product-block__misc .b-product-block__visible-price")->text()), "url" => trim($product->find(".b-product-block__info .b-product-block__main-link")->attr("href")) ]; $chars = $product->find(".b-product-block__info .b-product-block__tech-chars li"); foreach($chars as $char){ $tmp[$key]["chars"] = pq($char)->text(); } } phpQuery::unloadDocuments(); }
Теперь проверим, что у нас получилось, и выведем на экран:
-
if(!empty($value["chars"])): ?>
- endif; ?>
Вот мы и написали парсер контента PHP, как видите, нет нечего сложного, при помощи этого скрипта можно легко спарсить страницы любого сайта, но перед тем, как заканчивать статью, хотелось пояснить некоторые моменты. Во-первых, если вы хотите парсить более одной страницы, то не стоит забывать, что сам процесс парсинга ресурса затратная операция, поэтому в идеале лучше, чтобы скрипт был вынесен на отдельный сервер, где и будет запускаться по крону. Ещё один момент - к каждому донору стоит подходить индивидуально, так как, во-первых: у них разный HTML код и он, с течением времени, может меняться, во-вторых: могут быть различные защиты от парсинга и проверки, поэтому для подбора необходимого набора заголовков и параметров может потребоваться отладочный прокси (я пользуюсь Fiddler). И последние, что я добавлю - используйте для парсинга прокси и чем больше, тем лучше, так как, когда на сервер донора полетят тысячи запросов, то неизбежно IP, с которого осуществляется обращение будет забанен, поэтому стоит прогонять свои запросы через прокси-сервера.
Полный пример с библеотекай phpQuery вы найдете на github .
Отличная статья. Спасибо. Как раз сейчас разбираю пхп и тему парсеров.
Рад, что статья вам понравилась. В одной из следующих расскажу об уже готовых решениях для парсинга сайтов.
Потихоньку изучаю возможности PHP для создания парсеров. Я уже писала о том, как парсить . Сейчас расскажу об одном из способов парсинга html (он подойдет и для xml тоже, кстати). Повторю, что в php я не гуру, поэтому буду очень признательна, если вы оставите свои комментарии к поднятой теме.
Побродив по нашим и англоязычным форумам, поняла, что спор о том, лучше ли парсить html регулярными выражениями или использовать для этих целей возможности PHP DOM , является холиваром. Сама же я пришла к выводу, что все зависит от сложности структуры данных. Ведь если структура достаточно сложная, то с помощью регулярок приходится парсить в несколько этапов: сначала выделить большой кусок, потом разделить его на более маленькие и т.д.. В итоге, если данные сложные (или их очень много), то процесс парсинга может значительно затянуться. Ресурсоемкость в этом случае еще будет зависеть, конечно же, от самих регулярных выражений. Если в регэкспах много ".*" (они являются самыми ресурсоемкими, т.к. "прочесывают" исходный код с максимальной жадностью), то замедление будет заметным.
И вот как раз в этом-то случае как нельзя кстати приходится PHP DOM. Это удобный инструмент для парсинга как XML, так и HTML. Некоторые придерживаются мнения, что парсить html регэкспами вообще нельзя, и яростно защищают PHP DOM.
В свою очередь я ознакомилась с этим расширением, написав простенький скрипт. Который и привожу здесь, чтобы наглядно показать, как это все легко и просто. В примере разбирается html с частью карты сайта этого блога. Он присвоен переменной прямо внутри кода. В "боевых" же условиях исходные данные следует получать, например, через file_get_contents().
$html = "
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
Последние темы блога
http://сайт/2009/08/blog-post_06.html | Базы |
MySQL и Delphi. Express-метод | |
http://сайт/2009/08/blog-post.html | Пост о том, что лучше сто раз проверить |