Stop spamowi w leadach bez zabijania konwersji: lekki pipeline ML do scoringu formularzy

Stop spamowi w leadach bez zabijania konwersji: lekki pipeline ML do scoringu formularzy

Formularze leadowe są jak publiczne API: każdy może do nich „strzelić”, a Ty płacisz za każdy request – czasem dosłownie (CRM, automatyzacje, call center), a czasem reputacją (śmieci w bazie, zły obraz kampanii, fałszywe wnioski). W WordPressie/Elementorze problem jest szczególnie widoczny, bo formularze często są proste, szeroko dostępne i mocno zintegrowane z narzędziami marketingowymi.

Najczęściej zaczyna się od „to tylko trochę spamu”. Potem dochodzi koszt: handlowcy przestają ufać leadom, kampanie wyglądają „świetnie” w raportach, a realnie konwersja spada. W końcu ktoś próbuje łatki: CAPTCHA, blokady IP, ręczne reguły, czarne listy. I zwykle kończy się to w tym samym miejscu: albo spam dalej przechodzi, albo cierpią prawdziwi użytkownicy.

U nas przełomem było potraktowanie formularzy jak systemu z sygnałami i ryzykiem: nie „blokuj/nie blokuj”, tylko oceń prawdopodobieństwo spamu i podejmij decyzję zależnie od kosztu błędu. To jest bardzo wdzięczny obszar dla „lekkiego ML”: nie potrzebujesz LLM-ów ani wielkiej infrastruktury. Potrzebujesz czystego eventu, sensownych cech, prostego modelu i monitoringu driftu.

Tło i problem

Spam w leadach nie jest jednolity. W praktyce widzimy mieszankę:

  • automatycznych zgłoszeń masowych,
  • „semi-automatycznych” botów, które wyglądają jak człowiek,
  • zgłoszeń o niskiej jakości (np. przypadkowe dane), które biznesowo są równie kosztowne jak spam.

Największy błąd, jaki popełnia wiele zespołów, to wrzucenie wszystkiego do jednego worka „spam”. Skutek: wdrażasz jedną blokadę (CAPTCHA) i potem eskalujesz poziom „trudności”, gdy boty ją obchodzą. To spirala, w której przegrywa UX.

W realnym biznesie koszt błędu jest asymetryczny:

  • false positive (zablokowałeś prawdziwy lead) boli natychmiast i bezpośrednio,
  • false negative (przepuściłeś spam) boli pośrednio, ale kumuluje się w czasie (koszt obsługi, zaśmiecone dane, błędne decyzje).

Dlatego sensowny system nie powinien być „zero-jedynkowy”. Powinien umieć:

  • ocenić ryzyko,
  • dobrać reakcję do kontekstu (kampania, źródło, formularz),
  • zostawić ślad audytowy.

Wymagania / ograniczenia

Wymagania funkcjonalne

  • Ograniczyć spam docierający do CRM i automatyzacji.
  • Nie obniżyć konwersji (szczególnie na mobile).
  • Działać dla wielu formularzy i landingów bez ręcznego strojenia każdego z osobna.
  • Umożliwić obsługę wyjątków (kampanie, partnerzy, ruch B2B z NAT).

Wymagania techniczne

  • Niska latencja: scoring nie może wydłużać submitu w sposób zauważalny.
  • Niski koszt utrzymania: prosty model, proste feature engineering.
  • Przewidywalność i audyt: wiedzieć „dlaczego” lead trafił do danej ścieżki.
  • Monitoring driftu: boty się zmieniają, model też musi.

Ograniczenia i bezpieczeństwo

  • Minimalizacja danych (privacy): nie zbieramy więcej, niż trzeba; wrażliwe dane trzymamy krótko lub haszujemy.
  • Nie publikujemy „poradnika omijania zabezpieczeń”: opisujemy podejście obronne bez zdradzania szczegółów, które ułatwiają nadużycia.

Co nie działało wcześniej

1) CAPTCHA jako domyślne rozwiązanie

CAPTCHA bywa skuteczna na część botów, ale:

  • kosztuje konwersję (zwłaszcza mobile),
  • bywa problematyczna dla dostępności,
  • nie rozróżnia „niskiej jakości” leadów od spamu,
  • często przesuwa problem, zamiast go rozwiązać.

2) Ręczne reguły („if email contains…”, „if country == …”)

