fix(seo): bez prywatnych blacklist i z cache indeksu sitemapy
Blacklisty sa per urzadzenie od 2026-06-10, a SEO dalej wycinalo sume blacklist wszystkich urzadzen: 465 osob, 4477 tagow, z publicznych stron znikalo ~86% scen. To samo zapytanie przy liczeniu do /sitemap.xml pisalo 3,7 GB plikow tymczasowych i przy kilku wejsciach robota naraz zapychalo dysk (GOON-22). Teraz strony SEO pokazuja to, co katalog nowemu urzadzeniu, a indeks sitemapy liczy sie raz na 6 h (blokada na rownolegle zadania). Liczenie 135 MB plikow tymczasowych, indeks 13 s, potem 0,2 s. Wykonawcy 20 tys. -> 81 tys., sceny 3,5 mln. Na VPS dodatkowo temp_file_limit=2GB dla API w override. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f33f37f117
commit
7accd4692f
1 changed files with 30 additions and 43 deletions
|
|
@ -13,8 +13,11 @@ Zasady (świadome, nie przypadkowe):
|
|||
* **Age-gate = client-side overlay** (cookie `age_ok`). Treść jest w HTML, więc
|
||||
crawler ją indeksuje; overlay zasłania ją tylko ludziom do potwierdzenia 18+.
|
||||
Dodatkowo `RTA` meta tag dla filtrów rodzicielskich.
|
||||
* **Blacklist respektowany** te same wykluczenia performer/studio/tag co w /scenes.
|
||||
Nie publikujemy SEO-stron dla zblacklistowanej treści.
|
||||
* **Bez blacklist.** Blacklisty sa prywatne, per urzadzenie (od 2026-06-10), a strony
|
||||
SEO sa anonimowe, wiec pokazuja to, co katalog pokazuje nowemu urzadzeniu. Wczesniej
|
||||
wycinalismy sume blacklist WSZYSTKICH urzadzen: 465 osob ukrylo 4477 tagow i z SEO
|
||||
znikalo ~86% scen, a liczenie do /sitemap.xml pisalo 3,7 GB plikow tymczasowych
|
||||
i przy kilku naraz zapychalo dysk (Sentry GOON-22, 2026-09-22).
|
||||
* **Anti-thin-page** strona encji powstaje tylko gdy ma realną zawartość
|
||||
(performer/studio z ≥1 żywą sceną; scena z ≥1 żywym source). Pusta encja → 404,
|
||||
nie pusty doorway (Google karze masowe cienkie strony).
|
||||
|
|
@ -22,6 +25,8 @@ Zasady (świadome, nie przypadkowe):
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
|
@ -35,11 +40,6 @@ from sqlalchemy.orm import Session
|
|||
|
||||
from app.api.scenes import _needs_proxy, _wrap_image_proxy
|
||||
from app.db import get_session
|
||||
from app.models.blacklist import (
|
||||
BlacklistedPerformer,
|
||||
BlacklistedStudio,
|
||||
BlacklistedTag,
|
||||
)
|
||||
from app.models.performer import Performer, PerformerAlias
|
||||
from app.models.playback_source import PlaybackSource
|
||||
from app.models.scene import Scene, ScenePerformer, SceneTag
|
||||
|
|
@ -83,55 +83,24 @@ def _live_playback_exists():
|
|||
)
|
||||
|
||||
|
||||
def _not_blacklisted():
|
||||
"""Lista warunków WHERE wykluczających zblacklistowaną treść (performer/studio/tag).
|
||||
|
||||
Te same reguły co w GET /scenes, żeby SEO nie publikowało tego, co katalog ukrywa.
|
||||
"""
|
||||
return [
|
||||
~exists(
|
||||
select(1)
|
||||
.select_from(ScenePerformer)
|
||||
.join(
|
||||
BlacklistedPerformer,
|
||||
BlacklistedPerformer.performer_id == ScenePerformer.performer_id,
|
||||
)
|
||||
.where(ScenePerformer.scene_id == Scene.id)
|
||||
),
|
||||
~Scene.studio_id.in_(select(BlacklistedStudio.studio_id)),
|
||||
~exists(
|
||||
select(1)
|
||||
.select_from(SceneTag)
|
||||
.join(BlacklistedTag, BlacklistedTag.tag_id == SceneTag.tag_id)
|
||||
.where(SceneTag.scene_id == Scene.id)
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def _indexable_scenes():
|
||||
"""SELECT Scene żywych, nie-zblacklistowanych scen, baza pod listy i sitemap."""
|
||||
stmt = select(Scene).where(_live_playback_exists())
|
||||
for cond in _not_blacklisted():
|
||||
stmt = stmt.where(cond)
|
||||
return stmt
|
||||
"""SELECT Scene z żywym źródłem, baza pod listy i sitemap."""
|
||||
return select(Scene).where(_live_playback_exists())
|
||||
|
||||
|
||||
def _performer_indexable_exists():
|
||||
"""EXISTS: performer ma ≥1 indeksowalną (żywą, nie-blacklisted) scenę.
|
||||
"""EXISTS: performer ma ≥1 indeksowalną (żywą) scenę.
|
||||
|
||||
Trzyma sitemap performerów w zgodzie z tym, co realnie renderuje
|
||||
`performer_page`, inaczej sitemap zgłaszałby URL-e dające 404.
|
||||
"""
|
||||
sub = (
|
||||
return exists(
|
||||
select(1)
|
||||
.select_from(ScenePerformer)
|
||||
.join(Scene, Scene.id == ScenePerformer.scene_id)
|
||||
.where(ScenePerformer.performer_id == Performer.id)
|
||||
.where(_live_playback_exists())
|
||||
)
|
||||
for cond in _not_blacklisted():
|
||||
sub = sub.where(cond)
|
||||
return exists(sub)
|
||||
|
||||
|
||||
def _scene_card_rows(session: Session, scene_ids: list[uuid.UUID]) -> dict[uuid.UUID, dict]:
|
||||
|
|
@ -438,9 +407,27 @@ def _count(session: Session, stmt) -> int:
|
|||
return session.execute(select(func.count()).select_from(stmt.subquery())).scalar_one()
|
||||
|
||||
|
||||
# Indeks sitemapy liczy trzy agregaty po calym katalogu, a roboty potrafia zapytac kilka
|
||||
# razy w tej samej sekundzie (2026-09-04: 5 naraz). Liczymy raz na _SITEMAP_INDEX_TTL
|
||||
# w procesie; blokada sprawia, ze rownolegle zadania czekaja na jeden wynik.
|
||||
_SITEMAP_INDEX_TTL = 6 * 3600.0
|
||||
_sitemap_index_cache: tuple[float, str] | None = None
|
||||
_sitemap_index_lock = threading.Lock()
|
||||
|
||||
|
||||
@router.get("/sitemap.xml")
|
||||
def sitemap_index(session: Annotated[Session, Depends(get_session)]) -> Response:
|
||||
"""Sitemap index, listuje paginowane pod-sitemapy per typ encji."""
|
||||
global _sitemap_index_cache
|
||||
with _sitemap_index_lock:
|
||||
cached = _sitemap_index_cache
|
||||
if cached is None or time.monotonic() - cached[0] > _SITEMAP_INDEX_TTL:
|
||||
cached = (time.monotonic(), _build_sitemap_index(session))
|
||||
_sitemap_index_cache = cached
|
||||
return Response(cached[1], media_type="application/xml")
|
||||
|
||||
|
||||
def _build_sitemap_index(session: Session) -> str:
|
||||
n_perf = _count(
|
||||
session,
|
||||
select(Performer.id).where(_performer_indexable_exists()),
|
||||
|
|
@ -461,7 +448,7 @@ def sitemap_index(session: Annotated[Session, Depends(get_session)]) -> Response
|
|||
for p in range(pages):
|
||||
parts.append(f"<sitemap><loc>{bu}/sitemap/{kind}-{p}.xml</loc></sitemap>")
|
||||
parts.append("</sitemapindex>")
|
||||
return Response("\n".join(parts), media_type="application/xml")
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
@router.get("/sitemap/{kind}-{page}.xml")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue