Многие считают, что парсер – нечто сложное и не понятное. Так ли это?
Я не программист, ни по образованию, ни по роду занятий. Но люблю разбираться в разных вопросах. Поэтому решил разобраться и с парсерами. Тем более, что занялся продажей автомобильных масел и фильтров. Рыться в каталогах – долго и муторно. Охота так, чтобы отжал продавец кнопку, а ему показало, что нужно подать покупателю. Поэтому задача запарсить сайт производителя фильтров, записать все в БД, чтобы было доступно локально.
Все, что будет написано ниже, применимо не только к автомобильным фильтрам, а и к компьютерным комплектующим и прочим товарам, новостям, статьям, блогам.
Процесс установки и настройки WAMPили LAMPописывать нет смысла.
И так, приступим. Для начала надо изучить объект. Открываем страницу каталога. Открываем ее исходник. Любой такой каталог начинается с выбора производителя авто. Исходник содержит производителей и их Id в списке <select>. Это и будет нашей первой таблицей в БД. Создаем ее в PHPMyAdmin, обозвав _car_brand.
Первый столбец brand_id (Primary), второй brand_name. Я эту таблицу заполнил, написав разовую программку, которую тут же стер. Она использовала simple_html_dom и брала данные со страницы каталога. Можно заполнить вручную. Цель этой статьи не в том. Мы решаем дальнейшую проблему, уткнувшись в которую, многие не знают, как поступить, и забрасывают свой парсер.
Все дело в том, что страница меняется на экране динамически путем ajax запросов, при этом ее исходный текст остается неизменным. Т.е. куда то летит запрос и возвращается JSON. Потом на клиенте свежие данные обвешиваются HTML тегами. Вот эти запросы мы и будем формировать.
Создаем файлик poisk.php и открываем его в Notepad++. И начинаем кодить:
<?php
// считываем ИД производителя авто
$brandId=$_REQUEST["selBrand"];
// Если еще не выбирали производителя (1й этап)
if ($brandId == 0){
echo 'ВЫБЕРИТЕ ПРОИЗВОДИТЕЛЯ<form method="POST" action="poisk.php"> <select name="selBrand">';
$database = 'db_6';
$link = mysql_pconnect("mysql.ru", "dbu1", "polk");
mysql_select_db($database) or die("Немогуподключитьсякбазе.");
mysql_query("set character_set_server='utf8'");
mysql_query("set names 'utf8'");
// Берем полный список производителей и выводим
$car_brand = mysql_query("SELECT * FROM _car_brand");
// Цикл по строкам. Кому что не ясно, читаем про работу с БД
while ($brand_id = mysql_fetch_array($car_brand)){
$a=$brand_id[0];
$b=$brand_id[1];
echo '<option value="'.$a.'">'.$b.'</option>';
}
echo '</select> <input type="submit" id="btnAppSearch" name="btnAppSearch" value="Go"/></form>';
}
Коротко говоря, считали из базы и вывели полный список производителей…
Дальше интересней. Нам нужна вторая таблица в БД `_car_model с моделями авто. В ней поля (`car_model_id`, `car_model_brand_id`, `car_model_name`). Эту таблицу мы будем заполнять не по циклу, а в процессе работы с программой.
// Если производитель уже выбран
else {
// Считываем прилетел ли ИД модели
$selModel=$_REQUEST["selModel"];
if ($selModel == 0){
// Производитель выбран, но не выбрана модель (2й этап)
$database = 'db_6';
$link = mysql_pconnect("mysql.ru", "dbu1", "polk");
mysql_select_db($database) or die("Немогуподключитьсякбазе.");
mysql_query("set character_set_server='utf8'");
mysql_query("setnames 'utf8'");
// Смотрим имя производителя и его ИД
$car_brand = mysql_query("SELECT * FROM `_car_brand` WHERE `car_brand_id` = '$brandId'");
$brand_id = mysql_fetch_array($car_brand);
$a=$brand_id[0];
$b=$brand_id[1];
// Выводим имя производителя без возможности его сменить, только через обновление страницы
echo '<form method="POST" action="poisk.php">ПРОИЗВОДИТЕЛЬ : <select name="selBrand"><option value="'.$a.'">'.$b.'</option></select>
<a href="poisk.php">СМЕНИТЬ</a>
<br>ВЫБЕРИТЕМОДЕЛЬ : ';
echo '<select name="selModel">';
// Лезем в локальную БД, смотрим, есть ли в ней модели этого производителя
$car_model = mysql_query("SELECT * FROM `_car_model` WHERE `car_model_brand_id` = '$brandId'");
$i=0;
// Если в локальной БД есть модели, то выводим их, счетчик их считает...
while ($model_id = mysql_fetch_array($car_model)){
$c=$model_id[0];
$d=$model_id[2];
echo '<option value="'.$c.'">'.$d.'</option>';
$i++;
}
Тут вроде пока все просто, но, как мы помним, наша таблица с моделями на сей момент пуста… Поэтому и список будет пуст. Придётся лезть за ним на сайт.
// Если счетчик не посчитал, значит локально их нет...
if ($i<1){
// Лезем тогда на сайт за моделями
Как уже говорил, в исходнике страницы мы ничего не увидим, поэтому запускаем встроенные в браузер средства отладки. Я пользовался оперой. Поэтому правый клик => посмотреть код элемента. Переходим на вкладку Network, где переходим в под-вкладку XHR. Тут то и отображаются «подпольные» сетевые действия, которые нужно внимательно изучить. Выбираем в списке автомобиль (я выбрал TOYOTA) и кликаем «дальше»… В окне браузера появляется список с моделями, а на нашей вкладке мы видим что добавилась строка get_models/. Выбрав ее, видим много интересного. Видим адрес, по которому браузер отправлял запрос. Дальше нас интересует информация, которую браузер отправлял на сайт: Request headers. И данные, которые браузер передал: FormData. Snoopy.class - нам в помощь.
include ("Snoopy.class.php");
$snoopy = new Snoopy;
// Некоторые параметры Снупи знает, их и указываем от реквеста
$snoopy->accept = "application/json, text/javascript, */*; q=0.01";
// Некоторые параметры ему не известны, поэтому указываем как RAW
$snoopy->rawheaders["Accept-Encoding"] = "gzip, deflate";
$snoopy->rawheaders["Accept-Language"] = "ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4";
$snoopy->rawheaders["Connection"] = "keep-alive";
$snoopy->rawheaders["Content-Type"] = "application/x-www-form-urlencoded";
$snoopy->cookies["PHPSESSID"] = '7b60548db34612bc5af6';
$snoopy->cookies["ci_session"] = тут очень много всего было…;
$snoopy->host = "www.filter.com";
$snoopy->rawheaders["Origin"] = "http://www.filter.com";
$snoopy->referer = "http://www.filter.com/catalogue";
$snoopy->agent = "Mozilla/5.0 (Windows NT 6.1; WOW64)";
$snoopy->rawheaders["X-Requested-With"] = "XMLHttpRequest";
// Шапка закончилась, поэтому формируем массив запроса, передаваемый Пост
$submit_vars = array();
$submit_vars["brandId"] = $brandId;
// Ну а это адрес, куда весь запрос наш полетит
$submit_url = "http://www.filter.com/application/get_models/";
Еще раз, для понимания: все эти данные здесь для примера. У каждого сайта они будут свои. У меня, почему то, при передаче Content-Length, такого же, как и в запросе браузера, приходил пустой ответ от сервера. Попробовал не передавать его – работает. Значит параметр не важный для запроса. А вот без этого : $snoopy->rawheaders["X-Requested-With"] = "XMLHttpRequest";
Приходил пустой результат.
С ним сайт отвечает - результат получен!!!
Шлем запрос и получаем результат:
if($snoopy->submit($submit_url,$submit_vars))
{
// Если удачно залезли
$var = $snoopy->results;
// Тут к нам как раз и прилетел JSON, декодируем в массив
$vars = json_decode($var);
//Который потом разбираем
foreach($vars as $value){
$c=$value->app_model_id;
$d=$value->app_model_name;
// Выводим список моделей
echo '<option value="'.$c.'">'.$d.'</option>
';
// И пишем его в БД
$sql = mysql_query("INSERT INTO `db_ 6`.`_car_model` (`car_model_id`, `car_model_brand_id`, `car_model_name`) VALUES ('$c', '$a', '$d')");
}
}
}
echo '</select>';
echo '<input type="submit" id="btnAppSearch" name="btnAppSearch" value="Go"/> </form>';
}
Как можно увидеть, таблица с моделями заполняется при первом выборе производителя. При повторном запросе по этому производителю модели будут прилетать из локальной БД. Ну а дальше:
else {
// Оказалось, что выбран и производитель и модель
Проделываем то же самое с get_years, get_eng_vol, дойдя до ягодки get_applications, отправив которой в запросе
$submit_vars = array();
$submit_vars["modelId"] = $selModel;
$submit_vars["year"] = $year;
$submit_vars["eng_vol"] = $eng_vol;
Получим в ответ заветный JSON c {Oil;Air;Fuel;Cabin;Trans}
http://idemitsu.to.net.ru/poisk.php - вот полученный вариант каталога, который можно встроить в сайт или в окно 1с продавца…
Всем удачи и успехов.