📥 Откуда берём вакансии
27 Telegram-каналов с вакансиями для CPA-индустрии. Никакого «партнёрства» с этими каналами у нас нет — это публичные источники, которые мы парсим раз в час.
Список собирался руками с 2018 года. Мы наблюдаем за CPA-сообществом давно и каждый новый канал добавляем только после ручного отсева. Простое правило: за 30 дней должно выйти 5+ настоящих вакансий с конкретной компанией, ролью и контактами. Если канал на 80% состоит из репостов курсов, рекламы криптокошельков или объявлений «куплю акк ФБ» — мимо.
Текущий список (категория HR):
Среди них есть и официальные HR-аккаунты компаний (Parimatch Talents, LENKEP, Партнеркин), и независимые HR-агентства (CPA Hunter, Headshot, Adhunt), и агрегаторы (CPA WORK, Affiliate Work). Каждая карточка вакансии на сайте показывает первоисточник — какой канал и когда её опубликовал.
🔧 Как устроен парсер
Сидеть с одного аккаунта на 27 каналах — путь к бану. Telegram считает это подозрительной активностью. Вместо этого мы используем пул из 15 аккаунтов через библиотеку Telethon (Python). Они работают по очереди (round-robin): один забирает посты с пяти каналов, второй с других пяти, и так далее.
Каждые 60 минут парсер обходит ленту, забирает новые посты, прогоняет их через регулярки и записывает в таблицу careers.jobs. Если канал ничего нового не выложил — пропускаем. Если выложил спам или объявление о продаже акка — отсеиваем ещё на этапе фильтра.
Парсер не публикует сам, не отвечает в личные сообщения, не лайкает посты. Только чтение публичных каналов — то же самое, что делает любой подписчик, открывая свой Telegram утром.
🔄 Дедупликация — почему одна вакансия не появляется 14 раз
CPA-агентства любят рассылать вакансию веером: своим в @hrcpa, потом в @aff_job, потом в @works_cpa, потом ещё в трёх HR-чатах для подстраховки. Один и тот же текст всплывает буквально 5–15 раз. На сайте это смотрелось бы как мусор.
Мы считаем canonical hash от нормализованного содержимого: убираем эмодзи, нижний регистр, пробелы и пунктуацию, оставляем только осмысленные слова заголовка, компании и описания. Если хеш совпадает с уже существующей записью — это дубль, увеличиваем у неё source_count и добавляем источник в список «где ещё опубликовано».
Реальный пример с прошлой недели. Канал @aff_job 14 апреля написал «Ищем Media Buyer FB Gambling, бюджет от $50K, удалёнка». Канал @works_cpa 15 апреля выкладывает буква-в-букву тот же текст с другим эмодзи в заголовке. Канал @hrcpa 16 апреля добавляет «UPD: горящая, нужен сегодня». Хеш-функция убирает эмодзи и UPD-приписку, видит идентичный остаток — склеивает в одну запись с source_count=3.
Сейчас у нас в базе есть вакансия с source_count=24 (одна и та же позиция Media Buyer всплыла в 24 каналах за месяц), несколько с source_count=15. Без дедупа лента вакансий была бы примерно в 2–3 раза длиннее и состояла на 60% из повторов.
🏷 Классификация — что мы извлекаем из текста
У сырого поста длина варьируется от 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 дней её переопубликовали в наших источниках 19 раз — значит компания всё ещё ищет. Мы держим её в active с source_count=19. Если бы 30 дней подряд ни один источник её не упомянул — она бы сама ушла в архив.
Сейчас в базе: 563 активных вакансии и около 2 800 архивных за всю историю с конца 2024 года. Архив остаётся для ретроспективы — посмотреть, какие компании что искали, какие зарплаты были в индустрии. Но в основной ленте показываем только живое.
⚖️ Что мы НЕ делаем
У job-каталогов есть много способов заработать поверх трафика. Мы все эти способы отвергаем:
- Не берём комиссию с работодателей за публикацию или приоритет в выдаче. Все вакансии равны, ранжирование строго по
quality_scoreи свежести. - Не берём процент с найма. Мы не HR-агентство, мы каталог. Никаких «$2K за placement» с компании.
- Не берём оплату с кандидатов. Регистрация, отклики, любая активность — бесплатна. Никогда не введём pay-wall за просмотр контактов.
- Не публикуем «серые» вакансии — фарм аккаунтов, мульты, обнал, дроп-схемы — даже если такая вакансия есть в исходном канале.
- Не показываем Google AdSense, баннерную рекламу третьих сторон в карточках вакансий, не подмешиваем туда нативку.
- Не делаем спам-рассылки кандидатам в Telegram. Если у нас есть твой контакт — он только для уведомлений по подпискам, на которые ты сам подписался.
- Не передаём контакты компаний третьим сторонам. Всё, что есть в публичной карточке, и так публично в источнике.
Это не моральная позиция, а технический выбор: добавить любую из этих штук — каталог за месяц превратится в очередной мусорный сайт с накрученными зарплатами и фейковыми компаниями. Есть множество примеров, как это бывает.
👥 Кто за этим стоит
Affiliate.Careers — часть независимого проекта сообщества арбитражников. Никаких внешних инвесторов, никаких рекламных контрактов с конкретными CPA-сетями.
Команда работает по ролям: разработка платформы (Laravel + Postgres + Telethon-парсер), модерация спорных кейсов, ведение списка каналов-источников, поддержка пользователей. Всё небольшое, не корпоративное — это сознательно. Чем меньше компания, тем меньше соблазнов продавать «приоритетные размещения» или принимать «партнёрские предложения» от сомнительных рекрутеров.
Контакт для коррекций, удаления вакансий, жалоб на спам — внизу страницы.
🛠 Если работодатель просит удалить вакансию
Бывает: компания закрыла позицию, не хочет светиться, передумала. Алгоритм у нас такой:
- Проверяем, что обращение исходит от человека, имеющего отношение к компании. Самый простой способ — связь через тот же Telegram-канал, откуда мы взяли вакансию (если канал @hrcpa просит удалить — значит это они её и публиковали). Альтернатива — письмо с email-адреса домена компании.
- Если связь подтверждена — удаляем вручную в течение 24 часов. Полностью, не «скрываем», а удаляем запись из
careers.jobsи страница начинает отдавать 410 Gone. - Если связь не подтверждается — отвечаем, какие именно подтверждения нужны. Не удаляем по обращениям из левых аккаунтов — иначе схема превращается в «у конкурента нашлась вакансия — пишу, чтобы убрали».
Удалённая вакансия не возвращается даже если её заново опубликуют в источниках. У записи фиксируется removed_by_request=true, и парсер игнорирует canonical_hash на повторном появлении.
📩 Нашли проблему?
Кривое описание, опечатка, ушедшая вакансия которая всё ещё активна, или вообще «это не вакансия, это спам»? Напиши на страницу контактов — мы реагируем в течение часа в рабочее время.