Kufar.by — это примерно как avito.ru, только в Беларуси. После очередного “улучшения” там стало невозможно выбирать авто и недвижимость: цены показываются только в белорусских рублях, хотя рынок всегда будет в долларах (боже, храни Америку). Поэтому я сделал небольшой Chrome Extension, который добавляет рядом ориентировочную цену в долларах. Пока только для авто и недвиги. И да, по ощущениям, ЛПРы, которые это выкатывали, никогда не покупали ни то ни другое на своём сайте.
Пролог
Попытка #1
Цена в долларах. Верните обратно! Невозможно ориентироваться.
Первоначальное сообщение с претензией (которое отправлял в форме на сайте) не цитируется в почте, только ответ. Что уже странно, но “это база”. Получил вот такие волшебные ответы:
Ответ #1
По белорусскому законодательству расчёты и ценообразование внутри страны должны производиться в национальной валюте, то есть в белорусских рублях.
Раньше продавцы часто указывали цены в долларах, так как рынок недвижимости исторически был привязан к иностранной валюте. Чтобы не вводить покупателей в заблуждение и избежать путаницы (например, когда цена написана в долларах, а подразумевается в белорусских рублях), мы приняли решение изменить отображение цен на объекты недвижимости, приведя все объявления к единому стандарту.
Данное решение также основано на новых требованиях законодательства, касающихся оформления договоров с агентствами недвижимости, где цены фиксируются исключительно в белорусских рублях.
При этом мы стремимся максимально сохранить удобство для наших пользователей: возможность подачи объявления и фильтрацию предложений по валюте мы оставляем. Это позволяет вам по-прежнему работать с привычными ориентирами и находить наиболее подходящие варианты.
Если остались вопросы, то, пожалуйста, спрашивайте. Буду рада помочь.
Попытка #2
Вы не думаете о своих пользователях. Сижу с калькулятором и пересчитываю цену каждого объявления. Кто это придумал… С другой стороны, у конкурентов есть возможность извлечь свою пользу. Может это и к лучшему.
Ответ #2
Андрей, мы понимаем, что эти нововведения могли доставить неудобства, при этом следует учитывать, что данное решение также основано на требованиях законодательства.
Мы стремимся максимально сохранить удобство для наших пользователей: возможность подачи объявления и фильтрацию предложений по валюте мы оставляем. Также технические специалисты занимаются вопросом добавления валютного калькулятора.
Благодарим вас за обратную связь и что решили с нами поделиться мнением ☺️
Попытка #3
realt.by & onliner.by указывают “ориентировочную цену” в долларах, а вам можно пожелать удачи.
Ответ #3
А нет ответа.
Хватит это терпеть
Но зачем спорить, когда можно сделать себе требуемый интерфейс поверх текущего — #тыжеинженер. Нужен небольшой Chrome Extension, который открывает страницы Куфара, находит цены в белорусских рублях и рядом показывает приблизительную цену в долларах.
Без аккаунтов, без серверной части, без прокси. Просто контент-скрипт и курс НБРБ.
Исходники лежат тут: comerc/try-kufar.
Что получилось
Расширение работает на двух доменах (авто и недвига):
{ "content_scripts": [ { "matches": [ "https://auto.kufar.by/*", "https://re.kufar.by/*" ], "js": ["src/content.js"], "css": ["src/content.css"], "run_at": "document_idle" } ]}
Идея простая: на странице есть цены вида 68 683 р. или 256 873 р.. Скрипт превращает их в:
68 683 р. <span class="kufar-usd-price">≈ 25 165 $</span>
Выглядит как маленький зелёный бейдж. Не замена цены, а подсказка рядом.
Курс доллара
Курс берётся из официального API Нацбанка:
const RATE_URL = "https://api.nbrb.by/exrates/rates/USD?parammode=2";
Запрос делает не content script, а background service worker. Так меньше сюрпризов с CORS и проще хранить кеш.
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { if (!message || message.type !== "kufar:getUsdRate") { return false; } getUsdRate() .then((rateInfo) => sendResponse({ ok: true, rateInfo })) .catch((error) => sendResponse({ ok: false, error: error.message })); return true;});
Кеш на 6 часов:
const CACHE_TTL_MS = 6 * 60 * 60 * 1000;
Если API временно не отвечает, берётся последнее сохранённое значение. Для такой задачи этого более чем достаточно: это не трейдинг, это “понять порядок цены”.
Как ищем цены на странице
У Куфара много разных блоков. Листинг, страница объявления, попап на карте, “Похожие объявления”, “Ранее вы смотрели”, автостраницы. Классы тоже не подарок: CSS modules, хеши, разные компоненты. (Бардак в танковых войсках).
Поэтому привязываться к одному классу нельзя. Основной поиск идёт по текстовым узлам:
const PRICE_RE = /(^|[^\d])(\d[\d\s\u00a0.,]*?)\s*(р\.|руб\.?|BYN)(?=$|[^\wа-яё])/i;
Дальше TreeWalker проходит по тексту страницы:
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode(node) { return isPriceTextNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; }});
Но этого мало. Иногда цена разбита по вложенным span, иногда лежит в блоке с class*="price". Поэтому есть второй проход:
const candidates = root.querySelectorAll([ "[class*='price' i]", "[data-testid*='price' i]", "[aria-label*='цен' i]", "[aria-label*='price' i]"].join(","));
Это не идеально академически, зато устойчиво к реальной вёрстке.
Динамический DOM
Куфар дорисовывает части страницы после загрузки. Например, попап на карте появляется не сразу. Поэтому обычного запуска один раз недостаточно.
Решение стандартное:
const observer = new MutationObserver(() => { clearTimeout(scanTimer); scanTimer = window.setTimeout(() => scanPrices(), 250);});observer.observe(document.body, { childList: true, subtree: true, characterData: true});
Каждое изменение DOM не сканируется мгновенно. Есть debounce на 250 мс, чтобы не устроить странице маленький стресс-тест.
Конвертация
Сама математика смешная:
badge.textContent = `≈ ${formatUsd.format(byn / rateInfo.rate)}`;
Форматирование через Intl.NumberFormat:
const formatUsd = new Intl.NumberFormat("ru-RU", { style: "currency", currency: "USD", maximumFractionDigits: 0});
То есть 87 914 р. превращается примерно в ≈ 32 212 $.
Несколько неприятных мелочей
Самое интересное было не в курсе и не в регулярке. Самое интересное было в вёрстке.
Например, похожие объявления внизу страницы имеют overflow: hidden и text-overflow: ellipsis. Бейдж добавлялся, но для длинных цен просто обрезался. Пришлось помечать строку цены своим классом и разрешать перенос:
p.kufar-usd-price-row { display: flex !important; flex-wrap: wrap; gap: 2px 0 !important; overflow: visible !important; text-overflow: clip !important; white-space: normal !important;}
Для аренды есть отдельная история: 604.41 р. / мес.. После добавления доллара суффикс / мес. переставал влезать. Его пришлось вынести в отдельный span и принудительно отправить на новую строку:
.kufar-usd-rent-suffix { display: inline-flex; flex-basis: 100% !important;}
А ещё на автостранице есть кнопка “Оформить в лизинг от … р. /м.”. Это не цена объявления, поэтому такие блоки пропускаются:
const SKIP_CONTEXT_RE = /лизинг|\/\s*м\./i;
В итоге расширение конвертирует основную цену авто, но не лезет в платежи по лизингу.
Установка из исходников
Пока расширение не в Chrome Web Store, ставится как unpacked extension.
git clone https://github.com/comerc/try-kufar.gitcd try-kufar
Дальше в Chrome:
-
открыть
chrome://extensions; -
включить
Developer mode; -
нажать
Load unpacked; -
выбрать папку
try-kufar; -
открыть страницу Куфара.
Если меняли код локально, надо нажать reload у расширения на странице chrome://extensions, а потом перезагрузить вкладку с Куфаром. Это важно: content scripts и CSS не всегда обновляются просто по F5.
Итог
Получился маленький пользовательский слой поверх сайта. Куфар показывает цены в белорусских рублях, как ему хочется или как ему надо. Расширение рядом показывает привычный ориентир в долларах.
Никакой магии. Просто немного DOM, MutationObserver, API Нацбанка и терпение к чужой вёрстке. Codex решил проблему за пару часов!
ссылка на оригинал статьи https://habr.com/ru/articles/1037122/