PHP парсер HTML

Парсер html контента сайта на PHP

Решил привести статью в актуальный вид. Ранее на данной странице был представлен универсальный парсер HTML страниц на PHP. Но прошло уже более 4 лет, я наработал больше опыта в области разработки парсеров. И решил выложить новый пример PHP парсера с детальным разбором алгоритма работы.

Парсер прошлой версии носил гордое звание универсального, но это было весьма условное обозначение. Скрипт имел много ограничений, для его полноценного использования требовались знания в регулярных выражениях на PHP и JS.

Я подумал, подумал и решил, что более универсальным решением будет показать пример парсера на PHP и рассказать, как он работает. Так программисты, которые ранее не писали парсеров смогут решить свои задачи. А заказчики смогут понять возможности PHP в области парсинга сайтов и что реально можно требовать от программистов.

Парсер — это программа, которая анализирует входные текстовые данные, извлекает нужную информацию и на основе полученных данных выдаёт результат в заданном формате.

Общий алгоритм PHP парсинга предполагает, что ваш скрипт делает запрос по заданному адресу, получает ответ от сервера в виде HTML страницы, либо в каком-то другом текстовом формате, например CSV, JSON, XML. Далее полученная информация анализируется, из неё извлекаются (парсятся) нужные данные, на основе которых формируется результат. Полученные данные можно вывести на экран, либо записать в файл или БД.

Пример простого PHP парсера html контента

Предположим нам нужно спарсить цену на товары на сайте gearbest.com. Скрипт считывает заданную страницу, потом посредством регулярных выражений анализирует её контент и выделяет нужные нам куски HTML кода. Далее полученный результат выводится на экран.

<?php
Error_Reporting(E_ALL & ~E_NOTICE);
mb_internal_encoding("UTF-8");
set_time_limit(0);    // Попытка установить своё время выполнения скрипта

/* --- 1 --- Инициализируем переменные для запроса */
    $time_start = time();
    $error = array();
    $error_page = array();
    $action = 0;
    $gearbest_url = "";
    $charset = "UTF-8";    // Исходная кодировка страницы
    $uni_name = date("d-m-Y-H-i-s", time());
    
/* --- 1.1 --- Переопределяем переменные на основе GET или POST параметров */
    if(isset($_REQUEST['gearbest_url']))
        $gearbest_url = trim($_REQUEST['gearbest_url']);
    if(isset($_REQUEST['action']))
        $action = $_REQUEST['action'];

/* --- 1.2 --- Загрузка страницы при помощи cURL */
function curl_get_contents($page_url, $base_url, $pause_time, $retry) {
    /*
    $page_url - адрес страницы-источника
    $base_url - адрес страницы для поля REFERER
    $pause_time - пауза между попытками парсинга
    $retry - 0 - не повторять запрос, 1 - повторить запрос при неудаче
    */
    $error_page = array();
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0");   
    //curl_setopt($ch, CURLOPT_COOKIEJAR, str_replace("\\", "/", getcwd()).'/gearbest.txt'); 
    //curl_setopt($ch, CURLOPT_COOKIEFILE, str_replace("\\", "/", getcwd()).'/gearbest.txt'); 
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // Автоматом идём по редиректам
    curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0); // Не проверять SSL сертификат
    curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0); // Не проверять Host SSL сертификата
    curl_setopt($ch, CURLOPT_URL, $page_url); // Куда отправляем
    curl_setopt($ch, CURLOPT_REFERER, $base_url); // Откуда пришли
    curl_setopt($ch, CURLOPT_HEADER, 0); 
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // Возвращаем, но не выводим на экран результат
    $response['html'] = curl_exec($ch);
    $info = curl_getinfo($ch);
    if($info['http_code'] != 200 && $info['http_code'] != 404) {
        $error_page[] = array(1, $page_url, $info['http_code']);
        if($retry) {
            sleep($pause_time);
            $response['html'] = curl_exec($ch);
            $info = curl_getinfo($ch);
            if($info['http_code'] != 200 && $info['http_code'] != 404)
                $error_page[] = array(2, $page_url, $info['http_code']);
        }
    }
    $response['code'] = $info['http_code'];
    $response['errors'] = $error_page;
    curl_close($ch);
    return $response;
}
/* --- 1.3 --- Парсинг цены */
function get_gearbest_price($gearbest_url) {
    /*
    $gearbest_url - адрес страницы товара на Gearbest
    */
    $res_arr = array();
    $res_arr['price_list'] = array();
    $base_url = "https://www.gearbest.com";
    $response_arr = curl_get_contents($gearbest_url, $base_url, 5, 1);
    $page = $response_arr['html'];
    $page_code = $response_arr['code'];
    $res_arr['error_page'] = $response_arr['errors'];
//file_put_contents('page.txt', $page);
//$page = file_get_contents('page.txt');
    if(!empty($page) && $page_code == 200) {
        if($charset != "UTF-8") {
            $page = iconv("WINDOWS-1251", "UTF-8//IGNORE", $page);
        }
        $buffer = array();
        $regexp = "/(id=\"unit_price\"\s*data-orgp=\"(.*)\")|(class=\"my_shop_price new_shop_price\"\s*data-orgp=\"(.*?)\"\s*id=\"unit_price\")/Us";
        preg_match($regexp, $page, $buffer);
        $res_arr['price_list']['price'] = $buffer[2] ? $buffer[2] : $buffer[4];
        $regexp = "/bizhong=\"(.*)\">/Us";
        $buffer = array();
        preg_match($regexp, $page, $buffer);
        $res_arr['price_list']['currency'] = $buffer[1];
        $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 '<p>Цена: ' . $price_list['price'] . ' <span class=\"currency\">' . $price_list['currency'] . '</span></p>';
}
/* --- 1.4.2 --- Вывод ошибок */
function error_list_html($error) {
    if (!empty($error)) {
        echo "<p>Во время обработки запроса произошли следующие ошибки:</p>\n";
        echo "<ul>\n";
        foreach($error as $error_row) {
            echo "<li>" . $error_row . "</li>\n";
        }
        echo "</ul>\n";
        echo "<p>Статус: <span class=\"red\">FAIL</span></p>\n";
    } else {
        echo "<p>Статус: <span class=\"green\">OK</span></p>\n";
    }
}
/* --- 1.4.3 --- Вывод ошибок загрузки страниц */
function error_page_list_html($error_page) {
    if (!empty($error_page)) {
        echo "<ul>\n";
        foreach($error_page as $error_row) {
            echo "<li>[" . $error_row[0] . "] " . $error_row[1] . " - " . $error_row[2] . "</li>\n";
        }
        echo "</ul>\n";
    }
}
/* --- 1.4.4 --- Вывод работы скрипта */
function run_time_html($time_start) {
    if(!empty($time_start))
        echo "<!--p>Время работы: " . (time() - $time_start) . "</p-->\n";
}
/* --- 2 --- Получение контента из каталога Gearbest */
    if($action) { // если ошибок нет и данные формы поиска получены
        if(!empty($gearbest_url)) {
            $gearbest_url = trim($gearbest_url);
            $din_url =  $gearbest_url;
            $res_arr = get_gearbest_price($din_url);
            $price_list = $res_arr['price_list'];
            $error_page = $res_arr['error_page'];
            $error = $res_arr['error'];
        } else {
            $error[] = "Не задан адрес страницы с товаром";
        }
    }
/* --- 3 --- Вывод результатов работы парсера */
?>
<!doctype html>
<html>
<head>
    <title>Парсер цены товара на Gearbest.com</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <!--meta name="robots" content="noindex,nofollow"-->
</head>
<body>
<style>
.wrapper {
    max-width: 600px;
    margin: 0 auto;
}
h1 {
    text-align: center;
}
.action_form {
    max-width: 560px;
    margin: 0 auto;
}
.action_form input {
    width: 100%;
}
input[type="text"] {
    font-size: 1em;
    min-height: 36px;
    box-sizing: border-box;
}
input[type="submit"] {
    padding: 8px 12px;
    margin: 12px auto;
    font-size: 1.2em;
    font-weight: 400;
    line-height: 1.2em;
    text-decoration: none;
    display: inline-block;
    cursor: pointer;
    border: 2px solid #007700;
    border-radius: 2px;
    background-color: transparent;
    color: #007700;
}
input[type="submit"]:hover {
    background-color: #009900;
    color: #fff;
}
.result {
    border: 1px dotted #000;
    width: 100%;
    height: auto;
    overflow-y: auto;
    margin: 0px auto;
    padding: 10px;
}
.copyright {
    text-align: center;
}
.copyright a {
    color: #000;
}
.copyright a:hover {
    text-decoration: none;
}
.red {
    color: #770000;
}
.green {
    color: #007700;
}
</style>
<div class="wrapper">
    <h1>Парсер цены товара на Gearbest.com</h1>
    <form class="action_form" action="" method="post">
        <input type="hidden" name="action" value="1" />
        <input type="text" name="gearbest_url" value ="<?php if(!empty($gearbest_url)) echo $gearbest_url; ?>" placeholder="URL страницы товара" />
        <input type="submit" name="submit" value="Старт" />
    </form>
    <div class="result">
<?php
        if($action && !empty($price_list)) {
            price_list_html($price_list);
        }
?>
    </div>
    <div class="errors_block">
<?php
    error_page_list_html($error_page);
    error_list_html($error);
    run_time_html($time_start);
?>
    </div>
    <div class="copyright">&copy Идея и реализация - <a href="https://seorubl.ru/" target="_blank" title="Записки Предприимчивого Человека" rel="generator">ПЧ</a> // 16.04.2017 г.</div>
</div>
</body>
</html>

index.php — основной файл PHP скрипта парсера. Код парсера актуален на момент публикации. Со временем HTML код сайта источника может меняться и регулярные выражения уже не будут к нему подходить.

Существуют разные способы установки скрипта. Я работал с ним из-под XAMPP. Но можно парсер запускать прямо с виртуального хостинга. Просто заливаете файл index.php к себе на сайт в какую-либо папку и обращаетесь к нему через адресную строку браузера. Предположим, что вы закинули скрипт в папку my-parser в корневой директории вашего хостинга. Тогда в адресной строке нужно набрать URL: http://вашдомен.ru/my-parser/.

Скриншот главной страницы парсера цены с сайта gearbest.com:

PHP парсер цены товара gearbest

1. На главной странице парсера мы должны ввести адрес страницы товара. После нажатия на кнопку «Старт» страница перезагружается, отправляются данные формы на сервер и PHP скрипт делает запрос по заданному адресу с помощью библиотеки cURL.

За это действие отвечает функция curl_get_contents(), которая является аналогом стандартной PHP функции file_get_contents(), но с расширенным на основе cURL функционалом.
cURL — это расширение для PHP, которое обеспечивает поддержку библиотеки функций libcurl. Данный набор функций позволяет формировать POST и PUT запросы, скачивать файлы. Поддерживаются различные протоколы http, https, ftp и пр. Можно использовать прокси-серверы, cookies и аутентификацию пользователей. В общем, отличный инструмент для имитации действий пользователя в браузере.

cURL штука для разработки HTML парсеров очень полезная, и в одной из следующих статей я более подробно расскажу о приёмах работы с ней для целей парсинга.

2. Далее в функции get_gearbest_price() с помощью встроенной в PHP функции preg_match() и регулярных выражений проводится анализ ответной HTML страницы, полученной от стороннего сервера.

HTML код страницы товара gearbestОбратите внимание, что скрипт видит страницу в текстовом формате и анализировать предстоит именно её 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 отдаёте вашим знакомым.

В следующем цикле статей я покажу примеры реализации более сложных парсеров с авторизацией, сбор фото, работа с AJAX, парсинг ВК и т.п.

В общем, дорогие мои читатели, чем смог тем помог, читайте, учитесь и не забывайте ставить ссылки на блог Предприимчивого Человека.

13 thoughts on “Парсер html контента сайта на PHP”
  1. Здравствуйте
    Хотелось бы обговорить настройку парсера вашими силами. Как будете свободны напишите мне.

  2. Здравствуйте, вы ещё занимаетесь парсером? Хотел бы прибегнуть к вашим услугам.

  3. Добрый день. Возможно ли написание парсера под конкретные задачи? Можем ли мы списаться по почте? Спасибо!

  4. Минус, парсер работает без проксей, многие сайты блокируют на частые запросы.

    1. Пока не сталкивался в своей практике с необходимостью работать через прокси. Хотя, действительно, такие задачи встречаются. Конкретно этот парсер был разработан, без особого прикладного смысла, чисто показать общий алгоритм действий. В ближайшее время планирую выложить наработки по парсингу различных сайтов, в том числе с помощью cURL, с авторизацией и с распознаванием капчи.

  5. Доброго времени суток! Как я понял, вы можете выполнить задание любой сложности… не могли бы Вы со мной связаться по e-mail, хотелось бы детально обговорить кое-какие моменты по парсингу информации с одного сайта, а так же узнать цену за работу.

    1. Напишите мне на почту, на странице «Контакты». Цены и сроки работ смогу оценить после ознакомления с ТЗ.

  6. А можно ли у Вас заказать парсер вк + что бы он сразу импортировал контент в базу данных?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *