import html import json import logging import re from services.dates import parse_datetime logger = logging.getLogger(__name__) # JSON-LD structured data blocks (schema.org) that most news sites emit for SEO — # and Next.js's serialized page state. In both cases the headlines (and dates) # are already present in the HTML, so no JavaScript execution is required. _JSON_LD_RE = re.compile( r"""]*?\btype=(["'])application/ld\+json\1[^>]*>(.*?)""", re.IGNORECASE | re.DOTALL, ) _NEXT_DATA_RE = re.compile( r"""]*?\bid=(["'])__NEXT_DATA__\1[^>]*>(.*?)""", re.IGNORECASE | re.DOTALL, ) _ARTICLE_TYPES = { 'newsarticle', 'article', 'report', 'analysisnewsarticle', 'opinionnewsarticle', 'reviewnewsarticle', 'blogposting', 'liveblogposting', 'backgroundnewsarticle', 'reportagenewsarticle', } _URL_KEYS = ('url', 'href', 'link', 'canonicalUrl', 'slug', 'uri') def _has_url(obj): for key in _URL_KEYS: value = obj.get(key) if isinstance(value, str) and value.strip(): return True return False def _schema_type(obj): """Last path segment of an object's ``@type``, lowercased (handles URLs).""" if not isinstance(obj, dict): return '' t = obj.get('@type') if isinstance(t, list): t = t[0] if t else None if not isinstance(t, str): return '' return t.strip().rstrip('/').rsplit('/', 1)[-1].lower() def _iter_json_scripts(content): """Yield unescaped JSON bodies of JSON-LD and Next.js SSR ``