SEO

Hogyan készítettem egy algo penalty checkert?

A cikket Papp GáborPapp Gábor írta.

Hogyan tudod ellenőrizni, hogy egy oldal algo penaltyt kapott? Mire lehet használni még egy ilyen eszközt?

  • 2026. október 10.
  • 6 perc olvasás

Foglald össze ezt a cikket

Kattints a kedvenc AI eszközödre a gyors összefoglalóért:

Aki követi a szakmát, annak feltűnhetett, hogy a Google az utóbbi időben sokkal gyorsabban ad ki Spam Updateket és ezzel együtt a "penalty"-ket mint korábban. Google terminilógiában a penalty kizárólag a kézi műveletekre (Manual Action) vonatkozik. Az algoritmus-frissítések miatti visszaeséseket hivatalosan leértékelésként kéne kezelni (algorithmic demotion / re-evaluation) vagy hátrasorolásnak. De maradjunk a penalty-nél most az egyszerűség kedvéért.

Spam Updatek

2026-ban 9 hónap alatt annyi algo update ment már ki, mint előtte 2-3 év alatt összesen. Ennek nyilván a végtelen mennyiségű AI gyártott tartalom az oka. Ha az adataim nem csalnak, akkor így néznek ki a dedikált Spam Updatek az elmúlt években.

  • 2021: 3 frissítés (júniusi 1. és 2. rész, november)
  • 2022: 2 frissítés (október, decemberi Link Spam)
  • 2023: 1 frissítés (október)
  • 2024: 3 frissítés (március, június, december)
  • 2025: 1 frissítés (augusztus)
  • 2026: 4 frissítés (március, június, augusztus, szeptember)

Ha nem követed ilyen mélyen a szakmát, akkor ezzel az infoval most már Te is tisztában vagy. Szuper, mehetünk is tovább.

A múlt héten ránéztem egy tesztelős, homokozós weboldalamra és meglepődve tapasztaltam, hogy bizony 2026. júniusban kapott egy "büntetést". Nem de-facto kézi penaltyről van szó, hanem egy masszív hátrasorolásról, de maradjunk annyiban, hogy ez egy bünti, ahogy fent is írtam.

És ekkor gondolkoztam el azon, hogy ezt én 3 hónapig észre sem vettem. Oké, olyan domainről van szó, amit 1.5-2 éve raktam össze és azóta nem foglalkoztam vele, rá se néztem. De hát mégis milyen égő már, hogy észre sem vettem az egészet. Ezen változtatni kell.

A lenti grafikonon az oldal GSC adatait látod. Látszik, hogy a 2025. augusztusi Spam Update kedvezett neki, akkor megugrott a forgalom. Majd a 2026. júniusi update visszadobta masszívan, kvázi rárakta a 0 tengelyre az impressionöket és kattintásokat is. És az azóta bejelentett 2026. augusztusi és szeptemberi update sem oldotta fel a büntetést.

Felmerült a kérdés: hogy tudtam volna ezt hamarabb észrevenni?

algo penalty

A penalty checker alapjai

Volt 1 órám mielőtt indult a vonatom vidékre, mondom gyorsan vibe kódolok valamit. Ezt a promptot dobtam be a Gemininek.

egy api hívást intézek a google search consolehoz

https://www.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($json.Domain) }}/searchAnalytics/query

- segíts készíteni egy olyan json body-t ami leszedi az egyes napok impression és click adatait

- majd megvizsgálja hogy történt-e jelentős visszaesés nagyon rövid idő alatt

- a cél az lenne, hogy azonosítsam kapott-e algo penaltyt az oldal

- ha igen, akkor add vissza a dátumot amikor jelentősen visszaesett a forgalom

ezt n8n-ben akarom megépíteni. milyen lépések kellenek hozzá?

Előre tudtam, hogy a prompt sok helyen hibás, pontosabban nem elég pontos. Milyen időtávról van szó? Nincs definiálva mit jelent a penalty! Úgyhogy nem is számítottam arra, hogy elsőre tökéletes lesz, de pont ez volt a cél. Dobjon valamit, amíg elmegyek bepakolni. Ez lett az első kód.

Kód
const rows = $input.first().json.rows || [];

if (rows.length === 0) {
  return [{ json: { penaltyDetected: false, message: "Nincs elérhető adat a megadott időszakra." } }];
}

// Dátum szerinti sorrendbe rendezés (legrégebbitől a legfrissebbig)
rows.sort((a, b) => new Date(a.keys[0]) - new Date(b.keys[0]));

