RU | EN
📐

Методология

Как мы собираем 3 071+ вакансий из 10 источников (50 Telegram-каналов + Greenhouse / Lever / Ashby / Workable / RemoteOK / Himalayas / Remotive / WeWorkRemotely / Jobicy / Habr Career / Партнёркин / Arbihunter), склеиваем дубли, размечаем и проверяем то, что не должно было пройти автоматически.

📥 Откуда берём вакансии

Мы парсим 10 независимых источников. Самый большой — Telegram, 50 публичных каналов (из них 23 — HR-каналы CPA-индустрии). Сверху подключены четыре ATS-платформы (Greenhouse, Lever, Ashby, Workable), шесть досок удалённых вакансий (RemoteOK, We Work Remotely, Himalayas, Remotive, Jobicy, Habr Career), и два специализированных CPA-агрегатора (Партнёркин, Arbihunter). Никакого «партнёрства» с ними у нас нет — это публичные источники, которые мы парсим раз в час.

Telegram (основа). Список каналов собирался руками с 2018 года. Мы наблюдаем за CPA-сообществом давно и каждый новый канал добавляем только после ручного отсева. Простое правило: за 30 дней должно выйти 5+ настоящих вакансий с конкретной компанией, ролью и контактами. Если канал на 80% состоит из репостов курсов, рекламы криптокошельков или объявлений «куплю акк ФБ» — мимо. Текущий список HR-каналов выглядит так:

@adhunt_cpa_job, @aff_hunter, @aff_job, @arbitrage_work, @arbitrazh_vakansii_rabota, @be01team, @cpa_traffic_hr, @gamblingservices, @headshotagency, @hr_affiliate, @hr_arbitraz, @job4aff, @kashjob, @mediabuyers_lenkep, @opento_igaming, @partnerkin_job, @pro_vacancy, @r2bwork, @recruitingtc, @talents, @traff_job, @works_affiliate, @works_cpa

Плюс ещё около 30 каналов в категориях arbitrage, marketing, IT, general и гео-релокация (digital_jobster, dnative_job, hrcpa, hrcpagram, job_cpa, getitrussia, gjobs, dubai_work24 и т.д.). AI-классификатор читает каждое сообщение и решает «вакансия или нет» — это позволяет терпеть более шумные источники, не засоряя каталог.

ATS и job-доски. Для не-Telegram источников мы не скребём HTML — используем официальные фиды:

  • Greenhouse, Lever, Ashby, Workable — открытые ATS-эндпоинты. Следим за списком affiliate / iGaming / крипто / нутра работодателей и забираем новые позиции автоматически.
  • RemoteOK, We Work Remotely, Himalayas, Remotive, Jobicy — доски удалёнки. Фильтруем по нашей нише (affiliate-маркетинг, growth, performance, paid acquisition, крипта, iGaming).
  • Habr Career — русскоязычный tech-наём; оставляем только affiliate / арбитраж / SEO / growth роли.
  • Партнёркин, Arbihunter — профильные CPA-агрегаторы вакансий.

Фильтр affiliate-ниши. ATS-источники по умолчанию содержат массу непрофильных вакансий (инженеры, юристы, дизайнеры без affiliate-контекста). Каждое входящее объявление проходит через нишевый фильтр — whitelist из 40+ паттернов на RU/EN плюс blacklist. В последний прогон отсеяли ~8 000 вакансий, не имеющих отношения к нашей аудитории, и оставили ~1 700 реально интересных арбитражникам.

Двусторонний перевод RU↔EN. Telegram-каналы публикуют в основном на русском, ATS-платформы — в основном на английском. Мы переводим каждую вакансию на второй язык через LLM (тот же вызов одновременно вытаскивает структурированные поля), чтобы русскоязычная и /en/ версии каталога покрывали один и тот же набор.

Наша исходящая сеть. После модерации каждая свежая вакансия отправляется ещё и в нашу собственную сеть из 13 городских Telegram-каналов (Москва, Питер, Дубай, Лимассол, Тбилиси, Алматы, Ереван, Минск, Белград, Стамбул, Ташкент, Берлин и глобальная лента). География роли решает, в какой канал уйдёт пост — это уже публикация, не парсинг, но так замыкается цикл: источник → каталог → городской канал. На каждой карточке вакансии указан первоисточник — какой канал или платформа её опубликовали первыми и когда.

🔧 Как устроен парсер

Сидеть с одного аккаунта на 50 каналах — путь к бану. Telegram считает это подозрительной активностью. Вместо этого мы используем пул из 15 аккаунтов через библиотеку Telethon (Python). Они работают по очереди (round-robin): один забирает посты с пяти каналов, второй с других пяти, и так далее. ATS и job-доски обрабатывают отдельные воркеры — у каждой платформы свой скрейпер, который ходит в её публичный фид (HTML или JSON) и Telegram не трогает вообще.

Каждые 60 минут парсер обходит ленту, забирает новые посты, прогоняет их через регулярки и записывает в таблицу careers.jobs. Если канал ничего нового не выложил — пропускаем. Если выложил спам или объявление о продаже акка — отсеиваем ещё на этапе фильтра. Для ATS-источников вдобавок работает нишевый фильтр affiliate (whitelist + blacklist), чтобы backend-инженер Anthropic никогда не попал к нам в каталог.

Парсер не публикует сам, не отвечает в личные сообщения, не лайкает посты. Только чтение публичных каналов и публичных ATS-эндпоинтов — то же самое, что делает любой подписчик, открывая утром Telegram или careers-страницу компании.

🔄 Дедупликация — почему одна вакансия не появляется 14 раз

CPA-агентства любят рассылать вакансию веером: своим в @hrcpa, потом в @aff_job, потом в @works_cpa, потом ещё в трёх HR-чатах для подстраховки. Один и тот же текст всплывает буквально 5–15 раз. Добавь к этому, что многие работодатели одновременно вывешивают ту же позицию на Greenhouse, Lever или RemoteOK — и одна «Media Buyer FB Gambling» может иметь десяток точек публикации между Telegram и ATS. На сайте это смотрелось бы как мусор.

Мы считаем canonical hash от нормализованного содержимого: убираем эмодзи, нижний регистр, пробелы и пунктуацию, оставляем только осмысленные слова заголовка, компании и описания. Если хеш совпадает с уже существующей записью — это дубль, увеличиваем у неё source_count и добавляем источник в список «где ещё опубликовано». Кросс-источниковая склейка работает так же — Telegram-пост и запись из Greenhouse от одной и той же компании сливаются в одну карточку.

Реальный пример с прошлой недели. Канал @aff_job выложил «Ищем Media Buyer FB Gambling, бюджет от $50K, удалёнка». Канал @works_cpa выкладывает буква-в-букву тот же текст с другим эмодзи в заголовке. Канал @hrcpa добавляет «UPD: горящая, нужен сегодня». Хеш-функция убирает эмодзи и UPD-приписку, видит идентичный остаток — склеивает в одну запись с source_count=3.

Сейчас в каталоге около 3 100 архивных дублей, которые так и не показались отдельными карточками благодаря дедупу. Без него лента вакансий была бы примерно в 2–3 раза длиннее и состояла в основном из повторов.

🏷 Классификация — что мы извлекаем из текста

У сырого поста длина варьируется от 200 знаков («Нужен байер, +$») до 5000 («Полное описание роли, KPI, оффер, перформанс-кварталки, наша история на 2 экрана»). Чтобы фильтр работал, мы вытаскиваем структуру:

  • Вертикаль — iGaming, Crypto, Nutra, Finance, E-commerce, Agency. Хранится массивом в vertical_tags: одна вакансия может покрывать iGaming + Crypto одновременно.
  • Роль — Affiliate Manager, Media Buyer, BizDev, Developer, Designer, HR Recruiter, Sales. В role_tags.
  • Сениорити — junior / middle / senior / lead. Один уровень.
  • Формат — удалёнка / офис / гибрид.
  • Локация — нормализуем «Кипр, Лимассол» и «Limassol Cyprus» в одну запись.
  • Зарплата — пытаемся вытащить вилку, валюту и период (час/день/месяц/год). Если в тексте «$3000–5000 net» — фиксируем 3000–5000 USD/month.
  • Скиллы — список инструментов из external_data->skills (FB Ads, Keitaro, Bemob, Voluum, Binom, и т.д.).

Всё это извлекается двумя путями. Первый — regex-парсер: дёшево, быстро, ошибается редко на стандартных конструкциях («ЗП от 3000$» → 3000 USD). Второй — fallback на LLM, когда regex выдаёт низкую уверенность или вообще ничего не нашёл (плотный текст без структуры, длинный prose).

После извлечения каждая вакансия получает quality_score от 0 до 100. Чем больше полей заполнено (зарплата, локация, контакты, описание свыше 200 знаков, нормализованная компания), тем выше скор. На главной мы поднимаем выше те, у кого quality_score ≥ 70.

🤖 AI-модерация — где останавливается regex и подключается Claude

Regex отлично справляется с понятным форматом. Не справляется с замаскированным спамом, скрытым промо курсов и резюме-постами от соискателей. Поэтому всё, что у парсера в зоне неуверенности, едет в очередь к LLM (мы используем Claude Sonnet, провайдер Anthropic).

Промпт строгий: модель должна вернуть JSON с одним из трёх решений — publish, reject, review. Плюс короткое обоснование (для логов и ручного аудита). Никаких креативных свободных ответов.

Кейс из марта. Канал @aff_job 12 марта выложил пост: «Ищем тимлида в команду по сливу казино, ЗП $25K, удалёнка, без опыта». Парсер дал низкую уверенность — нет компании, нет конкретики, нет адекватных требований к опыту. Claude Sonnet отдал reject с обоснованием: «формулировка „без опыта на $25K без описания обязанностей" — типичный паттерн вытягивания контактов либо отмыва базы кандидатов; ставка не соответствует рынку для junior-уровня». Решение применилось автоматически, вакансия не попала в публичную ленту.

