import logging from re import findall import requests from services.normalization import normalize_headline from structs.headline import Headline logger = logging.getLogger(__name__) DEFAULT_TIMEOUT = 10 MIN_HEADLINE_WORDS = 3 def is_headline(text, stopwords=None): if not text or not isinstance(text, str): return False cleaned_text = text.strip() if not cleaned_text: return False words = cleaned_text.split() if len(words) < MIN_HEADLINE_WORDS: logger.debug("Text rejected as headline (fewer than %d words): %r", MIN_HEADLINE_WORDS, cleaned_text) return False if not any(c.isalnum() for c in cleaned_text): logger.debug("Text rejected as headline (no alphanumeric characters): %r", cleaned_text) return False if stopwords is not None: normalized = normalize_headline(cleaned_text, stopwords) if not normalized: logger.debug("Text rejected as headline (no meaningful tokens after stopword removal): %r", cleaned_text) return False return True def prepare_headlines(sources, stopwords, timeout=DEFAULT_TIMEOUT): logger.info("Starting preparation of headlines for %d sources", len(sources) if sources else 0) headlines = [] if not sources: logger.warning("No sources provided to prepare_headlines.") return headlines for idx, source in enumerate(sources, start=1): if not source or not source.strip(): logger.warning("Skipping empty source at index %d", idx) continue source_url = source.strip() logger.info("Fetching source [%d/%d]: '%s'", idx, len(sources), source_url) try: response = requests.get(source_url, allow_redirects=True, timeout=timeout, headers={'User-Agent': 'Anya news bot'}) logger.debug("Received HTTP response %d for '%s' (content length: %d bytes)", response.status_code, source_url, len(response.content)) if response.status_code != 200: logger.warning("Source '%s' returned non-200 status code: %d", source_url, response.status_code) source_content = response.text except requests.exceptions.Timeout as e: logger.error("Request timed out for source '%s': %s", source_url, e, exc_info=True) continue except requests.exceptions.RequestException as e: logger.error("HTTP request failed for source '%s': %s", source_url, e, exc_info=True) continue except Exception as e: logger.error("Unexpected error fetching source '%s': %s", source_url, e, exc_info=True) continue logger.debug("Parsing HTML content from '%s' for headline candidates", source_url) try: link_texts = findall(r'<(?:a|span)\b[^>]*>\s*([^<]*?)\s*', source_content) logger.info("Found %d candidate tags in source '%s'", len(link_texts), source_url) except Exception as e: logger.error("Regex extraction failed on content from '%s': %s", source_url, e, exc_info=True) continue source_headlines_count = 0 for tag_idx, link_text in enumerate(link_texts, start=1): cleaned_text = link_text.strip() if not cleaned_text: logger.debug("Skipping empty tag text at position %d from '%s'", tag_idx, source_url) continue if not is_headline(cleaned_text, stopwords): logger.debug("Skipping non-headline tag text at position %d from '%s': %r", tag_idx, source_url, cleaned_text) continue logger.debug("Processing tag [%d/%d] from '%s': %r", tag_idx, len(link_texts), source_url, cleaned_text) try: normalized_headline = normalize_headline(cleaned_text, stopwords) headline = Headline(cleaned_text, normalized_headline) headlines.append(headline) source_headlines_count += 1 except Exception as e: logger.error("Failed to normalize/create headline for text %r from '%s': %s", cleaned_text, source_url, e, exc_info=True) logger.info("Successfully extracted %d headlines from source '%s'", source_headlines_count, source_url) logger.info("Finished preparing headlines. Total headlines collected across all sources: %d", len(headlines)) return headlines