javascript

Цены в долларах на Kufar.by

  • четверг, 21 мая 2026 г. в 00:00:18
https://habr.com/ru/articles/1037122/

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.git
cd try-kufar

Дальше в Chrome:

  1. открыть chrome://extensions;

  2. включить Developer mode;

  3. нажать Load unpacked;

  4. выбрать папку try-kufar;

  5. открыть страницу Куфара.

Если меняли код локально, надо нажать reload у расширения на странице chrome://extensions, а потом перезагрузить вкладку с Куфаром. Это важно: content scripts и CSS не всегда обновляются просто по F5.

Итог

Получился маленький пользовательский слой поверх сайта. Куфар показывает цены в белорусских рублях, как ему хочется или как ему надо. Расширение рядом показывает привычный ориентир в долларах.

Никакой магии. Просто немного DOM, MutationObserver, API Нацбанка и терпение к чужой вёрстке. Codex решил проблему за пару часов!

UPD Вернулась “ориентировочная” цена в долларах и евро. Mission completed.