feat(tags): zdejmij znaczniki jakosci + przytnij ogon tagow rule34video
Znaczniki jakosci (hd, 4k, 720p, HD Videos, wzorzec ^\d{3,4}p$) trafiaja na stoplist
i znikaja z katalogu: 323 379 przypisan, 16 tagow. Powod: apka ma osobny filtr jakosci
liczony ze ZRODEL ODTWARZANIA, ktory jest wiarygodny, a tag jest deklaracja tuba i bywa
falszywy. Dwa rownolegle sygnaly o tym samym, z ktorych jeden klamie, sa gorsze niz jeden.
Ogon rule34video: 356 scen wnosilo 1112 roznych tagow do WSPOLNEJ listy uzywanej przez
caly katalog, z czego 602 wystepowaly dokladnie raz (nazwy postaci i serii, tagowanie
w stylu booru). Nowy job co 24h zostawia tagi z >=3 scen i najwyzej 15 na scene,
uporzadkowane po czestosci w obrebie serwisu.
Wazny szczegol reguly, wychwycony dopiero w symulacji: ochrona tagow wspolnych dotyczy
TYLKO progu, nie limitu. Pierwsza wersja wylaczala tagi wspolne rowniez z limitu i przez
to prawie nie dzialala, rekord spadal 161 -> 147 zamiast do 15, bo najbardziej obsypana
scena skladala sie glownie z tagow wspolnych.
Wynik na produkcji: srednia na scenie rule34video 13,5 -> 9,1, rekord 161 -> 15, zadna
scena nie zostala bez tagow. Usuniete 1554 przypisania i 2475 osieroconych tagow.
VACUUM ANALYZE po obu operacjach.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
38a2724963
commit
8c8f22d388
5 changed files with 175 additions and 7 deletions
|
|
@ -150,6 +150,11 @@ class Settings(BaseSettings):
|
|||
sched_junk_performers_hours: int = Field(
|
||||
default=24, validation_alias="GOON_SCHED_JUNK_PERFORMERS_HOURS"
|
||||
)
|
||||
# Przycinanie ogona tagow rule34video (tagowanie booru: nazwy postaci/serii).
|
||||
# 356 scen wnosilo 1112 tagow do WSPOLNEJ listy, 602 z nich uzyte raz. 24h. 0 = off.
|
||||
sched_tag_tail_trim_hours: int = Field(
|
||||
default=24, validation_alias="GOON_SCHED_TAG_TAIL_TRIM_HOURS"
|
||||
)
|
||||
# Ingest freshness watchdog, alert do Sentry gdy aktywny tube (origin
|
||||
# tube:<sitetag>) przestał dawać nowe sceny > próg. Łapie zamrożenie
|
||||
# pojedynczego origin, którego globalny monitor (jeden Source "tube-scraper") nie
|
||||
|
|
|
|||
|
|
@ -15,15 +15,18 @@ Kryterium wpisu: tag nie zawezaja wyszukiwania, bo pasuje do niemal wszystkiego
|
|||
w katalogu ("porn", "sex", "hot"), albo opisuje sposob dystrybucji, a nie tresc
|
||||
("premium", "exclusive", "leaked", "download").
|
||||
|
||||
Swiadomie NIE na liscie:
|
||||
- `onlyfans` (71 972 przypisan) - to realny typ tresci, ludzie tego szukaja.
|
||||
- `4k`, `hd`, `720p`, `1080p` (ok. 110 tys.) - niosa jakas informacje, choc
|
||||
niepewna. Apka ma osobny, wiarygodny filtr jakosci liczony ze zrodel odtwarzania,
|
||||
wiec te tagi sa zbedne, ale usuniecie ich to decyzja produktowa, nie sprzatanie
|
||||
smieci. Do przegadania osobno.
|
||||
Znaczniki jakosci (`hd`, `4k`, `720p`, `HD Videos`) tez sa na liscie, decyzja z 2026-08-14.
|
||||
Powod: apka ma osobny filtr jakosci liczony ze ZRODEL ODTWARZANIA, ktory jest wiarygodny,
|
||||
a tag jest deklaracja tuba i bywa falszywy. Dwa rownolegle sygnaly o tym samym, z ktorych
|
||||
jeden klamie, sa gorsze niz jeden. Samo `HD Videos` mialo 205 488 przypisan.
|
||||
|
||||
Swiadomie NIE na liscie: `onlyfans` (71 972 przypisan). To realny typ tresci, nie chwyt
|
||||
marketingowy, ludzie tego szukaja.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
#: Porownanie po slugu (lowercase, dywizy), bo ta sama rzecz przychodzi jako
|
||||
#: "Full HD", "full-hd" i "FullHD".
|
||||
JUNK_TAG_SLUGS: frozenset[str] = frozenset(
|
||||
|
|
@ -65,10 +68,30 @@ JUNK_TAG_SLUGS: frozenset[str] = frozenset(
|
|||
"streaming",
|
||||
"online",
|
||||
"full-video",
|
||||
# znaczniki jakosci, patrz docstring: dubluja filtr liczony ze zrodel i klamia
|
||||
"hd",
|
||||
"hd-porn",
|
||||
"hd-videos",
|
||||
"sd",
|
||||
"fhd",
|
||||
"uhd",
|
||||
"full-hd",
|
||||
"fullhd",
|
||||
"ultra-hd",
|
||||
"high-definition",
|
||||
"hi-res",
|
||||
"4k",
|
||||
"8k",
|
||||
"4k-porn",
|
||||
}
|
||||
)
|
||||
|
||||
#: Rozdzielczosc jako tag: 240p, 480p, 720p, 1080p, 2160p... Wzorzec zamiast wyliczanki,
|
||||
#: bo tuby wymyslaja kolejne warianty przy kazdym re-scrape.
|
||||
_RESOLUTION_RE = re.compile(r"^\d{3,4}p$")
|
||||
|
||||
|
||||
def is_junk_tag(slug: str) -> bool:
|
||||
"""True dla tagu, ktorego nie chcemy trzymac w katalogu."""
|
||||
return slug.strip().lower() in JUNK_TAG_SLUGS
|
||||
s = slug.strip().lower()
|
||||
return s in JUNK_TAG_SLUGS or bool(_RESOLUTION_RE.match(s))
|
||||
|
|
|
|||
|
|
@ -308,6 +308,21 @@ def _job_phash_blacklist() -> None:
|
|||
log.exception("[scheduler] phash blacklist failed")
|
||||
|
||||
|
||||
def _job_tag_tail_trim() -> None:
|
||||
"""Przycina ogon tagow rule34video (booru: nazwy postaci, serii, autorow).
|
||||
356 scen wnosilo 1112 roznych tagow do wspolnej listy, 602 uzyte dokladnie raz.
|
||||
Zostawia tagi z >=3 scen i najwyzej 15 na scene; tagi uzywane takze poza
|
||||
rule34video sa chronione, bo naleza do wspolnego slownika."""
|
||||
log.info("[scheduler] tag tail trim starting")
|
||||
try:
|
||||
from app.scheduler.tag_tail_trim import run
|
||||
|
||||
stats = run()
|
||||
log.info("[scheduler] tag tail trim done: %s", stats)
|
||||
except Exception:
|
||||
log.exception("[scheduler] tag tail trim failed")
|
||||
|
||||
|
||||
def _job_junk_performers() -> None:
|
||||
"""Kasuje kategorie i studia podszywające się pod performerów („Creampie",
|
||||
„Natural tits", „Brazzers"). Audyt 2026-07-27: 277 rekordów, 33 784 przypisania.
|
||||
|
|
@ -550,6 +565,17 @@ def build_scheduler(cfg: dict[str, Any]) -> BlockingScheduler:
|
|||
cfg["phash_backfill_minutes"], _batch,
|
||||
)
|
||||
|
||||
if cfg.get("tag_tail_trim_hours"):
|
||||
sched.add_job(
|
||||
_job_tag_tail_trim,
|
||||
IntervalTrigger(hours=cfg["tag_tail_trim_hours"], start_date=INTERVAL_ANCHOR),
|
||||
id="tag_tail_trim",
|
||||
replace_existing=True,
|
||||
max_instances=1,
|
||||
coalesce=True,
|
||||
)
|
||||
log.info("scheduler: tag tail trim every %dh", cfg["tag_tail_trim_hours"])
|
||||
|
||||
if cfg.get("junk_performers_hours"):
|
||||
sched.add_job(
|
||||
_job_junk_performers,
|
||||
|
|
|
|||
112
app/scheduler/tag_tail_trim.py
Normal file
112
app/scheduler/tag_tail_trim.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
"""Przycinanie ogona tagow z rule34video (tagowanie w stylu booru).
|
||||
|
||||
Skad problem: rule34video taguje jak booru, czyli nazwami postaci, serii i autorow.
|
||||
Zmierzone 2026-08-14: **356 scen ma 1112 roznych tagow**, srednio 13,5 na scene, rekord
|
||||
161 (zgloszenie uzytkownika o scenie "TEMPTATION - Episode 14"). Z tych 1112 tagow
|
||||
**602 wystepuja dokladnie raz**, a 763 najwyzej dwa razy. To sa nazwy wlasne.
|
||||
|
||||
Same w sobie nie sa bledne, ludzie ich szukaja, ale 356 scen wrzuca ponad tysiac pozycji
|
||||
do WSPOLNEJ listy tagow, ktorej uzywa caly katalog (1,9 mln scen). Stad przyciecie,
|
||||
a nie skasowanie zrodla.
|
||||
|
||||
Dwie reguly, obie potrzebne (zmierzone osobno):
|
||||
1. **Prog czestosci**: tag musi wystepowac na >=3 scenach rule34video. Sam prog zbija
|
||||
srednia 13,5 -> 10,9 i rekord 161 -> 104, wiec NIE wystarcza.
|
||||
2. **Limit na scene**: najwyzej 15 tagow, zostawiamy najczestsze w obrebie serwisu.
|
||||
Dopiero to domyka rekord.
|
||||
|
||||
Ochrona tagow wspolnych dotyczy TYLKO progu, nie limitu, i to jest istotne. Tag uzywany
|
||||
takze poza rule34video (np. "Bondage") nalezy do wspolnego slownika, wiec nie kasujemy go
|
||||
z powodu niskiej czestosci w obrebie serwisu. Ale limit na scene obejmuje go normalnie:
|
||||
scena nie powinna miec 161 tagow niezaleznie od tego, czyje one sa.
|
||||
|
||||
Pierwsza wersja tej reguly wylaczala tagi wspolne takze z limitu i przez to prawie nie
|
||||
dzialala: rekord spadal 161 -> 147 zamiast do 15, bo najbardziej obsypana scena skladala
|
||||
sie glownie z tagow wspolnych.
|
||||
|
||||
Uwaga o zbieznosci: limit na scene obniza liczniki tagow, wiec kolejny przebieg moze
|
||||
zejsc nieco nizej, zanim sie ustabilizuje. Liczniki bierzemy ze SNAPSHOTU sprzed zmian
|
||||
w danym przebiegu, zeby jeden przebieg nie kaskadowal sam na sobie.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from sqlalchemy import text
|
||||
|
||||
from app.db import session_scope
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
ORIGIN = "tube:rule34video"
|
||||
MIN_SCENES_PER_TAG = 3
|
||||
MAX_TAGS_PER_SCENE = 15
|
||||
|
||||
_SQL = text(
|
||||
"""
|
||||
WITH r34 AS (
|
||||
SELECT DISTINCT p.scene_id AS id
|
||||
FROM playback_sources p
|
||||
WHERE p.origin = :origin
|
||||
),
|
||||
-- snapshot licznikow SPRZED zmian w tym przebiegu
|
||||
freq AS (
|
||||
SELECT st.tag_id, count(*) AS c
|
||||
FROM scene_tags st JOIN r34 ON r34.id = st.scene_id
|
||||
GROUP BY st.tag_id
|
||||
),
|
||||
-- tag uzywany takze poza rule34video nalezy do wspolnego slownika, nie ruszamy
|
||||
shared AS (
|
||||
SELECT DISTINCT st.tag_id
|
||||
FROM scene_tags st
|
||||
WHERE st.tag_id IN (SELECT tag_id FROM freq)
|
||||
AND st.scene_id NOT IN (SELECT id FROM r34)
|
||||
),
|
||||
ranked AS (
|
||||
SELECT st.scene_id, st.tag_id, f.c AS cnt,
|
||||
(st.tag_id IN (SELECT tag_id FROM shared)) AS is_shared,
|
||||
row_number() OVER (PARTITION BY st.scene_id ORDER BY f.c DESC, st.tag_id) AS rn
|
||||
FROM scene_tags st
|
||||
JOIN r34 ON r34.id = st.scene_id
|
||||
JOIN freq f ON f.tag_id = st.tag_id
|
||||
),
|
||||
doomed AS (
|
||||
SELECT scene_id, tag_id FROM ranked
|
||||
-- limit obowiazuje wszystkie tagi; prog tylko te wylacznie rule34video
|
||||
WHERE rn > :max_tags OR (cnt < :min_scenes AND NOT is_shared)
|
||||
)
|
||||
DELETE FROM scene_tags st
|
||||
USING doomed d
|
||||
WHERE st.scene_id = d.scene_id AND st.tag_id = d.tag_id
|
||||
"""
|
||||
)
|
||||
|
||||
# Tagi, ktore po przycieciu nie wisza juz nigdzie. Zostawione zasmiecalyby liste tagow
|
||||
# mimo zerowej liczby scen.
|
||||
_SQL_ORPHANS = text(
|
||||
"""
|
||||
DELETE FROM tags t
|
||||
WHERE NOT EXISTS (SELECT 1 FROM scene_tags st WHERE st.tag_id = t.id)
|
||||
AND NOT EXISTS (SELECT 1 FROM movie_tags mt WHERE mt.tag_id = t.id)
|
||||
AND NOT EXISTS (SELECT 1 FROM blacklisted_tags bt WHERE bt.tag_id = t.id)
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def run() -> dict[str, int]:
|
||||
"""Przytnij ogon i skasuj osierocone tagi. Zwraca licznik zmian."""
|
||||
with session_scope() as session:
|
||||
removed = session.execute(
|
||||
_SQL,
|
||||
{
|
||||
"origin": ORIGIN,
|
||||
"min_scenes": MIN_SCENES_PER_TAG,
|
||||
"max_tags": MAX_TAGS_PER_SCENE,
|
||||
},
|
||||
).rowcount
|
||||
orphans = session.execute(_SQL_ORPHANS).rowcount
|
||||
session.commit()
|
||||
log.info(
|
||||
"tag_tail_trim: usunieto %s przypisan, %s osieroconych tagow", removed, orphans
|
||||
)
|
||||
return {"removed": removed or 0, "orphans": orphans or 0}
|
||||
|
|
@ -224,6 +224,8 @@ def run_forever() -> int:
|
|||
# Śmieciowi performerzy, kategorie/studia udające osoby (audyt 2026-07-27).
|
||||
# Kryterium wąskie celowo: szerokie kasuje realnych ludzi bez refa w TPDB.
|
||||
"junk_performers_hours": getattr(settings, "sched_junk_performers_hours", 24) or None,
|
||||
# Ogon tagow rule34video (nazwy wlasne w stylu booru zalewajace liste tagow).
|
||||
"tag_tail_trim_hours": getattr(settings, "sched_tag_tail_trim_hours", 24) or None,
|
||||
# Taxonomy scene_count refresh, denormalizacja liczników (perf fix 0019).
|
||||
"taxonomy_counts_hours": getattr(settings, "sched_taxonomy_counts_hours", 3) or None,
|
||||
# Ingest freshness watchdog, per-origin alert do Sentry (report 14f3a655).
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue