PHP Simple HTML DOM Parser пример использования

Ноя. 17, 2010| 22:49

Наверное, любой php-разработчик рано или поздно сталкивается с задачей написания html парсера. Задачи разнообразные : вытащить телефоны и адреса поставщиков с сайта желтых страниц, расписание кинотеатра на день или даже просто распарсить большой справочник (двухколоночную таблицу), который почему-то прислали в html, а не в excel-файле. Способов море : с использованием регулярных выражений, строковых функций (strpos, strstr и т.д.), с помощью DOM и так далее. Один из моих парсеров загружал страницу, обрезал начало и конец, конвертил из cp1251 в utf8, присоединял в начало и конец xml узлы и дальше все парсилось с помощью SimpleXML и XPath.

А потом в один рабочий день я нашел PHP Simple HTML DOM Parser универсальный (пока не было задач, с которыми он бы не справился), удобный в использовании, хороший парсер. На сайте есть хорошая и понятная документация, а я покажу пример всего из 60-70 строк кода, который вытащит нам 1000 последних добавленных машин в Астане с сайта kolesa.kz (1000 потому что сайт выдает 1000 машин максимум на запрос).

Итак, в БД вам потребуется три таблички для хранения данных:

CREATE TABLE IF NOT EXISTS `kolesa_brands` (
  `id` tinyint(3) NOT NULL,
  `name` varchar(100) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

Таблица для хранения марок автомобилей : уникальный идентификатор и наименование.  Марки мы возьмем из выпадающего списка "Марка" с поисковой панели Колес

CREATE TABLE IF NOT EXISTS `kolesa_models` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `brand_id` tinyint(3) NOT NULL,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `brand_id` (`brand_id`,`name`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

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

CREATE TABLE IF NOT EXISTS `kolesa_cars` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `model_id` int(11) NOT NULL,
  `price` int(11) NOT NULL,
  `year` year(4) NOT NULL,
  `region` tinyint(2) NOT NULL,
  `link` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

Таблица для хранения автомобилей : идентификатор, идентификатор модели, цена, год выпуска, код региона, ссылка.

Собственно сам исходный код :

<?php
require ('simple_html_dom.php');

$dbn mysql_connect('localhost''user''password');
mysql_select_db('parser'$dbn);
mysql_query('SET NAMES utf8');

$brands = array();

$html str_get_html(file_get_contents('http://kolesa.kz'));
$insert 'INSERT INTO kolesa_brands (id, name) VALUES ';
$select $html->find('select[id=auto.car.mm_0]'0);
foreach(
$select->find('option') as $opt)
{
    if(
$opt->value == '') continue;
    
$insert .= '('.$opt->value.', \''.$opt->plaintext.'\'),';
    
$brands[$opt->value] = $opt->plaintext
}

mysql_query(rtrim($insert','));


for(
$i=1$i 1000$i++)
{
    
$link 'http://kolesa.kz/a/search?cat[0]=auto.car&sort_by[add_date]=desc&sort_by[_sys.up]=desc&limit=20&page='.$i.'&das[region]=Астана&das[auto.car.grbody]=&das[auto.car.mm][0]=&das[auto.car.mm][1]=&das[year][l]=&das[year][h]=&das[price][l]=&das[price][h]=&das[_sys.hasphoto]=&das[_sys.torg]=&das[auto.car.transm]=&das[auto.volume][l]=&das[auto.volume][h]=&das[auto.fuel]=&das[auto.run][h]=&das[auto.color]=&das[auto.color_m]=&_txt_=';

    
$page file_get_contents($link);
    if(
strpos($page'Увы, нет таких объявлений') !== false)
    {
        break;
    }
    
$html str_get_html($page);

    
$div $html->find('div.lcol'0);


    foreach(
$div->find('div[class^=good]') as $ob)
    {
        if(!
is_object($ob)) continue;
        
        
$name $ob->find('a.mm'0)->plaintext;    
    
        foreach(
$brands as $k => $v)
        {
            if(
strpos($name$v) !== false)
            {
                
$name trim(str_replace(array($v' '), ''$name));
                
mysql_query('INSERT IGNORE INTO kolesa_models (brand_id, name) VALUES ('.$k.', \''.$name.'\')');
                break;    
            }
        }
        
$model_id mysql_insert_id();
        if(
$model_id == 0)
        {
            
$res mysql_query('SELECT id FROM kolesa_models WHERE brand_id = '.$k.' AND name = \''.$name.'\'');
            
$model_id =(int) mysql_result($res0);
        }
    
        
$price $ob->find('span.price'0)->plaintext;   
        
$price trim(str_replace(array('$'' '), ''$price)); 
    
        
$year $ob->find('span.year'0)->plaintext;        
        
$year trim(str_replace(array('г.'' '), ''$year));
        
        
$link $ob->find('a.mm'0)->href;
        
        
mysql_query('INSERT INTO kolesa_cars (model_id, price, year, region, link) VALUES ('.$model_id.', '.$price.', '.$year.', 1, \''.$link.'\')');
    }
}

Сильно не ругайте, для подсветки использовал функцию highlight_file()

Пояснения :

  • Подключаем файл с парсером, подключаемся к БД
  • Загружаем страницу kolesa.kz, отдаем строку парсеру. Главная функция парсера find(), она всегда возвращает массив. Поэтому при поиске выпадающего списка марок вторым аргументом указываем 0, чтобы получить первый найденный элемент. Конструкция "select[id=auto.car.mm_0]" показывает, что искать надо select, у которого есть атрибут id со значением auto.car.mm_0.  Потом в этой селекте находит все элементы option и в цикле, пропуская пустое значение "Любая" (марка) заполняем массив brands и записываем в таблицу марок. Обратите внимание, что можно обратиться к атрибуту $opt->value.
  • Далее в цикле дергаем странички, находим там div класса lcol, в котором содержится список объявлений. Каждое объявление хранится в div класса good. Но есть еще выделенные объявления, они выводятся в div классах типа good mark_2. В любом случае конструкция div[class^=good] вернет все div с атрибутом класс, название класса начинается на "good" .
  • Вытаскиваем наименование марки и модели, цену, год. Производим чистку полученных значений, чтобы положить в БД. Разделяем марку и модель. INSERT IGNORE INTO kolesa_models   ложит в БД модели автомобилей, IGNORE нужен, чтобы не дублировались модели. Специально для этого делали уникальный ключ в таблице, если бы не было IGNORE то БД бы падала от ошибки, а так всего лишь предупреждение и продолжает работать.

Скачать исходный код

Ну в общем и все. У парсера еще есть куча классных фич, таких как: навешивание callback функций, перемещение по DOM-дереву, добавление узлов и т.д.


Метки: php
Имя :
Сайт или эл. почта :
Сообщение :
Код подтверждения :
 
  • , Ноя. 18, 2010| 12:35
    Я, как всегда, по протоколу, потому что в PHP не того. :)

    У меня код вылезает за границу экрана. Реквестирую скроллбары или переносы.

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

    Ceterum censeo, что надо запоминать имя комментатора и рассылать уведомления об ответе.
    reply to this
    • zloi, Ноя. 18, 2010| 20:50
      это надо заморачиваться, чтобы нормально исходный код показывать. пока времени нету

      рсску сделал, чтобы отдавала целиком статьи. проверяй

      имя комментатора и уведомления - надо время. его пока не очень много )
      reply to this
    • zloi, Ноя. 20, 2010| 00:14
      прикрутил запоминание имени комментатора и рассылку уведомлении об ответе )

      тебе скорей всего ответ в спам придет на гмыле
      reply to this
  • , Дек. 16, 2010| 18:44
    А на чем она вообще колеса .kz написана?
    /a/search?cat=auto.car&das;[auto.car.grbody]=&das;[auto.car.mm][0]=&das;[auto.car.mm][1]=&das;[region]=&das;[price][l]=&das;[price][h]=&das;[year][l]=&das;[year][h]=&das;[_sys.torg]=&das;[auto.fuel]=&das;[auto.volume][l]=&das;[auto.volume][h]=&das;[auto.run][h]=&das;[auto.car.transm]=&das;[auto.color]=&das;[auto.color_m]=&das;[car.dwheel]=&_txt_=&das;[_sys.hasphoto]=

    Напиши плиз на почту? я чет такой код не встречала?
    reply to this