let penaltyDetected = false;
let dropDetails = null;

// Küszöbérték: 50%-os vagy annál nagyobb hirtelen esés
const DROP_THRESHOLD = 0.50; 

for (let i = 7; i <= rows.length - 3; i++) {
  // Előző 7 nap átlagos kattintásszáma (bázis)
  const prev7Days = rows.slice(i - 7, i);
  const avgPrevClicks = prev7Days.reduce((sum, r) => sum + r.clicks, 0) / 7;
  
  // A vizsgált 3 napos ablak átlaga
  const current3Days = rows.slice(i, i + 3);
  const avgCurrentClicks = current3Days.reduce((sum, r) => sum + r.clicks, 0) / 3;

  // Minimális forgalmi küszöb a téves riasztások kiszűrésére (pl. napi átlag min. 10 kattintás)
  if (avgPrevClicks > 10) {
    const dropRatio = (avgPrevClicks - avgCurrentClicks) / avgPrevClicks;
    
    if (dropRatio >= DROP_THRESHOLD) {
      penaltyDetected = true;
      dropDetails = {
        penaltyDetected: true,
        dropStartDate: rows[i].keys[0],
        baselineAvgClicks: Math.round(avgPrevClicks),
        droppedAvgClicks: Math.round(avgCurrentClicks),
        dropPercentage: Math.round(dropRatio * 100),
        impressionsBefore: Math.round(prev7Days.reduce((sum, r) => sum + r.impressions, 0) / 7),
        impressionsAfter: Math.round(current3Days.reduce((sum, r) => sum + r.impressions, 0) / 3)
      };
      break; // Az első jelentős esési pont rögzítése
    }
  }
}

return [{ json: dropDetails || { penaltyDetected: false, message: "Nem észlelhető jelentős hirtelen visszaesés." } }];

Mi történt az első kód futtatása után:

  • több téves riasztást is kaptam, mert vannak outbreak értékek a grafikonon, és ezért a soron következő napokat is jelentős, akár 60-70%-os visszaesésnek vette.
  • Ezt a lenti grafikonon a kérdőjeles részeknél látni. Ezek téves riasztások voltak, amiket vizuálisan azonnal látsz, de a gyatra kezdő prompt miatt nem jött rá a rendszer.
valós algo penalty

Úgyhogy kértem a rendszert, módosítsuk a kódot.

módosítsd úgy a kódot, hogy a dropot valamilyen átlagban nézd (2-3 napos csúszó átlag, vagy heti átlag) mert 1-1 nap lehet olyan ingadozás ami false positive dropot azonosít

Mozgó átlag bevezetése

A Gemini azt javasolta elsőre, hogy a téves riasztások (false positive-ok) kiszűrésére a legjobb módszer a 7 napos gördülő heti átlag használata egy 14 napos bázisátlaggal szemben. Mert ez teljesen kiküszöböli a hétvégi/munkanapi szezonalitást (pl. ha hétvégén természetesen leesik a forgalom), valamint az 1-2 napos kiugrásokat és ünnepeket. Ok, mehet, csinálhatja. Visszajött a következő kód:

Kód
const rows = $input.first().json.rows || [];

if (rows.length === 0) {
  return [{ json: { penaltyDetected: false, message: "Nincs elérhető adat a megadott időszakra." } }];
}

// Dátum szerinti növekvő sorrend (legrégebbitől a legfrissebbig)
rows.sort((a, b) => new Date(a.keys[0]) - new Date(b.keys[0]));

// BEÁLLÍTÁSOK
const BASELINE_DAYS = 14;     // Bázis időszak hossza (14 napos simított átlag)
const EVALUATION_DAYS = 7;    // Vizsgált időszak (7 napos heti átlag - kiszűri a heti szezonalitást)
const DROP_THRESHOLD = 0.40;  // 40%-os heti átlagos visszaesési küszöb (heti szinten a 40% már drasztikus)
const MIN_DAILY_CLICKS = 10;  // Minimális átlagos napi kattintás a bázisban (zajszűrés)

const dropsFound = [];

