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?

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.
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.

Ú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:
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:
- Medián (Median) használata átlag helyett: A medián érzéketlen a kiugró értékekre (breakout adatokra).
- 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:
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:
- 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).
- 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.
- Sürgős riasztás (Slack / Email):
{{ $json.warningDetected }}Equalstrue- Ü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!"
- Üzenet: "Figyelem! Az elmúlt 3 napban (
- Megerősített büntetés riasztás:
{{ $json.penaltyDetected }}Equalstrue- Ü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.
[
{
"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
}
}
]

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.

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.


