Решаем квартирный вопрос при помощи API Яндекс.Карт

В жизни даже самого «махрового» IT-шника порой наступает момент, когда нужно не только вылезти из своей берлоги на улицу, но целиком перенести себя на новое место жительства. Обычный человек в таких случаях вооружается Интернетом и прочёсывает сайты недвижимости в поисках подходящих вариантов, которые отмечаются на карте, выписываются или распечатываются, а затем планомерно прозваниваются. Если наступает конец цикла, а задача ещё не выполнена — goto line 1… А на каком-то этапе человеку это надоедает и он идёт в агенство.

Вот и в моей жизни пришло время для переезда, но проведя несколько дней за такой рутинной деятельностью я вспомнил, что незря ношу бороду есть такой чудесный сервис, как Яндекс.Карты, и у них есть не менее чудесное API. Посидев одно утро и скомбинировав всё с простейшим граббером на PHP и XPath я получил такую вот красочную карту, где разными маркерами можно отмечать объекты (квартиры) по любому из критериев, или просто одним взглядом оценить, какие из них ближе к желаемому месту дислокации (в моём случае это было метро):

Снимок экрана



Сразу скажу, что для меня это первый опыт поиска квартиры и потому я могу не знать о сервисе, делающим что-то подобное — однако после нескольких дней поиска ничего такого на вторичном рынке недвижимости я не нашёл. А нашёл бы — не было бы повода писать этот пост, так что поскорее продолжим, пока кто-нибудь из хабраюзеров не вставил своё веское «ня»…

Граббер


Информацию будем брать с сайта «Бюллетеня недвижимости»: у них удобный поиск с кучей фильтров, но без карты, что весьма проблематично для людей вроде меня, которые не знают каждую улицу на память.

На сайте есть возможность просмотреть все результаты поисковой выдачи (не более 300) на одной странице в режиме печати — этим мы и воспользуемся. URL имеет такую форму:
http://www.bn.ru/zap_fl.phtml?print=printall&параметры_поиска


Допустим, мы получили страницу с нужными результатами по фильтру. Как вытащить из неё список квартир? Обычно я эту задачу решаю регулярками, но у этого сайта какая-то особая неструктура HTML, поэтому проще оказалось использовать XPath (W3Schools). С его помощью легко получить список строк в таблице (), а из них - ячейки с адресом, ценой и прочим, просто перебрав коллекцию DOMNode.

Всё вместе это выглядит так:
// Все объявления.
$all = array();
$baseURL = 'http://www.bn.ru/zap_fl.phtml?print=printall&';
// Текст, который выводит сайт при отсутствии результатов поиска.
$empty = iconv('utf-8', 'cp1251', 'Количество найденных вариантов 0');

// В моей форме есть возможность выбора нескольких районов.
foreach ((array) $_REQUEST['region'] as $region) {
  $url = $baseURL."region$region=$region&";
  // Другие критерии...

  // Этот цикл нужен, чтобы результатов не было больше 300 - таким образом мы 
  // получаем все объявления по нашим критериям, даже если их больше 300.
  foreach (range(0, 10000, 1000) as $price0) {
    $reqURL = $url."price1=$price0&price2=".($price0 + 999);
    // Скачиваем страницу - см. примечание после кода.
    $data = dl($reqURL);

    if (!strpos($data, $empty)) {
      // Результаты есть.
      $offers = parse($data);
      $all = array_merge($all, $offers);
    }

    // Не бомбим сервер запросами.
    usleep(200000);
  }
}


Функция dl() может быть заменена на вызов cURL или простой file_get_contents() - правда, последний у меня не сработал и я использовал свой класс для скачки ресурсов, имитирующий браузер.

Функция parse() для разбора HTML в массив объявлений.
function parse($html) {
  // Сообщаем DOMDocument, что у нас документ в UTF-8 (единственный способ,
  // который сработал у меня). 
  $html = '<?xml encoding="UTF-8">'.iconv('cp1251', 'utf-8', $html);

  $doc = new DOMDocument('1.0', 'utf-8');
  @$doc->loadHTML($html) or die('loadHTML: '.$html);

  // Делаем выборку строк таблицы с объявлениями.
  $xpath = new DOMXPath($doc);
  // На странице несколько таблиц, плюс разные типы строк и ячеек - добавляем
  // предикаты (условия) для выбора только тех, что содержат объявления.
  $nodes = $xpath->query('//table[@class="results"]/tr[th[@class="head_kvart"] or td[@width or @class="tooltip"]]');

  // Готовый массив с квартирами.
  $results = array();
  // В таблице нет колонки с числом комнат (вернее, она не всегда есть) - считаем
  // их сами.
  $roomCount = 1;

  // $nodes - коллекция элементов-строк (tr).
  foreach ($nodes as $row) {
    // Собираем данные из ячеек - площадь, адрес и т.п.
    $cells = array();
    $cell = $row->firstChild;

    while ($cell) {
      $cell->nodeType == XML_ELEMENT_NODE and $cells[] = trim($cell->nodeValue);
      $cell = $cell->nextSibling;
    }

    if (count($cells) == 1) {
      // Строка всего с одной ячейкой - числом комнат для результатов в строках
      // ниже. Запоминаем его.
      $roomCount = (int) reset($cells);
    } else {
      $cells[0] = $roomCount;
      
      // Некоторые объявления имеют colspan на полях с ценой - заполняем остальные
      // данные нулевыми значениями.
      if (count($cells) == 10) {
        array_splice($cells, 6, 1, array(0, '', $cells[6], ''));
      }
      
      // Получаем адрес объявления, чтобы мы могли просмотреть подробности с карты.
      $html = $row->ownerDocument->saveXML($row);
      if (preg_match('~<a href="([^"]+)~u', $html, $match)) {
        array_unshift($cells, 'http://www.bn.ru'.$match[1]);
      } else {
        array_unshift($cells, '');
      }

      // Наконец, присваиваем цепочке ячеек осознанные имена.
      $keys = array('url', 'rooms', 'address', 'floors', 'houseType', 'area', 'areaLiving', 'areaKitchen', 'toilet', 'price', 'conditions', 'seller', 'phone', 'notes');

      $result[] = array_combine($keys, $cells);
    }
  }

  return $result;
}


Карта


Отлично, половина задачи выполнена - массив $all содержит все объявления в удобном для работы формате. Дело за малым - расположить их на карте. Для удобства приведу образец элемента массива:
array(
  'url' => 'http://www.bn.ru/detail/flats/xxxxxx.html?from=search', 
  'rooms' => 1, 
  'address' => '7 Советская ул., xxx', 
  'floors' => '1\\5', 
  'houseType' => 'СФ', 
  'area' => '30', 
  'areaLiving' => '18.3', 
  'areaKitchen' => '6', 
  'toilet' => ' ', 
  'price' => '3100', 
  'conditions' => ' ', 
  'seller' => 'xxxxx Недвижимость', 
  'phone' => '(965) xxxxxxx', 
  'notes' => 'ПП свободна ХС торг',
)


Базовый HTML для минимально работающей Яндекс.Карты:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Map grabber</title>

    <script src="//api-maps.yandex.ru/2.0/?load=package.standard&lang=ru-RU" type="text/javascript"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>

    <style>
      html, body, .#map { margin: 0; padding: 0; }
      #map { width: 100%; height: 800px; }
    </style>
  </head>
  <body>
    <div id="map"></div>
  </body>
</html>


jQuery для работы Карт не нужен, но он пригодится для раскрашивания маркеров.

Допустим, у нас уже есть полученный список квартир. Его мы сбросим в массивы на JavaScript и всё остальное уже будем делать на нём:
// Массив адресов для расстановки маркеров.
var coords = []
// Массив информации об этих адресах.
var info = []

<?foreach ($all as $offer) {?>
    coords.push('Санкт-Петербург, <?=$offer['address']?>')
    info.push(<?=json_encode($offer, JSON_UNESCAPED_UNICODE)?>)
<?}?>

ymaps.ready(init);


Теперь самое интересное - расставляем маркеры в функции init(). Для этого используется геокодирование и объект MultiGeocoder из примера API Карт; ему мы передаём список адресов (coords), он получает по ним список координат (широта и долгота), по которым мы выставляем на карте маркеры.

function init() {
  // Создаём карту с центром в в Санкт-Петербурге:
  var map = new ymaps.Map('map', {
    center: [59.932666, 30.329596],
    zoom: 13,
    behaviors: ['default', 'scrollZoom'],
  })

  // Добавляем кнопки масштаба и линейку:
  map.controls
    .add('zoomControl', {left: 5, top: 5})
    .add('mapTools', {left: 35, top: 5})

  // Преобразуем адреса в координаты с помощью геокодера Карт.
  (new MultiGeocoder({boundedBy: map.getBounds()}))
    .geocode(coords)
    .then(
      function (res) {
        // Сервер вернул нам результат для всех запрошенных адресов.
        for (var i = 0; i < res.geoObjects.getLength(); i++) {
          var cells = info[i]
          
          // Создаём текст для показа при клике на каркере - код для таблицы 
          // я пропустил, так как там ничего сложного.
          var text = '<p>' + $('<b>').append(
            $('<a>')
              .attr({href: cells.url, target: '_blank'})
              .text(cells.address)
          )[0].outerHTML + '</p>'

          // Геообъект - уже готовый маркер, полученный от сервера.
          var geo = res.geoObjects.get(i)
          // Сохраняем объект маркера для простого поиска позже.
          info[i].geo = geo
          // Заменяем стандартный текст в окошке при клике.
          geo.properties.set('balloonContentBody', text)
        }

        // Ставим все маркеры на нашей карте.
        map.geoObjects.add(res.geoObjects)
      },
      function (err) { alert(err) }
    )
    
  return map
}


Вот и всё. Собрав описанное выше в законченный скрипт мы одним нажатием можем расставить все актуальные объявления на карте и выбрать подходящий вариант. Не знаю как для вас, а для меня это очень сильно облегчило поиск.


А как же?..


Самые внимательные могли заметить, что я пропустил подсветку, которая так радует глаз на скриншоте в начале статьи. А зря! Это, пожалуй, так же удобно, как и сама расстановка объектов на карте.

Я решил не изобретать велосипед и сделал поля для ввода кусков кода на JavaScript для проверки условий, которые применяются к каждому маркеру. Если одно из условий совпадает, маркер окрашивается в другой цвет и другие условия не проверяются. Таким образом можно подсветить, например, все однокомнатные квартиры, затем - все остальные, имеющие большую кухню, а затем - те из оставшихся, которые находятся на втором этаже.

Полный код приводить не буду - он тривиален. Приведу только часть для подсветки (типы маркера описаны в API):
// Типы маркеров и их условия вида [ [тип_маркера, код_условия], ... ].
var markers = []

// Поле ввода маркера находится внутри <p data-marker="twirl#redDotIcon">.
// twirl#redDotIcon - тип маркера (preset) для передачи в карту.
$('p[data-marker] input').val(function (i, value) {
  var marker = $(this).parent().attr('data-marker')
  value = $.trim(value)
  value && markers.push([marker, value])
  return value
})

// Перебираем имеющиеся маркеры (см. init()). cells - {price: 123, rooms: 2, ...}.
$.each(info, function (i, cells) {
  var colored = false

  for (var i = 0; i < markers.length && !colored; i++) {
    var item = markers[i]
    var func = new Function('cells', 'return ' + item[1]);

    // Выполняем код для проверки условия - если оно выполняется, красим маркер.
    if (func(cells)) {
      cells.geo.options.set('preset', item[0])
      // Пропускаем оставшиеся типы маркеров.
      colored = true
    }
  }

  // Не одно условие не совпало - красим маркер в умолчательный цвет.
  colored || cells.geo.options.set('preset', 'twirl#blueIcon')
})


Поработав над этим кодом ещё немного можно сделать куда более оптимизированную и удобную систему, но для одного утра, по-моему, совсем неплохо!

Надеюсь, статья была вам полезна. С радостью отвечу на вопросы или исправлю неточности, если таковые найдутся.

UPD: по просьбе читателя выложил полный код страницы на Gist. Можете использовать как угодно, но без гарантии и инструкции по эксплуатации :)
Поделиться публикацией

Комментарии 51

    +4
    Очень круто. Если доведёте до эндюзер-кондиции, может получиться очень неплохой сервис.
    Персонально я тоже вечно ломал себе голову, как риэлторы во всем этом разбираются.
      +5
      У меня тоже была идея сделать такой сервис, но как обычно времени на все интересные проекты не хватает. Возможно кто-то возьмёт идею на вооружение — я лично очень удивился (и до сих пор с трудом верю), что этого до сих пор никто не придумал.
        +3
        Такое ощущение, что идея подобного сервиса возникает в голове у каждого айтишника, которому вдруг нужно найти новое место жительства.
        Я когда-то задумывался, как хорошо было бы, если бы можно было на карте отсортировать сразу варианты по близости к метро и времени пути до работы с учетом всех пробок итд. Теоретически, вся эта инфа есть на Яндекскартах, но можно ли это брать во вселенских масштабах? На этом мой энтузиазм завял.
          0
          ну наример на циане есть карта со всеми предложениями. только она спрятана, и видна из конкретных карточек отдельных квартир, но там показываются все предложения.
          +1
          Персонально я тоже вечно ломал себе голову, как риэлторы во всем этом разбираются.


          Да риэлторы вообще не парят насчёт географической привязки объявлений через апи яндекс-карт :)
          –1
          однако после нескольких дней поиска ничего такого на вторичном рынке недвижимости я не нашёл


          Жили бы мы в Москве, могли бы воспользоваться новым сервисом Choister. Надеюсь, они скоро и в Питер придут, а то сам в поисках жилья, но ни одного вменяемого сервиса нет. Узнавал у агентов — с их стороны всё так же дико выглядит.

          P.S. Это не реклама!
            +4
            Сервис выглядит красиво и вкусно, но в выдаче куча мусора.
            В разделе покупка куча результатов, которые на самом деле означают аренду. Это чинится указанием нижней грани цены, но уже неприятно.
            Потом и вовсе выяснилось, что у некоторых квартир вместо цены за всё выдаётся цена за квадратный метр.

            Лучше бы у такого сервиса было меньше, значительно меньше поставщиков информации, зато все бы показывались правильно…
              +1
              А чем циан не понравился? Поиск в нарисованном мышкой регионе, цветные маркеры, правда по одному на дом. Куча параметров.
                –1
                Гораздо удобнее, когда фильтры находятся рядом с результатами поиска и сразу же влияют на выдачу.

                В циане необходимо переходить на новую страницу, чтобы изменить параметры поиска. Почти та же проблема у Яндекс.Недвижимости: страница перезагружается. Может показаться, что это мелочи, но я очень привык пользоваться поисковиками авиабилетов, в которых подобный функционал давно реализован и уже не является особенностью отдельно взятого сайта — так у всех. И меня удивляет, что такого нет в поисковиках недвижимости. Ребята из Choister поняли это, и делают красивый и удобный сервис. Успехов им.

                С фильтрацией мусора, надеюсь, тоже разберутся — Яндекс же делает это каким-то образом.
                  0
                  Когда я год назад искал квартиру на циане в Петербурге, там был один мусор. Зато в Москве у них все замечательно.
                  0
                  О да, квартира за 20т.р. на цветном бульваре — это несомненно реально.
                  На cian информационный лохотрно или заманушные пустышки хотя бы банят.

                  p.s. не работает под Android.
                  +20
                  Вот сервис Яндекс Недвижимость — ссылка на уже готовый запрос.

                  — привязка к картам
                  — аггрегирует со многих источников недвижимости
                  — удобные фильтры.
                    0
                    Надо же, про ипотеку на Я.Услугах знал, а про этот — нет. Спасибо, сохранил к себе — с первого взгляда похоже на то, что мне нужно.
                    +2
                    Информацию будем брать с сайта «Бюллетеня недвижимости»: у них удобный поиск с кучей фильтров, но без карты,
                    ?! По карте. Ссылка прямо со страницы поиска, которую Вы по сути и грабите, правая верхняя «кнопка».
                      +1
                      Н-да, вот что значит попал на сайт с гугла и не читал меню… Но всё равно здесь как-то странно карта работает — зачем ей постраничная навигация — это на карте-то?

                      Попробовал потаскать карту мышкой — маркеры прыгают, при смене масштаба тоже самое. Неудобно. Плюс нет подсветки маркеров — нельзя найти однокомнатную квартиру рядом с трёхкомнатной, к примеру (да, передо мной стоит такая нетривиальная задача).
                      +3
                      Вы молодец, все что вы сделали это здорово, но… Вся эта деятельность — явный пример подмены результата процессом… Ибо сервисов, показывающих квартиры на карте — как бы даже не один, начиная с яндекса.
                        +2
                        Не знаю как у вас, а у меня удовольствие от самого процесса программирования часто затмевает здравый смысл и достаточно одной мыли «А что, если...» — и всё, процесс пошёл. Обычный человек, наверное, обратился бы к гуглу, но… судя по количеству добавлений в избранное этого поста я всё-таки сделал благое дело :)
                        +3
                        Мало пользы из за любви риэлторов к фейк объявлениям. Лишь бы им позвонили. Я в итоге сдался, и описал риэлтору что нужно, а она звонила когда черкз нее проходил подходящий вариант.
                          +2
                          квартиру нашли?
                            0
                            Пока нет, в процессе.
                            0
                            У автора хорошая мысль покрасить в разный цвет маркеры по какому-то критерию. Близость к метро хорошо, но специфично для крупных городов. У нас, например, ищут по району. Интересно покраситить и по качественной харектиеристике кваритры (площадь, квартирность и т.п.). но есть проблема и в этом случае — в одном доме может быть много разных вариантов и как красить непонятно. У яндекс карт есть кластеры, их целесообразнее в этих случаях использовать, но как красить сам кластер в этом случае — непонятно…

                            PS. У нас в городе это реализовано примерно так.
                              +1
                              PS. У нас в городе это реализовано примерно так.

                              Здорово, нечто подобное должно быть и для Питера. Да, собственно, ссылок здесь уже накидали, на тот же Яндекс.

                              По поводу маркеров — у меня один маркер — одно объявление, если они все в одном доме (чего я у «БН» почти не встречал), то будут перекрывать друг друга. Как вариант — завести отдельный тип маркера и отмечать им те, которые находятся слишком близко друг к другу.
                                +1
                                Как вариант — завести отдельный тип маркера

                                Можно, но лучше воспользоваться кластерами от авторов API Яндекс.Карт, которые уже реализуют этот функционал. :)
                                  0
                                  Да, это лучший вариант. Раньше пользоваться не приходилось. Возьму на заметку.
                              0
                              Многообещающий заголовок у статьи, однако. И в моем захолустье нашлись квартиры, как ни странно.
                                –31
                                Статья ориентирована на «махровых» понаехавших.

                                Вредная статья.
                                  –35
                                  Прекратите сюда «ехать».

                                  Вас слишком много.

                                  Ехайте в Воронеж.
                                  +2
                                  Вот это неплохо было бы распарсить:
                                  arenda-piter.ru/workpage.php?page=online
                                    0
                                    Насколько я понял, обычный человек не сможет этим воспользоваться?
                                      +2
                                      Разве что с моей машины и с подсказками свыше. А что, сильно понравилось?
                                      –6
                                      ООП? Нет, не слышал.
                                        +6
                                        Вы способны написать качественный чистовик за одно утро по всем канонам программирования? Если да — снимаю шляпу.
                                          +7
                                          Что не так с процедурным стилем?
                                          0
                                          Около двух лет назад сделали по Самаре такое:
                                          realty.samara24.ru/map_search/

                                          Для всех поступающих объявлений автоматически распознаются адреса. Сложнее всего было написать парсер этих объявлений, когда из текста объявления нужно было понять адрес и дальше вычислить координаты. В целом получилось довольно успешно, порядка 95% распознается.
                                            0
                                            Заранее прошу прощения за обилие рекламы, это зависит не от меня
                                            +2
                                            Я никогда не был настолько зол, как в те дни, когда столкнулся с подобной проблемой в Москве.

                                            Чертовы риелторы засрали все площадки объявлений фейками до полной невозможности использования. Беда в том, что работать через риелтора выгодно арендодателям и прямых контактов становится все меньше.

                                            Была идея еще_одного сервиса, который бы банил эту дрянь по телефонам, почте, адресам, оставляя только непосредственных владельцев и выступал в качестве гаранта за минимальный процент (2-5%) вместо риелторских 50-100, но юрподготовка такой штуки не интересна.
                                            В принципе есть несколько фильтрующих сервисов, Новоебенево, например. Однако у них мало контента, что говорит о непопулярности прямой аренды.
                                              0
                                              Ужасный сайт.
                                              1. Несерьёзное название (среди молодёжи может и будет популярно)
                                              2. Отсутствие многих нужных параметров, таких как длительный срок (чтобы вычеркнуть аренду на лето), минут до метро (отсеить далёкие предложение).
                                              3. Нет системы жалоб, комнаты перепутаны с квартирами.
                                              4. Клик на новый baloon не закрывает старый.

                                              p.s. тот же мусор и на Choister, и на Я.Недвижимость. То есть у всех, кто парсит тот же самый irr.ru, на 90% состоящий из фейка. Например это АН bazaarenda.irr.ru — 100% фейка, все фотографии чужие с сайтов дизайн-студий.
                                                0
                                                И база стала огромной. Забили похоже или продали. Год назад там штук 200 квартир всего было и 3 города.
                                                Судя по названию сервис делали ради эксперимента, как в этом топике.
                                              –1
                                              Карта с объявлениями жилья для Киева, Харькова, Днепропетровска, Донецка, Одесса с поиском и блокнотом: domowed.com.
                                                0
                                                Никак не могу понять — здесь уже накидали штук пять ссылок на сайты по разным городам, но почему они не объедены в один (Яндекс не в счёт)? Ведь нет никакой разницы, показывать квартиры в Москве или в Киеве.

                                                Вопрос риторический.
                                                0
                                                а как же Cian? там и карта есть и много критериев.
                                                зы: не реклама. он правда очень удобен.
                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                    0
                                                    Основная проблема как раз в том, что найденная квартира может быть размещена риэлтером. А значит никаких гарантий…
                                                      +2
                                                      Основная проблема, что большинство квартир «рыба» которые «только что сдали, но у нас есть другие варианты...»
                                                      0
                                                      От всех этих сервисов толку немного до тех пор, пока рынок не повернется лицом к арендатору. Сейчас у человека сдающего квартиру, нет никаких оснований делать это без агента. Агент снимает с него все заботы, а комиссию получает с арендатора. Поэтому только если через друзей или знакомых, кто-то съезжает, у тебя есть шанс перехватить и заехать.
                                                        0
                                                        Сервис может стать полезным, если будет найденные варианты отсеивать по номерам телефонов: если один телефон встречается для разных квартир — помечаем как риэлтор. Плюс вести базу этих номеров, чтобы через пару месяцев довольно точно отсеивать риэлторов.
                                                          0
                                                          да, каюсь, тоже написал приложение для поиска и отслеживания объявлений =)
                                                          даже два: граббер на java(jsoup либа + пишет в sqlite), фронтенд на adobe air (более удобный поиск, отслеживание изменений объявлений и тп)

                                                          выглядит так
                                                            0
                                                            И это чисто для себя? Выглядит посерьёзней моей странички.
                                                              0
                                                              да, внутри простая самописная ОРМ для флекса из другого проекта, остальная гуйня на эйре делается быстро, за пару дней справился.
                                                            0
                                                            По просьбе читателя выложил полный код страницы на Gist. Можете использовать как угодно, но без гарантии и инструкции по эксплуатации :)
                                                              0
                                                              Пожалуйста, не используйте для своей функции имя dl. Есть такая встроенная функция: php.net/dl

                                                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                              Самое читаемое