for (let i = BASELINE_DAYS; i <= rows.length - EVALUATION_DAYS; i++) {
  // 1. Előző 14 nap bázisátlaga (kattintás és megjelenés)
  const baselineWindow = rows.slice(i - BASELINE_DAYS, i);
  const avgBaselineClicks = baselineWindow.reduce((sum, r) => sum + r.clicks, 0) / BASELINE_DAYS;
  const avgBaselineImpressions = baselineWindow.reduce((sum, r) => sum + r.impressions, 0) / BASELINE_DAYS;

  // 2. A vizsgált 7 napos gördülő ablak átlaga
  const evalWindow = rows.slice(i, i + EVALUATION_DAYS);
  const avgEvalClicks = evalWindow.reduce((sum, r) => sum + r.clicks, 0) / EVALUATION_DAYS;
  const avgEvalImpressions = evalWindow.reduce((sum, r) => sum + r.impressions, 0) / EVALUATION_DAYS;

  // Csak akkor vizsgáljuk, ha az oldal alapforgalma eléri a minimumot
  if (avgBaselineClicks >= MIN_DAILY_CLICKS) {
    const dropRatio = (avgBaselineClicks - avgEvalClicks) / avgBaselineClicks;

    if (dropRatio >= DROP_THRESHOLD) {
      dropsFound.push({
        dropStartDate: rows[i].keys[0],
        dropEndDate: rows[i + EVALUATION_DAYS - 1].keys[0],
        baselineAvgClicks: Math.round(avgBaselineClicks),
        droppedAvgClicks: Math.round(avgEvalClicks),
        dropPercentage: Math.round(dropRatio * 100),
        baselineAvgImpressions: Math.round(avgBaselineImpressions),
        droppedAvgImpressions: Math.round(avgEvalImpressions)
      });

      // Léptetés a vizsgált ablak végére (+7 nap), hogy ne jelezzen újra ugyanarra a hullámra
      i += EVALUATION_DAYS - 1;
    }
  }
}

return [{
  json: {
    penaltyDetected: dropsFound.length > 0,
    totalDropsFound: dropsFound.length,
    drops: dropsFound,
    latestDrop: dropsFound.length > 0 ? dropsFound[dropsFound.length - 1] : null
  }
}];

De ez a kód is hibás volt, mert a kiugró értékek szintén eltolták a bázisátlagot. Tehát ez a típusú átlagszámítás se jó megoldás.

A grafikon alapján pontosan láthatjuk, ami a hamis riasztásokat okozza: április végén volt egy hatalmas kiugrás (spike/breakout), ami mesterségesen megemelte a bázisátlagot, így az utána következő normál forgalom már "visszaesésnek" tűnt a kód számára. Ugyanakkor június végén (kb. 06. 20-án) történt egy valódi, drasztikus és tartós összeomlás, ahol a kattintásszám szinte nullára zuhant és ott is maradt.

Átlag helyett medián

Ezért a probléma megoldására két (matematikai) védelmet építettünk be:

  1. Medián (Median) használata átlag helyett: A medián érzéketlen a kiugró értékekre (breakout adatokra).
  2. Tartóssági ellenőrzés (Sustained Drop Check): A kód a visszaesést követő legalább 14 napos időszakot vizsgálja. Ha a forgalom néhány nap után visszaugrik, azt figyelmen kívül hagyja. Ha pedig legalább 14 napig tartósan alacsony marad, az jelenti a valódi algoritmus-büntetést.

Ez lett a módosított kód:

Kód
const inputData = $input.first()?.json || {};
const rows = inputData.rows || [];

if (!Array.isArray(rows) || rows.length === 0) {
  return [{
    penaltyDetected: false,
    message: "Nincs elérhető adat a megadott időszakra."
  }];
}

// 1. Dátum szerinti rendezés (növekvő)
rows.sort((a, b) => new Date(a.keys[0]) - new Date(b.keys[0]));

// --- BEÁLLÍTÁSOK ---
const BASELINE_DAYS = 28;      // 4 hetes bázisidőszak a medián számításához (kiszűri a tüskéket)
const SUSTAINED_DAYS = 14;     // Tartósság: legalább 14 napig alacsonynak kell maradnia a forgalomnak
const DROP_THRESHOLD = 0.50;   // 50%-os tartós visszaesési küszöb
const MIN_BASELINE_CLICKS = 10;// Minimális napi medián kattintás a bázisban

// Segédfunkció: Medián számítása (teljesen ignorálja a kiugró spike-okat)
function getMedian(numbers) {
  if (numbers.length === 0) return 0;
  const sorted = [...numbers].sort((a, b) => a - b);
  const middle = Math.floor(sorted.length / 2);
  if (sorted.length % 2 === 0) {
    return (sorted[middle - 1] + sorted[middle]) / 2;
  }
  return sorted[middle];
}

const dropsFound = [];

