Парсинг контента с интернет магазина

7th Апрель 2011 | Категории: parser, PHP | Метки: , ,

Ко мне часто обращаются знакомые с просьбами скачать какую-то информацию с другого сайта. Просьбы бывают абсолютно разными: от автоматизации вывода погоды/курса валют, до скачивания целых баз данных (посты с блогов, товары интернет магазинов и т. д.).
Утром мне пришёл СПАМ с рекламой одного парфюмерного магазина, и я решил отомстить им.

Сегодня мы будем парсить интернет-магазин.

англ. parse
1) синтаксический анализ, синтаксический разбор; грамматический разбор;
2) анализировать, разбирать.

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

Приступим.

Шаг 0. Предисловие.

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

Шаг 1. Изучаем URL.

Открываем http://parfum-collection.ru, смотрим на URL товаров:
http://parfum-collection.ru/product/gardenia/50322.html
http://parfum-collection.ru/product/iris-ukioye/50325.html
http://parfum-collection.ru/product/boadicea-the-victorious/50340.html

Похоже, что на сайте настроено ЧПУ (Человеко-Понятные URL). Если уберем часть URL, получим ссылки, эквивалентные приведенным выше:
http://parfum-collection.ru/product/50322.html
http://parfum-collection.ru/product/50325.html
http://parfum-collection.ru/product/50340.html

Последний сегмент - уникальный id товара.

Чтобы получить информацию о всех товарах на сайте нужно:
1. Зайти на главную страницу, считать все категории товаров.
1.1 Для каждой категории товаров - нужно получить название всех брендов.
1.2 Для каждого бренда получить список всех товаров.
1.3 Скачать информацию по всем товарам.

или

2. Запустить полный перебор товаров от 1 до 60000 (подбираем опытным путём).

Нас устраивает более легкий вариант - полный перебор. Перебор займет чуть больше времени, но гарантирует считывание всех товаров. И помните: мы пошли легким путём.

Шаг 2. Изучаем страницу с товаром.

Кликаем на произвольный товар, к примеру: http://parfum-collection.ru/product/50322.html
Откинув шапку, меню и подвал, получим примерно такую картину:

Важная часть страницы с товаром

Важная часть страницы с товаром

Опишем блоки (слева направо, сверху вниз):
1. Наименование товара
2. Путь товара (иерархия его категорий)
3. Блок с картинками
4. Цена. У одного товара может быть несколько вариантов поставки с разной ценой (выбирается из раскрывающегося списка)
5. Текстовое описание
6. Параметры товара

Остальная информация нам не интересна.

Заглянем в исходный код страницы. Весь HTML код хорошо приправлен комментариями. Нам же лучше. Нас интересует часть текста от

<!-- Товар  /-->
до
<!-- Характеристики товара #END /-->

Основные элементы разметки опишем в следующем пункте.

Шаг 3. Парсим.

Создаем пустой файл parse.php.

max_execution_time - нужен, чтобы ограничить время выполнения скрипта. Из-за большого количества товара полный парсинг может занять значительный промежуток времени. Поэтому выставим 0 - не ограничено.

Страницу загружаем с помощью file_get_contents - как самый простой способ.

В итоге получается код:

<?php
ini_set("max_execution_time", 0); 
 
$id = 50322;
parse ($id);
 
function parse ($id)
{
	$file = file_get_contents("http://parfum-collection.ru/product/{$id}.html");
	var_dump($file);
}
?>

Запустили. Порадовались контенту страницы. Лёд тронулся.

Дальше вырежем центральную часть с информацией о товаре.

preg_match("|<!-- Товар  /-->(.*)<!-- Характеристики товара #END /-->|is", $file, $trunc);

Если регулярное выражение сработало, то в $trunc[1] будет находиться нужный нам контент.

Запускаем. Получаем пустой результат. Проверяем регулярку - все верно. Смотрим на кодировку - ага: Кодировка сайта - UTF-8, а кодировка исходника - win-1251. Меняем кодировку. Запускаем и получаем вырезанный фрагмент. Далее продолжаем работать только с вырезанным фрагментом.

Получаем наименование товара:

Название и категории товара

Название и категории товара

Наименование товара находится между тегами

. Это единственная пара тегов h1 на странице. Так что тут все легко.

Получаем категорию товара: Категорию получаем в два этапа: сначала вырезаем div с id=path, после чего, с помощью функции explode, разбиваем на части фрагменты пути. Первый и последний элемент нам не нужны. Делаем им unset.

Получаем картинки товара:

Список изображений товара

Список изображений товара

Исследуем, как выводятся картинки. Вначале идет большая картинка. Под ней идет список миниатюр, при клике на которые происходит замена большой картинки. Нас интересуют участки кода, подчеркнутые красным. Они уникальны и по ним можно получить список картинок.

Получаем цены и варианты товара:

Варианты и цены

Варианты и цены

Для этого нам надо написать два регулярных выражения. Первое - будет получать id и вариант товара. Второе - получать id и цену товара. Полученное заносим в один массив, и получаем связку из варианта товара и его цены.

Получаем описание товара:

Описание товара

Описание товара

Тут все просто. В выделенном фрагменте находится искомый текст. Ничего сложного.

Получаем характеристики товара:

Параметры товара

Параметры товара

Характеристики товаров занесены в таблицу, и разбиты построчно. Для начала получаем все строки (всё что находится между ). Далее разбиваем по ячейкам. В первой ячейке каждой строки находится название характеристики. Во второй ячейке - перечисление всех характеристик, разделённых с помощью   Воспользуемся функцией explode, чтобы получить каждую характеристику по отдельности. Дополнительно пройдемся по каждой ячейке полученного массива, и обрежем пробелы с начала и конца строки.

Шаг 4. Сохранение и другие фишки.

В статье не описано, как сохранить данные. Но людям, осилившим статью, не составит большого труда сохранить итоговый массив в БД. Так же в статье нет упоминания как сохранять картинки. Всё очень просто, и выглядит примерно так:

# для каждого товара, для каждой картинки товара:
$file = file_get_contents($img_url);
$handle = fopen('new_img_name',"x");
fwrite($handle, $file);
fclose($handle);
Более надёжную реализацию сохранения оставляю на совесть читателя.

Какие недостатки у данной реализации? Самый главный недостаток - использование file_get_contents для получения исходного кода страницы с товаром. В случае подвисания сети - функция будет ожидать 30 секунд. Что бы управлять таймаутом соединения нужно использовать сокеты или CURL. Использование сокетов или curl усложняет работу скрипта (по крайней мере для новичка), но дает множество плюсов. Больше всего я люблю указывать в user-agent "Mozilla/5.0 (compatible; googlebot/2.1; +http://www.google.com/bot.html)"

PS.
Исходный код
Итоговая структура данных:

$data (array)
name Gardenia
path
array
1
array
url /category/parfum/1.html
path Парфюм
2
array
url /category/selective-fragrances/6.html
path Эксклюзивная Парфюмерия
3
array
url /brand/chanel/18.html
path Chanel
img
array
0
array
url http://parfum-collection.ru/pix/2011/02/04/3_hn87yh8.jpg
1
array
url http://parfum-collection.ru/pix/2011/02/04/3_239t6zk.jpg
price
array
739
array
text туалетная вода 75ml
cost 7800
733
array
text туалетная вода 200ml
cost 12 500
text


Chanel Gardenia (Шанель Гардения) - аромат подчеркнет вашу сексуальность, а также индивидуальность, великолепный образ, чувство стиля, прекрасный вкус, а главное - безграничную и умопомрачительную женственность.

params
array
0
array
text Ноты аромата
values
array
0 апельсин (цветы)
1 гардения
2 мускус
3 сандаловое дерево
4 цитрусовые
5 ваниль
1
array
text Cемейства
values
array
0 мускусные
1 фруктовые
2 цветочные

Subscribe without commenting


  1. Sampo
    9th Май 2011 в 18:42

    А в чём состоит месть злым спамерам?
    Однажды делал примерно то же самое, но прямо из терминала, используя wget, grep и sed. В конце все картинки обрезал, панорамировал и поменял яркость/контраст (imagemagic). Думаю, можно ещё использовать алгоритм цепей Маркова для микширования текстов описаний — где-то выкинуть пару слов, где-то поменять порядок следования.

  2. admin
    9th Май 2011 в 18:49

    Sampo :

    А в чём состоит месть злым спамерам?

    В статье не упоминается, но:
    1. Хозяева интернет магазина заказали SEO-раскрутку одной фирме.
    2. Эта фирма занималась рассылкой спама, с парфюмерными предложениями.
    3. Статья помогает интернет магазину поделится своей базой со всеми 🙂
    4. В дополнении к этому — хостеру SEO компании было отправлено письмо с информацией о спаме, с предложением принять меры.

  3. ryst
    7th Февраль 2012 в 19:16

    Оо да ты просто злой гений :))) Ещебы ссылки на магазин закрыл ноиндексом.
    Статья хорошая. Спасибо.

  4. 2nd Декабрь 2012 в 02:12

    Может подскажите как из такой ссылки _http://www.tissot.ua/tissot-i-id-i-37-i-product-i-T014.427.16.031.00-i-index.html выделить id для парсера? Или тут по другой схеме нужно парсить?

  5. 2nd Декабрь 2012 в 02:43

    >Запустили. Порадовались контенту страницы. Лёд тронулся.

    как вы это делаете?

  6. Тарлюн Максим
    2nd Декабрь 2012 в 10:40

    _http://www.tissot.ua/tissot-i-id-i-38-i-product-i-T014.427.16.031.00-i-index.html
    Большую часть URL можно удалить. В итоге ссылки на страницу с товаром имеют вид:
    _http://www.tissot.ua/tissot-i-id-i-38

    Меняя последнюю цифру мы получаем разные товары.

  7. Тарлюн Максим
    2nd Декабрь 2012 в 10:42

    Что именно?

  8. 2nd Декабрь 2012 в 19:32

    Тарлюн Максим :Что именно?

    ну как происходит механизм парсинга? Он с сайта запускается или …? Если это доступно лишь опытным кодерам, то я не буду приставать)))

  9. Тарлюн Максим
    2nd Декабрь 2012 в 20:18

    @Miroslav
    Я пишу парсеры на PHP или Perl. Это интерпретируемые языки. Что бы запустить такой скрипт нужен веб-сервер. Подойдет и локальный. Например denwer или Open Server.
    Если вы задаете такие вопросы, то написание парсеров пока не для Вас.
    Научитесь азам. Попробуйте парсить элементарные вещи (курс валют или погоду). А дальше разбирайтесь с более сложными задачами.

  10. 2nd Декабрь 2012 в 22:03

    запускал локальный сервер, но лишь для того, чтобы сайт собрать. Denwer
    а так вы правы, наверное, это не для меня… Не готов

  11. Евгений
    19th Апрель 2013 в 13:41

    А как парсить результаты поиска на сайте, если URL не отражает условий поиска (по крайней мере, полностью). Например, здесь
    http://212.50.98.101/findtour-ag/Extra/QuotedDynamic.aspx

  12. Тарлюн Максим
    19th Апрель 2013 в 13:53

    Результаты выдачи можно получить отправив POST запрос по адресу формы. Запустите FireBug и исследуйте какие запросы посылает форма при отправке. Попробуйте послать аналогичный запрос. Если все пройдет успешно — вы получите блок с таблицей.

  13. Евгений
    19th Апрель 2013 в 14:04

    Класс, спасибо 🙂

  14. Рома
    18th Апрель 2014 в 23:25

    У меня на локальном OpenServer функция file_get_contents вызывает ошибку. На бесплатном хостинге пишет Boolean False. В чем может быть проблема? Спасибо.

  15. Рома
    18th Апрель 2014 в 23:46

    Попробовал изменить цель (сайт), получилось. А вот на нужном мне сайте не работает. Это защита у них такая от парсинга или что?

  16. Рома
    18th Апрель 2014 в 23:50

    failed to open stream: HTTP request failed! HTTP/1.0 500 Internal Server Error in C:\OpenServer\domains\domain\test.php on line 9