За март LLM-модерация отбросила примерно 7% постов из тех, что прошли regex-фильтр. Среди них: переодетые рекламы курсов («курс по медиабаингу за $99, после прохождения трудоустроим»), попытки собрать резюме («пришли свой опыт нам в личку, мы перешлём работодателю»), и явные пирамидки.

Решения с уверенностью ≥75% (publish) или ≥80% (reject) применяются автоматически. Всё ниже — в ручную модерацию. Ручная очередь смотрится раз в день, обычно там 5–15 кейсов.

⏰ Жизненный цикл — почему вакансии исчезают через 30 дней

Каждая вакансия живёт 30 дней с последнего появления в источнике. Не появилась — закрываем автоматически, переводим в status=expired. Старая страница остаётся доступной по прямой ссылке, но получает noindex,follow и пропадает из поиска.

Этот срок выбран не от потолка. Мы смотрели типовое время жизни вакансии в iGaming/CPA: компании держат активный поиск в среднем 21–28 дней, потом либо находят кого-то, либо пересматривают требования. 30 дней — комфортный буфер, чтобы не закрыть живую позицию слишком рано.

Реальный пример. Вакансия «Media Buyer FB Gambling CTEAM» опубликована 1 февраля. За следующие 30 дней её переопубликовали в наших источниках несколько раз — значит компания всё ещё ищет. Мы держим её в active с увеличенным source_count. Если бы 30 дней подряд ни один источник её не упомянул — она бы сама ушла в архив.

Сейчас в базе: 3 071 активных вакансий и около 6 072 архивных за всю историю с конца 2024 года. Архив остаётся для ретроспективы — посмотреть, какие компании что искали, какие зарплаты были в индустрии. Но в основной ленте показываем только живое.

⚖️ Что мы НЕ делаем

У job-каталогов есть много способов заработать поверх трафика. Мы все эти способы отвергаем:

  • Не берём комиссию с работодателей за публикацию или приоритет в выдаче. Все вакансии равны, ранжирование строго по quality_score и свежести.
  • Не берём процент с найма. Мы не HR-агентство, мы каталог. Никаких «$2K за placement» с компании.
  • Не берём оплату с кандидатов. Регистрация, отклики, любая активность — бесплатна. Никогда не введём pay-wall за просмотр контактов.
  • Не публикуем «серые» вакансии — фарм аккаунтов, мульты, обнал, дроп-схемы — даже если такая вакансия есть в исходном канале.
  • Не показываем Google AdSense, баннерную рекламу третьих сторон в карточках вакансий, не подмешиваем туда нативку.
  • Не делаем спам-рассылки кандидатам в Telegram. Если у нас есть твой контакт — он только для уведомлений по подпискам, на которые ты сам подписался.
  • Не передаём контакты компаний третьим сторонам. Всё, что есть в публичной карточке, и так публично в источнике.

Это не моральная позиция, а технический выбор: добавить любую из этих штук — каталог за месяц превратится в очередной мусорный сайт с накрученными зарплатами и фейковыми компаниями. Есть множество примеров, как это бывает.

👥 Кто за этим стоит

Affiliate.Careers — часть независимого проекта сообщества арбитражников. Никаких внешних инвесторов, никаких рекламных контрактов с конкретными CPA-сетями.

Команда работает по ролям: разработка платформы (Laravel + Postgres + Telethon-парсер), модерация спорных кейсов, ведение списка каналов-источников, поддержка пользователей. Всё небольшое, не корпоративное — это сознательно. Чем меньше компания, тем меньше соблазнов продавать «приоритетные размещения» или принимать «партнёрские предложения» от сомнительных рекрутеров.

Контакт для коррекций, удаления вакансий, жалоб на спам — внизу страницы.

🛠 Если работодатель просит удалить вакансию

Бывает: компания закрыла позицию, не хочет светиться, передумала. Алгоритм у нас такой:

  1. Проверяем, что обращение исходит от человека, имеющего отношение к компании. Самый простой способ — связь через тот же Telegram-канал, откуда мы взяли вакансию (если канал @hrcpa просит удалить — значит это они её и публиковали). Альтернатива — письмо с email-адреса домена компании.
  2. Если связь подтверждена — удаляем вручную в течение 24 часов. Полностью, не «скрываем», а удаляем запись из careers.jobs и страница начинает отдавать 410 Gone.
  3. Если связь не подтверждается — отвечаем, какие именно подтверждения нужны. Не удаляем по обращениям из левых аккаунтов — иначе схема превращается в «у конкурента нашлась вакансия — пишу, чтобы убрали».

Удалённая вакансия не возвращается даже если её заново опубликуют в источниках. У записи фиксируется removed_by_request=true, и парсер игнорирует canonical_hash на повторном появлении.

📩 Нашли проблему?

Кривое описание, опечатка, ушедшая вакансия которая всё ещё активна, или вообще «это не вакансия, это спам»? Напиши на страницу контактов — мы реагируем в течение часа в рабочее время.