feat(search): Microdata-Fallback erkennt rezeptwelt & Co.
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m15s

Aus dem Log (q="Königsberger klopse"): 11 rezeptwelt-Treffer kamen durch
alle URL-Filter, wurden aber von hasRecipeJsonLd als non-recipe gedroppt.
Ursache: rezeptwelt.de nutzt Microdata (itemtype=schema.org/Recipe) statt
application/ld+json.

- hasRecipeJsonLd → hasRecipeMarkup: prüft jetzt zusätzlich per Regex
  auf itemtype=(https?://)schema.org/Recipe. Alter Export bleibt als
  Deprecated-Weiterleitung erhalten.
- Log zeigt jetzt auch die ersten 3 gedropten URLs als dropped samples,
  damit neue Problem-Domains einfach zu diagnostizieren sind.
- Migration 010 räumt alle thumbnail_cache-Einträge mit has_recipe=0 aus
  — die waren mit dem alten Check falsch-negativ und müssen neu
  klassifiziert werden.

Tests: 4 neue Cases für hasRecipeMarkup (JSON-LD, http/https Microdata,
Negativ-Fall).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-18 08:32:18 +02:00
parent 15c15c8494
commit 2e196b4834
4 changed files with 63 additions and 5 deletions

View File

@@ -2,7 +2,7 @@ import type Database from 'better-sqlite3';
import { parseHTML } from 'linkedom';
import { listDomains, normalizeDomain } from '../domains/repository';
import { fetchText } from '../http';
import { hasRecipeJsonLd } from '../parsers/json-ld-recipe';
import { hasRecipeMarkup } from '../parsers/json-ld-recipe';
export type WebHit = {
url: string;
@@ -235,7 +235,7 @@ async function enrichPageMeta(
});
meta = {
image: extractPageImage(html, url),
hasRecipe: hasRecipeJsonLd(html) ? 1 : 0
hasRecipe: hasRecipeMarkup(html) ? 1 : 0
};
} catch {
// Fetch failed — leave hasRecipe null (unknown) so we don't permanently
@@ -363,9 +363,19 @@ export async function searchWeb(
);
if (opts.enrichThumbnails !== false) {
const enriched = await enrichAndFilterHits(db, hits);
const droppedUrls = hits
.filter((h) => !enriched.find((e) => e.url === h.url))
.map((h) => h.url);
console.log(
`[searxng] q=${JSON.stringify(trimmed)} pageno=${pageno} enrich=${hits.length} dropped_non_recipe=${hits.length - enriched.length} final=${enriched.length}`
`[searxng] q=${JSON.stringify(trimmed)} pageno=${pageno} enrich=${hits.length} dropped_non_recipe=${droppedUrls.length} final=${enriched.length}`
);
// Nur die ersten 3 URLs mitloggen, damit das Log nicht explodiert. Genug
// um eine Seite manuell zu analysieren („warum wurde die abgelehnt?").
if (droppedUrls.length > 0) {
console.log(
`[searxng] dropped samples: ${droppedUrls.slice(0, 3).join(' | ')}`
);
}
return enriched;
}
return hits;