PHP парсер html phpquery

PHP парсер без регулярных выражений

Основой парсеров HTML сайтов являются две функции: подготовка запросов к серверу и разбор ответа с помощью регулярных выражений. Но что делать, например, в случаях, когда нужно по-быстрому что-то спарсить, а разбираться с премудростями работы с регулярными выражениями лень? В таких случаях нам помогут специальные PHP библиотеки для работы на сервере с HTML структурами данных, также как на jQuery.

Для этих целей больше 10-ка разных библиотек написано PHP HTML DOM, phpQuery, QueryPath, XPath. Ещё есть варианты, встроенные в различные PHP фреймворки.

Я для данной статьи выбрал phpQuery парсер для использования jQuery подобных селекторов. В применении данной библиотеки нет ничего сложного, если вы имеете опыт работы с javascript библиотекой jQuery или её аналогами.

Парсер цены товара на основе phpQuery

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

Сразу покажу код файла index.php:

<?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 */
/* --- 1.2.1 --- Загрузка страницы при помощи 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 --- Функции для Gearbest.com */
/* --- 1.3.1 --- Парсинг цены товара */
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);
		}
		require_once('/phpQuery/phpQuery.php');
		$doc = phpQuery::newDocumentHTML($page);
		phpQuery::selectDocument($doc);
		$res_arr['price_list']['price'] = pq('#unit_price')->attr('data-orgp');
		$res_arr['price_list']['currency'] = pq('.switcher-currency-c span a')->attr('data-bizhong');
		$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($errors) {
	if (!empty($errors)) {
		echo "<p>Во время работы парсера произошли следующие ошибки:</p>\n";
		echo "<ul>\n";
		foreach($errors 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 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);
			$res_arr =  get_gearbest_price($gearbest_url);
		} else {
			$res_arr['errors'][] = "Не задан адрес страницы товара";
		}
	}
/* --- 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"],
input[type="button"] {
	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,
input[type="button"]: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($res_arr['price_list'])) {
			price_list_html($res_arr['price_list']);
		}
?>
	</div>
	<div class="errors_block">
<?php
	error_list_html($res_arr['errors']);
	run_time_html($time_start);
?>
	</div>
	<div class="copyright">&copy Идея и реализация - <a href="https://seorubl.ru/" target="_blank" title="Записки Предприимчивого Человека" rel="generator">ПЧ</a> // 01.05.2017 г.</div>
</div>
</body>
</html>

Основные изменения были сделаны в функции get_gearbest_price(), из неё пропали регулярные выражения. Вместо них подключается библиотека phpQuery и парсятся нужные данные из атрибутов. Элементы HTML выбираются по значению классов и id.

require_once('/phpQuery/phpQuery.php');
$doc = phpQuery::newDocumentHTML($page);
phpQuery::selectDocument($doc);
$res_arr['price_list']['price'] = pq('#unit_price')->attr('data-orgp');
$res_arr['price_list']['currency'] = pq('.switcher-currency-c span a')->attr('data-bizhong');

Очень похоже на принципы работы с DOM объектами в jQuery.

В итоге получился вполне рабочий парсер цен без использования регулярных выражений.

Парсер цены без регулярных выражений

Конечно, внутри библиотеки phpQuery активно применяются регулярные выражения для разбора DOM структуры документа, но от нас эта магия скрыта.

В парсере из примера я использовал собственную функцию curl_get_contents() для запросов к серверу и получения от него ответов. В phpQuery и её аналогах есть готовые функции для скачивания HTML страниц. Но они не всегда применимы для целей парсинга сайтов, так как используют стандартную функцию PHP get_file_contents().

Из плюсов применения для разработки парсеров на основе phpQuery можно выделить простоту работы и широкий функционал выборки по селекторам и атрибутам. Поддержка разных структур данных: XML, HTML, xHTML.

Минусы характерные:

  • использование в проекте дополнительной сторонней библиотеки (например phpQuery остановилась в развитии в 2009 году),
  • довольно тяжеловесный скрипт (около 1 мб),
  • для целей парсинга используется малая часть доступного функционала.

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

Но как вариант, при необходимости быстро сделать парсер со сложной обработкой DOM или XML структуры данных, вполне оправданно применить какую-нибудь PHP jQuery подобную библиотеку. Надо только обязательно протестировать скорость работы полученного скрипта и стабильность при некорректных входных данных.

2 thoughts on “PHP парсер без регулярных выражений”
  1. Подскажите как спарсить php файл из другого сервера, то есть как вывести переменные из файла.

    1. Никак, это противоречит самому механизму работы веб-сервера и PHP. Если только взломать сам сервер и выкачать нужные файлы, но это совсем другая история…

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

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