Office Hunt
объявлений в базе: 4 345Office Hunt — утилита поиска коммерческих помещений по объявлениям OLX в Ташкенте.
Начиналась она для себя: владелец студии искал офис, устал листать выдачу и написал инструмент под собственную задачу. Потом её купил риелтор — и мы адаптировали. Имя заказчика не называем.
Историю рассказываем как есть, потому что она обычная и хорошая: инструмент вырос из настоящей боли, а не из брифа. Так получаются вещи, которые работают.
У объявлений на OLX нет координат.
Формально они есть: площадка отдаёт точку. Но точка эта — центроид района. То есть все объявления Яккасарая лежат ровно в одной координате, все объявления Мирзо-Улугбека — в другой одной координате. Для карты это бесполезно: районы в Ташкенте по несколько километров поперёк, а офис ищут в шаговой доступности от конкретного места.
Настоящая география лежит не в полях, а в тексте объявления. Живым языком: «за Максимкой», «напротив Анхора», «рядом с Ц13».
Всё содержание этого кейса — как вынуть географию из прозы.
Ташкент называет свои места как хочет, и это надо принять как данность.
Один и тот же объект в объявлениях — это Ипак Йули, Ipak Yo'li, Максима Горького, Горького и просто Максимка. Пять имён, две письменности, разные эпохи: улицу переименовали, а город продолжает говорить по-старому. Ни одно из этих написаний не выводится из другого никаким правилом.
Поэтому справочник ориентиров ведётся руками: 22 ориентира и 65 имён к ним. Это не «парсер ключевых слов» — это выверенный словарь, где у каждого места записаны все его настоящие названия.
Семь апострофов
Отдельная мелочь, которая ломает всё, если её не заметить.
В узбекской латинице o' пишут семью разными знаками: правой одинарной кавычкой, модификаторной перевёрнутой запятой, модификаторным апострофом, обратным штрихом, акутом, левой кавычкой и обычным ASCII-апострофом. Часть — от разных клавиатур, часть — от автозамены в Word, через который объявление прошло по дороге.
Для человека это один и тот же символ. Для сравнения строк — семь разных слов. Все семь приводятся к одному до всякого поиска.
Обычный парсер цепляется за разметку: этот div, тот класс. Дизайнер площадки двигает блок — парсер молча начинает собирать мусор.
Мы не разбираем вёрстку вообще. OLX кладёт в страницу серверное состояние — готовые данные объявления одним объектом, из которого страница потом и рисуется. Мы берём этот объект целиком и читаем как данные.
Следствие простое: перерисовка страницы наш сбор не трогает. Сломать его может только смена самой структуры данных, а это у площадки происходит несравнимо реже, чем правка оформления.
Наивная проверка «нас забанили» — поискать в ответе слово captcha. На OLX она даёт ложную тревогу на каждом запросе: слово captcha есть в скрипте самой честной страницы, в настройках капчи, которую площадка держит наготове для форм.
Различие оказалось не в словах, а в размере. Нормальная страница OLX весит около 1,8 МБ. Страница блокировки — меньше 30 КБ. Поэтому маркеры блокировки ищутся только в коротких ответах, а длинные пропускаются без проверки.
Это тот случай, когда правильный признак вообще не там, где его ищут по умолчанию.
Тихое удушение
Хуже явного бана — когда площадка не отказывает, а просто начинает отдавать меньше. Кода ошибки нет, страница приходит, объявлений в ней вдруг три вместо пятидесяти. Обход при этом «успешно завершается», и в базе тихо появляется дыра.
Признак: три короткие выдачи подряд. Одна — бывает, район и правда маленький. Три подряд — нас режут, и обход надо останавливать, а не дописывать в базу неполный результат.
Промо-объявления ломают обход «по уже виденному»
Инкрементальный обход устроен очевидно: идём по свежим, дошли до объявления, которое уже в базе, — значит догнали прошлый прогон, останавливаемся.
Промо-объявления это ломают. Они пришпилены наверх выдачи всегда, и на второй день обход упирается во вчерашнее промо на первой же строке и решает, что новых объявлений нет.
Решение: промо сохраняем — там тоже бывает нужное, — но стоп-сигналом не считаем. Обход останавливает только обычное объявление, которое мы уже видели.
Это не техника, это знание рынка, и без него расчёт врёт в сотни раз.
В аренде коммерческой недвижимости Ташкента:
- цена в сумах — это за квадратный метр в месяц;
- цена в у.е. — это за объект целиком в месяц.
Никакого поля «за что цена» в объявлении нет. Валюта и есть это поле. Поэтому месячный платёж считается по-разному: сумовую цену умножаем на площадь, у.е. берём как есть.
Если этого не знать, объект за 60 000 сумов за метр выглядит дешевле обеда.
Когда в тексте нашлось имя ориентира, остаётся вопрос, что оно значит. «В Ц13» и «напротив Ц13» — разные адреса, а для отрицательных зон («вот сюда не хочу») разница принципиальная.
Поэтому в трёх словах перед найденным именем ищутся предлоги соседства — напротив, возле, около, рядом, за, плюс узбекские qarshisida, yonida, yaqinida и их кириллические написания. Нашёлся такой — упоминание помечается и в определении позиции не участвует.
Лучше не знать, где объект, чем поставить его не туда.
В приложении нет встроенного клиента к языковой модели. Ни одного обращения к API в коде. И это осознанно.
Работает связка иначе. Инструмент командной строки отдаёт по одному объявлению компактный JSON: описание, найденные ориентиры с их баллами и пометкой соседства, оценочная позиция, цена с валютой, площадь, ссылки на фотографии. Формат стабильный и рассчитан на чтение моделью. Живой агент вызывает эту команду в терминале, читает выдачу, смотрит фотографии, выносит вердикт — и записывает его обратно второй командой.
Инструмент с устойчивым выводом плюс агент поверх него — это рабочая схема, а не недоделка. У неё есть прямые достоинства: вердикт выносит модель, которую можно поменять на другую, не трогая код; всё, что она видела и решила, лежит в базе; ничего не тратится на объявления, отсеянные до неё правилами.
Здесь мы говорим ровно то, что видно в базе.
Текст дал географию только для 9,6 % объявлений. Из 4 345 собранных позиция восстановилась у 418. Остальные 3 927 помечены как «нужен человек» — их разбирают руками.
Причина в самой природе нечёткого сравнения. Порог совпадения — 85 из 100. На настоящих именах оно даёт 100: лабзак, шота руставели, анхор парк, ипак йули — попадания точные и бесспорные. А на мусоре оно даёт 85, ровно порог: бизнес центр подтягивается к ориентиру Ц13, этаж площадь — к Мустакиллик, учебный центр — снова к Ц13.
И мусора количественно больше. Из 485 найденных упоминаний 290 стоят ровно на пороге, и почти все они ложные. Поднять порог — потеряются настоящие имена в склонениях. Опустить — утонем.
Это записано в самом коде: порог помечен как стартовый, требующий калибровки на живых данных, а не как решённый вопрос. Мы не считаем такую пометку слабостью. Инструмент, который честно говорит «здесь я не знаю, посмотри сам», полезнее того, который уверенно ставит офис не на ту улицу.
Задача звучала как «собрать объявления». На деле собрать их — самая простая часть.
Настоящая работа была в том, чтобы понять предмет: как этот город называет свои места, почему валюта — это единица измерения, чем тихое замедление отличается от отказа и почему «напротив» меняет смысл. Ничего из этого нет в документации площадки. Всё это выясняется, когда садишься и разбираешься.
И ровно так же честно надо признавать границу: где инструмент не знает — там работает человек, и он должен об этом знать заранее.
Оставьте номер, и мы перезвоним
Одно поле. Расскажете задачу — как удобно: звонком, в Telegram или голосовым. Сразу назовём срок и вилку цены, а на следующий день пришлём три концепции. Бесплатно.