За последние 5 лет я написал множество лоадеров. Это так называемые программки, которые парсят инфу на сайтах-источниках и сохраняют ее себе в базу. Зачастую они представляют из себя последовательность регулярных выражений, с помощью которых находятся значения в нужных клеточках. Лоадеры могут авторизоваться, могут коннектиться через прокси, а иногда даже распознавать защитные картинки. Суть не в этом.
Теоретическая проблема в том, что невозможно написать абсолютно автоматический лоадер. Мы можем затырить любую инфу, но база превращается в свалку, если лоадер теряет классификацию сайта-источника. А когда начинаем сохранять классификацию, возникает проблема.
Рассмотрим пример. Пусть есть автосайт, на который грузятся объявления о продаже авто с сотни других ресурсов. Лоадер парсит объяву, выдает массив:
Автоматический лоадер часто работает так: смотрит в таблице марок по названию, если есть ford — берет id марки, если нет — добавляет «ford» в марки, и берет его id. То же делает с моделью и модификацией. Потом добавляет объявление с полученными id-шниками. Такая система плоха тем, что обязательно найдется объява, в которой на месте марки будет «ФОРД» или не «ВАЗ», а «VAZ», или «автоВаз», или не «Санкт-Петербург», а «С-Петербург»,«СПб»,«Cп-б». Умный гугл поймет, что это синонимы, а наш глупенький лоадер, сверяющий названия посимвольно, нет. В результате получается бардак в таблицах с классификациями.
Пытаясь минимизировать ручной труд монгола/модератора, я придумал такой алгоритм.
Прежде всего, лоадер состоит из двух частей.
Первая — loader_pages.
Скрипт просматривает страницы со списками объявлений типа вот таких http://cars.auto.ru/cars/used/ford/focus/ и тупо собирает ссылки на отдельные объявы. + находит ссылки на переходы по страницам и идет по ним рекурсией. Нашел ссылку на объяву — добавил ее в базу или, если она уже добавлена, обновил «дату последнего нахождения» на текущую. Это нужно для того, чтобы (лоадер работает ежечасно) удалять объекты, у которых дата нахождения ссылки достаточно старая (это значит, что ссылка уже не найдена, а значит объект с источника был удален).
Вторая — loader_offer.
Берет из базы еще не обработанные ссылки, грузит html, парсит. Получает массив типа
Грузит табличку compares. В ней находятся сопоставления, которые будут вручную обрабатываться модератором. Табличка состоит из полей:
В нашем случае,
Если соответствующее сравнение уже проставлено, ура победа, берем id-шник. Если нет — добавляем в compares новое сравнение, а объект не добавляем.
Модератор просматривает не проставленные сравнения и сопоставляет им значения из соответствующих «хороших» наших таблиц с марками автомобилей, моделями, городами итд.
Паренты.
Все хорошо работает пока таблицы маленькие. К примеру, марки авто — их всего 100. Сопоставить раз плюнуть. Моделей в моей базе 7000, а модификаций — 20.000. Представляете, из 20 тысяч выбрать сопоставление модификации «1.6 Ti-VCT 5d», которая у меня называется «1.6 Ti-VCT»? Модератор умирает. Или нужен хороший поиск.
Но можно сделать проще. При загрузке объявы мы будем обрабатывать сравнения по-порядку, сначала марка, затем модель, после модификация. Берем сравнение для марки,
находим его или добавляем — не суть. Берем id-шник этого сравнения и записываем его в дополнительное поле parent для сравнения модели:
То же самое делаем в модификации, в парент которой пишем id сравнения модели.
Модератор работает по-порядку. Сначала берет сравнения марок и все их проставляет. Потом берет сравнение модели. При этом мы видим, что у сравнения есть parent-сравнение марки, которое уже проставлено, поэтому в качестве вариантов для сопоставления нужно выводить не все возможные модели, а только те, у которых марка соответствует значению этого parent-сравнения. Ну то есть «Ford» проставили, а затем «Focus» выбираем не из 7000 моделей, а только из сотни моделей фордов.
Суть этого поста вовсе не в том, что я придумал что-то абсолютно новое. Просто нигде не встречал описания этих программ. А у меня мне нравится именно излишняя практичность, потому что в принципе ясно, что каждый объект — подмножество вершин некоторых деревьев, а парсер — это сопоставление элементов html-кода страницы этим вершинам. Можно бы было навести теорию, что-то вроде языка для описания парсеров итд… С другой стороны, средний код лоадера на php у меня занимает 2 страницы. И не ясно, стоит ли париться с теорией, потому как мне не придумать, как еще уменьшить и упростить этот код, даже применив какой-то абстрактный язык.
Теоретическая проблема в том, что невозможно написать абсолютно автоматический лоадер. Мы можем затырить любую инфу, но база превращается в свалку, если лоадер теряет классификацию сайта-источника. А когда начинаем сохранять классификацию, возникает проблема.
Рассмотрим пример. Пусть есть автосайт, на который грузятся объявления о продаже авто с сотни других ресурсов. Лоадер парсит объяву, выдает массив:
{марка:"ford", модель:"focus", модификация:"1.6 Ti-VCT 5d", описание: итд...}.
Автоматический лоадер часто работает так: смотрит в таблице марок по названию, если есть ford — берет id марки, если нет — добавляет «ford» в марки, и берет его id. То же делает с моделью и модификацией. Потом добавляет объявление с полученными id-шниками. Такая система плоха тем, что обязательно найдется объява, в которой на месте марки будет «ФОРД» или не «ВАЗ», а «VAZ», или «автоВаз», или не «Санкт-Петербург», а «С-Петербург»,«СПб»,«Cп-б». Умный гугл поймет, что это синонимы, а наш глупенький лоадер, сверяющий названия посимвольно, нет. В результате получается бардак в таблицах с классификациями.
Пытаясь минимизировать ручной труд монгола/модератора, я придумал такой алгоритм.
Прежде всего, лоадер состоит из двух частей.
Первая — loader_pages.
Скрипт просматривает страницы со списками объявлений типа вот таких http://cars.auto.ru/cars/used/ford/focus/ и тупо собирает ссылки на отдельные объявы. + находит ссылки на переходы по страницам и идет по ним рекурсией. Нашел ссылку на объяву — добавил ее в базу или, если она уже добавлена, обновил «дату последнего нахождения» на текущую. Это нужно для того, чтобы (лоадер работает ежечасно) удалять объекты, у которых дата нахождения ссылки достаточно старая (это значит, что ссылка уже не найдена, а значит объект с источника был удален).
Вторая — loader_offer.
Берет из базы еще не обработанные ссылки, грузит html, парсит. Получает массив типа
{марка:"ford", модель:"focus", модификация:"1.6 Ti-VCT 5d", описание: итд...}
Грузит табличку compares. В ней находятся сопоставления, которые будут вручную обрабатываться модератором. Табличка состоит из полей:
{лоадер,тип,найденное значение,id в соответствующей таблице классификации}.
В нашем случае,
{лоадер:"auto.ru",тип:"марка",значение:"ford",сопоставление:"..."}.
Если соответствующее сравнение уже проставлено, ура победа, берем id-шник. Если нет — добавляем в compares новое сравнение, а объект не добавляем.
Модератор просматривает не проставленные сравнения и сопоставляет им значения из соответствующих «хороших» наших таблиц с марками автомобилей, моделями, городами итд.
Паренты.
Все хорошо работает пока таблицы маленькие. К примеру, марки авто — их всего 100. Сопоставить раз плюнуть. Моделей в моей базе 7000, а модификаций — 20.000. Представляете, из 20 тысяч выбрать сопоставление модификации «1.6 Ti-VCT 5d», которая у меня называется «1.6 Ti-VCT»? Модератор умирает. Или нужен хороший поиск.
Но можно сделать проще. При загрузке объявы мы будем обрабатывать сравнения по-порядку, сначала марка, затем модель, после модификация. Берем сравнение для марки,
{лоадер:"auto.ru",тип:"марка",значение:"ford",сопоставление:"..."},
находим его или добавляем — не суть. Берем id-шник этого сравнения и записываем его в дополнительное поле parent для сравнения модели:
{лоадер:"auto.ru",тип:"модель",значение:"focus",сопоставление:"...",parent:"id сравнения марки"}.
То же самое делаем в модификации, в парент которой пишем id сравнения модели.
Модератор работает по-порядку. Сначала берет сравнения марок и все их проставляет. Потом берет сравнение модели. При этом мы видим, что у сравнения есть parent-сравнение марки, которое уже проставлено, поэтому в качестве вариантов для сопоставления нужно выводить не все возможные модели, а только те, у которых марка соответствует значению этого parent-сравнения. Ну то есть «Ford» проставили, а затем «Focus» выбираем не из 7000 моделей, а только из сотни моделей фордов.
Суть этого поста вовсе не в том, что я придумал что-то абсолютно новое. Просто нигде не встречал описания этих программ. А у меня мне нравится именно излишняя практичность, потому что в принципе ясно, что каждый объект — подмножество вершин некоторых деревьев, а парсер — это сопоставление элементов html-кода страницы этим вершинам. Можно бы было навести теорию, что-то вроде языка для описания парсеров итд… С другой стороны, средний код лоадера на php у меня занимает 2 страницы. И не ясно, стоит ли париться с теорией, потому как мне не придумать, как еще уменьшить и упростить этот код, даже применив какой-то абстрактный язык.