// A ciklus végigmegy az adatsoron úgy, hogy van mögötte BASELINE, előtte SUSTAINED ablak
for (let i = BASELINE_DAYS; i <= rows.length - SUSTAINED_DAYS; i++) {

  // A) BÁZIS ÉRTÉK (Előző 28 nap MEDIÁNJA)
  const baselineRows = rows.slice(i - BASELINE_DAYS, i);
  const baselineMedianClicks = getMedian(baselineRows.map(r => r.clicks));
  const baselineMedianImpressions = getMedian(baselineRows.map(r => r.impressions));

  // B) VISSZAESÉS UTÁNI ÉRTÉK (Következő 14 nap MEDIÁNJA)
  const postDropRows = rows.slice(i, i + SUSTAINED_DAYS);
  const postDropMedianClicks = getMedian(postDropRows.map(r => r.clicks));
  const postDropMedianImpressions = getMedian(postDropRows.map(r => r.impressions));

  // Csak akkor vizsgáljuk, ha a bázisidőszakban volt érdemi forgalom
  if (baselineMedianClicks >= MIN_BASELINE_CLICKS) {
    const dropRatio = (baselineMedianClicks - postDropMedianClicks) / baselineMedianClicks;

    // C) Ellenőrzés: Eléri-e a 50%-os esést TARTÓSAN (14 napon át)?
    if (dropRatio >= DROP_THRESHOLD) {
      dropsFound.push({
        dropStartDate: rows[i].keys[0],
        dropEndDate: rows[i + SUSTAINED_DAYS - 1].keys[0],
        baselineMedianClicks: Math.round(baselineMedianClicks),
        droppedMedianClicks: Math.round(postDropMedianClicks),
        dropPercentage: Math.round(dropRatio * 100),
        baselineMedianImpressions: Math.round(baselineMedianImpressions),
        droppedMedianImpressions: Math.round(postDropMedianImpressions)
      });

      // Ugrás 14 nappal előre, hogy ugyanazt a büntetéshullámot ne jelentse többször
      i += SUSTAINED_DAYS - 1;
    }
  }
}

return [{
  penaltyDetected: dropsFound.length > 0,
  totalDropsFound: dropsFound.length,
  drops: dropsFound,
  latestDrop: dropsFound.length > 0 ? dropsFound[dropsFound.length - 1] : null
}];

Végeredmény:

  • Ignorálja az április végi kiugrást: Ha az áprilisi kiugró napon 80 kattintás volt, a 28 napos sorozatban ez az egyetlen magas érték a getMedian() rendezés során a tömb szélére kerül, így a bázisérték továbbra is a reális ~25-30 kattintás marad (nem torzítja a bázist).
  • Kizárja a rövid kiugrásokat: Ha egy technikai hiba miatt 2 napig leesik a forgalom, de a 3. napon visszatér, a rákövetkező 14 napos medián nem fog 50%-os esést mutatni, így a kód nem ad riasztást.
  • Azonosítja a június 20. körüli büntetést: Amikor június végén a napi forgalom ~15-20-ról tartósan 1-2-re zuhan le (és 14 napig ott is marad), a kód azonnal észleli a permanent esést, és pontosan visszaadja a büntetés kezdő dátumát.

Előrejelzési funkció

Elkezdtem örülni, hogy közeledek a válaszhoz. De felmerült bennem a következő dilemma.

ez a kód el tudja azt is kapni hogyha éppen folyamatban van egy visszaesés?

mondjuk ma 2026-10-10-én esik vissza a forgalom akkor hány nap után tud jelenzni a kód a csúszó átlag miatt? hisz előremenően nem lesz elég adata hogy biztosra mondja a visszaesést. ezt valahogy ki lehet szűrni hogy előrejelző indikátor is legyen benne, ne csak utólag tudjon szólni?

Ezután 2 perc alatt kinyomoztuk, hogy ez így nem lesz jó.

Mert az előző kód tényleg nem tudta azonnal elkapni a friss esést. Mivel a Search Console-nak, így az API-nak is van 2-3 napos adatcsúszása, és ha rászámoljuk a 14 napos "tartóssági ellenőrzés"-t, akkor a rendszer 16-17 napos késéssel jelzett volna csak előremenő jelleggel. Ez így tuti nem jó.

Ezért szétválasztottam a kódot.