Reguły są kuszące, bo są szybkie. Ale:

  • szybko rosną w liczbę i konflikty,
  • trudno je utrzymać i testować,
  • są łatwe do obejścia przez adaptujące się nadużycia,
  • działają globalnie, choć ryzyko jest lokalne (konkretne kampanie/formularze).

3) Blokowanie po IP / geolokacji

To często generuje false positives (firmowe NAT, VPN, podróże), a spam i tak znajduje obejścia. To narzędzie może być warstwą, ale nie jedyną.

4) Brak „prawdy” w danych

Największa praktyczna przeszkoda w ML to etykiety. Jeśli jedyną etykietą jest „to wyglądało na spam”, to model będzie uczył się opinii, nie rzeczywistości. Potrzebujesz sygnału biznesowego: czy lead był realny, czy doszło do kontaktu, czy lead został odrzucony jako spam.

Nowe podejście / architektura

Zamiast jednego „muru” zrobiliśmy pipeline:

  1. Event z formularza (WordPress/Elementor) → ustandaryzowany kontrakt lead.submitted.
  2. Feature extraction (część na froncie, część na backendzie) → cechy behawioralne i kontekstowe.
  3. Scoring service → zwraca spam_score + kilka flag (np. „needs_challenge”).
  4. Decision engine → mapuje score na akcję:
    • przepuść,
    • przepuść do „quarantine”,
    • poproś o dodatkową weryfikację / challenge (tylko gdy ryzyko wysokie).
  5. Feedback loop → etykiety z CRM/ops (odrzucony, skontaktowany, sprzedaż) wracają do treningu.
  6. Monitoring → drift cech, jakość danych, metryki biznesowe.

To jest „ML jako mała usługa”, a nie wielki projekt data science. Kluczowe jest to, że ML nie jest jedyną warstwą – jest warstwą decyzyjną nad sygnałami.

Jak to działa

Kontrakt danych: event „lead.submitted”

Pierwszy krok to uporządkowanie tego, co w ogóle zbieramy. Bez kontraktu każdy formularz jest inny, a każdy „fix” jest jednorazowy.

Przykładowy event (JSON) – wersjonowany, z minimalną ilością danych osobowych:

{
  "event": "lead.submitted",
  "version": "1.1",
  "timestamp": "2026-01-12T09:05:12Z",
  "request_id": "req_2c6b1f...",
  "form": {
    "id": "contact_main",
    "page": "/oferta/wordpress-opieka",
    "campaign": "q1-2026"
  },
  "lead": {
    "email": "user@example.com",
    "phone_present": true,
    "message_length": 238
  },
  "context": {
    "utm_source": "google",
    "utm_medium": "cpc",
    "referrer_host": "www.google.com",
    "locale": "pl-PL",
    "timezone_offset_min": 60
  },
  "signals": {
    "time_to_submit_ms": 18400,
    "input_changes": 23,
    "paste_events": 2,
    "has_js": true
  },
  "network": {
    "ip_hash": "sha256:...",
    "asn": 12345
  }
}

Dlaczego tak:

  • dane osobowe trzymamy minimalnie (część sygnałów jako boolean/length),
  • IP nie jest przechowywane jawnie (hash), ale nadal daje korelację,
  • mamy version, bo kontrakt będzie ewoluował.

Cechy: co mierzymy, a czego nie

Wykrywanie spamu działa najlepiej, gdy opiera się na zachowaniu i anomaliach, a nie na „treści wiadomości”. Dla leadów B2B treść bywa krótka, a email/phone to dane wrażliwe.

Cechy, które zwykle są stabilne i etyczne:

  • czas do submitu (time_to_submit_ms) – bardzo krótki bywa podejrzany, ale uwaga na autofill,
  • liczba zmian w polach (input_changes) – ekstremalne wartości też są sygnałem,
  • czy były wklejenia (paste_events) – nie jako „złe”, tylko jako element kontekstu,
  • typ formularza i landing (form.id, page) – różne strony mają różny profil ruchu,
  • źródła ruchu (utm, referrer_host) – ale ostrożnie: to nie ma dyskryminować, tylko pomagać w segmentacji ryzyka,
  • sygnały środowiska (czy działa JS, podstawowe parametry klienta) – bez fingerprintingu „na sterydach”.

Czego unikamy albo traktujemy bardzo ostrożnie:

  • pełnej treści wiadomości jako feature (privacy + ryzyko wycieku),
  • „reguł” opartych o wrażliwe atrybuty,
  • agresywnego device fingerprinting.

W praktyce wystarcza 20–60 cech. Więcej to zwykle szum i koszt utrzymania.

Lista praktycznych zasad:

  • cechy mają być tanie do policzenia,
  • cechy mają być stabilne w czasie,
  • cechy mają być audytowalne (wiesz, co znaczą),
  • każda cecha ma właściciela: kto ją utrzymuje i po co istnieje.

Model: prosty, tani, przewidywalny

Dla tego problemu świetnie działają klasyczne modele tablicowe:

  • regresja logistyczna (dobry baseline, łatwa interpretacja),
  • gradient boosting (np. XGBoost/LightGBM) – często lepsza skuteczność przy podobnym koszcie inference.

Wybór zależy od Twoich priorytetów:

  • jeśli chcesz maksymalnej przejrzystości → logistyczna,
  • jeśli chcesz lepszego ROC/PR i masz trochę więcej dojrzałości → boosting.

U nas typowo zaczynamy od logistycznej + kilka ręcznie dobranych cech. Dopiero gdy mamy porządny feedback loop, przechodzimy do boostingu. Powód jest prosty: najpierw dane i etykiety, potem model.

Decyzje: trzy ścieżki zamiast jednej blokady

Największa zmiana produktowa: nie blokujemy „w ciemno”. Robimy routing:

  1. Accept – lead idzie normalnie do CRM i automatyzacji.
  2. Quarantine – lead idzie do osobnej kolejki/tagu:
    • nie uruchamia drogich automatyzacji,
    • można go zweryfikować automatycznie (np. asynchronicznie) lub ręcznie w razie potrzeby.
  3. Challenge – dla wysokiego ryzyka:
    • minimalne tarcie (np. dodatkowy krok weryfikacji),
    • używane rzadko, bo kosztuje UX.

To podejście działa biznesowo, bo możesz dobrać strategię do kampanii:

  • w kampanii z wysoką stawką za lead możesz być bardziej konserwatywny (więcej quarantine),
  • na landingach o krytycznej konwersji możesz minimalizować challenge.

Trening i ewaluacja: metryki, progi, koszt błędów

Tu jest sedno „inżynierskiego ML”: dobierasz metrykę do kosztu.

Metryki modelowe, które mają sens:

  • precision/recall dla klasy „spam”,
  • PR-AUC (często lepsze niż ROC-AUC przy niezbalansowanych danych),
  • confusion matrix dla kilku progów.

Metryki biznesowe, które są równie ważne:

  • % leadów w quarantine,
  • % false positives (prawdziwe leady w quarantine/challenge),
  • czas reakcji handlowców (czy quarantine nie blokuje zbyt długo),
  • koszt spamu w CRM (czas, licencje, automatyzacje).

Pseudokod treningu (celowo prosty):

# 1) Wczytaj eventy + etykiety z CRM/ops
events = load_events("lead.submitted", since_days=90)
labels = load_labels(since_days=90)  # np. spam/rejected, contacted, qualified

dataset = join(events, labels, on="request_id")

# 2) Feature engineering (stabilne, tanie cechy)
X = featurize(dataset)
y = dataset["is_spam"]  # 1 spam, 0 legit

# 3) Split czasowy (ważne przy drift)
train, valid = time_split(X, y, cutoff="2025-12-15")

# 4) Model baseline
model = LogisticRegression(class_weight="balanced")
model.fit(train.X, train.y)

# 5) Ewaluacja + dobór progu pod koszt
scores = model.predict_proba(valid.X)[:, 1]
threshold = choose_threshold(scores, valid.y, target_fp_rate=0.01)

save_model(model, threshold, version="2026.01.0")

Dlaczego split czasowy: boty i ruch się zmieniają, a losowy split bywa zbyt optymistyczny.

Dobór progu: zamiast „0.5”, dobierasz pod akceptowalny false positive rate albo pod maksymalizację wartości (jeśli potrafisz policzyć koszt).


Deployment i monitoring: drift, jakość danych, alerty

Deployment zwykle robimy jako mały serwis:

  • endpoint /score-lead przyjmuje event (lub referencję),
  • zwraca score, decision, model_version.

Przykładowe API:

POST /score-lead
{
  "request_id": "req_2c6b1f...",
  "features": {
    "time_to_submit_ms": 18400,
    "input_changes": 23,
    "paste_events": 2,
    "has_js": true,
    "form_id": "contact_main",
    "utm_source": "google"
  }
}

Odpowiedź:

{
  "spam_score": 0.82,
  "decision": "quarantine",
  "model_version": "2026.01.0",
  "reasons": ["very_fast_submit", "anomalous_pattern"] 
}

reasons to nie „wyjaśnienie matematyczne”, tylko audytowe flagi, które pomagają debugować i rozmawiać z biznesem.

Monitoring dzielimy na trzy warstwy:

  1. Jakość danych (data quality)
  • % eventów bez kluczowych pól,
  • nagłe zmiany rozkładów (np. time_to_submit_ms spada o połowę),
  • wzrost „unknown” w kategoriach (nowe utm_source).
  1. Drift
  • PSI/KS na wybranych cechach (nie trzeba na wszystkim),
  • porównanie rozkładu score w czasie,
  • trend precision/recall na leadach, które dostały etykiety.
  1. Biznes
  • trend quarantine rate,
  • trend „spam w CRM” (np. ręczne oznaczenia),
  • wpływ na konwersję (tam, gdzie umiesz to mierzyć).

Alerty, które realnie mają sens:

  • quarantine rate rośnie skokowo (np. +50% dzień do dnia),
  • spada liczba leadów „qualified” mimo podobnego ruchu (sygnał false positives),
  • data quality: brakuje sygnałów z frontu (np. padł JS snippet).

Wyniki / metryki / lekcje

Bez dostępu do Twoich danych nie będę udawał konkretnych liczb jako faktów. Ale realistycznie, w podobnych wdrożeniach, często da się osiągnąć (szacunek, zależny od ruchu i źródeł):

  • redukcję spamu trafiającego do CRM o 60–90%,
  • utrzymanie false positives na poziomie <1–2% (gdy progi dobierzesz konserwatywnie),
  • mniejsze koszty operacyjne: mniej „ręcznego czyszczenia”, mniej automatyzacji odpalanych na śmieciach.

Najważniejsze lekcje:

  • Etykiety są ważniejsze niż model. Jeśli nie masz feedback loop (spam/legit), to ML będzie zgadywał.
  • Routing > blokada. Quarantine to genialny kompromis: chroni system, nie zabija UX.
  • Prosty model + dobry monitoring wygrywa z „mocnym modelem bez kontroli”.
  • Drift jest pewny. Boty i kampanie się zmieniają – mierz to od dnia 1.

Konkretne wnioski w punktach:

  • Zacznij od 10–20 cech i modelu baseline.
  • Zadbaj o wersjonowanie eventu i modelu.
  • Dobierz próg pod koszt false positives, nie pod „intuicję”.
  • Monitoruj data quality, zanim zaczniesz monitorować „AI”.

Co dalej / roadmapa

Gdy fundament działa, typowa roadmapa wygląda tak:

  1. Lepsze etykiety i szybszy feedback
    • integracja z CRM: statusy leadów,
    • proste narzędzie dla handlowców: „oznacz jako spam” (1 klik),
    • automatyczna walidacja e-mail/telefon w tle (bez tarcia w submit).
  2. Model per segment
    • osobne progi per formularz/kampania,
    • ewentualnie osobne modele, jeśli profile ruchu są skrajnie różne.
  3. Active learning / human-in-the-loop
    • ręczna weryfikacja tylko „granicznych” przypadków,
    • wykorzystanie tych danych do poprawy modelu.
  4. Ochrona na brzegu jako warstwa komplementarna
    • lepszy rate limiting dla źródeł, które generują nadużycia,
    • ale kontrolowane, żeby nie blokować dobrego ruchu.
  5. Privacy i retencja
    • przegląd pól, które zbierasz,
    • skrócenie retencji sygnałów, które nie są potrzebne do uczenia,
    • audyt dostępu do danych.

Wnioski

Spam w formularzach to problem produktowo-operacyjny, nie tylko „antybotowy”. Jeśli potraktujesz go jak system decyzyjny, lekkie ML daje świetny zwrot: mniej śmieci w CRM, mniej kosztów i mniej frustracji – bez wrzucania każdemu użytkownikowi CAPTCHA na wejściu.

Jeśli chcesz to wdrożyć u siebie, zacznij od:

  1. ustandaryzowania eventu lead.submitted,
  2. wprowadzenia „quarantine” jako trzeciej ścieżki,
  3. prostego modelu + monitoringu driftu i jakości danych.

Jeśli chcesz, mogę też pomóc ułożyć minimalny kontrakt eventu pod Twoje formularze i listę cech, które są „warte” zbierania przy zachowaniu prywatności.

Na ile ten artykuł był dla Ciebie pomocny?

Powiązane wpisy