Ahhoz ugyanis, hogy a kód előrejelző indikátorként (Early Warning System) is működjön, egy kétlépcsős detektálást kell alkalmazni:

  1. Korai figyelmeztetés (Early Warning): Az API-ból elérhető legfrissebb 3 nap átlagát hasonlítja össze a megelőző 28 nap mediánjával. Ha a legutóbbi 3 napban bezuhant a forgalom, azonnal riaszt (a GSC API által engedélyezett leggyorsabb időpontban, azaz 2-3 napos csúszással).
  2. Megerősített büntetés (Confirmed Penalty): A korábbi 14 napos tartóssági teszt, ami utólag igazolja, hogy nem csak egy 1-2 napos átmeneti kiugrásról volt szó.

Ezért a kód működést is szédszetem két külön részre.

  1. Sürgős riasztás (Slack / Email): {{ $json.warningDetected }} Equals true
    • Üzenet: "Figyelem! Az elmúlt 3 napban ({{ $json.ongoingWarning.warningStartDate }}) a forgalom {{ $json.ongoingWarning.dropPercentage }}%-kal esett vissza a korábbi átlaghoz képest. A büntetés vagy technikai hiba jelenleg is folyamatban lehet!"
  2. Megerősített büntetés riasztás: {{ $json.penaltyDetected }} Equals true
    • Üzenet: "Igazolt Algo Penalty: A forgalom visszaesése tartósnak bizonyult."

Final output

És amikor lefutott a végső kód, akkor ezt az outputot kaptam.

Kód
[
  {
    "warningDetected": false,
    "ongoingWarning": null,
    "penaltyDetected": true,
    "totalConfirmedDrops": 1,
    "confirmedDrops": [
      {
        "dropStartDate": "2026-06-20",
        "dropEndDate": "2026-07-03",
        "baselineMedianClicks": 11,
        "droppedMedianClicks": 5,
        "dropPercentage": 55
      }
    ],
    "latestConfirmedDrop": {
      "dropStartDate": "2026-06-20",
      "dropEndDate": "2026-07-03",
      "baselineMedianClicks": 11,
      "droppedMedianClicks": 5,
      "dropPercentage": 55
    }
  }
]
google penalty ellenőrző json outpu

Az egyetlen igazolt drop és penalty 2026-06-20-án volt és 2026-07-03-án validálta. Ha pedig menet közben is futott volna már a rendszer, akkor 2026-06-23/25 környékén már szólt volna előre, hogy figyelj, szerintem gond van.

Az algo penalty ellenőrzés persze csak egy felhasználási módja ennek a rendszernek. Az egyéb eséseket is tudja kezelni, amikor egy projektnél, ügyfélnél a forgalom hirtelen esni kezd. Akár egy technikai hiba, egy senki által észre nem vett noindex miatt.

Illetve arra is használható, hogy az inverzét teszteljem. Azaz mikor oldja fel a Google egy-egy domain büntetését? Illetve mikor lódul meg egy oldal nagyon a korábbi bázishoz képest? Szóval jó lenne ezeket is látni a jövőben.

A fenti kódot és prototípust tényleg 1 óra alatt tető alá tudtam hozni konkrétan kézi promptolással (agentek nélkül) és mire felszálltam a vonatra már live, productionben működik a staging szerveren. Korántsem tökéletes, már látom a hibáit, de jobb mint a semmi (Done is Better Than Perfect). Úgyhogy most jön a finomhangolás és a reszelés (időszakok, thresholdok, stat módszertan stb). Ez azért igénybe vesz majd jó pár órát még, de utána ezt tényleg be lehet vezetni az összes projekt esetében.

Csak pontosan rögzíteni kell a use-caseket, és azokat egyesével le kell kezelni. Azaz, hogy mikor mit csináljon, kinek szóljon, ha baj van és az milyen action stepeket von maga után. Szóval ezen fogok dolgozni majd a következő pár napban, a végeredményt pedig elviszem az idei DCC konf SEO workshopjaira.

Ha te is szeretnél hasonló dolgokat csinálni, megtanulni hogy működik ez az egész, akkor gyere el a DCC Extra AI automation + n8n workshopjára, amit én tartok 2026.10. 20-án délelőtt.

AI Automation n8n segítségévelHasonlóan jó, érdekes, hasznos és izgalmas dolgokról lesz szó: https://jegy.digitalcubeconf.com/napirend?nap=extra

Papp Gábor
Szakmai vezető, The Pitch

2015 óta vezetem a The Pitch szakmai munkáját, a csapattal évente több mint száz ügyfélnek segítünk. Egyik szakmai szervezője vagyok a Digital Cube Confnak, az ország legnagyobb analitika, PPC és SEO konferenciájának. A képzésen azt adom át, ami az ügyfélmunkában tényleg működik.

Kapcsolódó cikkek