# feedParsing.py
"""
RSS feed parsing module for BeyondBinary.
Provides a fallback mechanism to extract article content from RSS feeds
when direct web page fetching fails.
"""
import logging
import requests
import lxml.html
from typing import Dict, Any, Optional, List
from urllib.parse import urljoin, urlparse
logger = logging.getLogger("BeyondBinary.FeedParsing")
if not logger.handlers:
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
USER_AGENT = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 BeyondBinary/1.1"
)
def find_rss_feed_url(page_url: str) -> Optional[str]:
"""
Attempts to discover an RSS feed URL from a given page.
Checks for common paths and tags in the HTML.
"""
try:
headers = {"User-Agent": USER_AGENT}
response = requests.get(page_url, headers=headers, timeout=5.0)
if response.status_code != 200:
return None
doc = lxml.html.fromstring(response.content)
# 1. Look for
rss_links = doc.xpath('//link[@rel="alternate" and @type="application/rss+xml"]/@href')
if rss_links:
return urljoin(page_url, rss_links[0])
# 2. Look for
atom_links = doc.xpath('//link[@rel="alternate" and @type="application/atom+xml"]/@href')
if atom_links:
return urljoin(page_url, atom_links[0])
# 3. Try common feed paths
common_paths = ["/rss", "/feed", "/index.xml", "/rss.xml", "/feed.xml"]
parsed = urlparse(page_url)
base_url = f"{parsed.scheme}://{parsed.netloc}"
for path in common_paths:
test_url = urljoin(base_url, path)
try:
# Use HEAD request to check existence without downloading body
head = requests.head(test_url, headers=headers, timeout=3.0, allow_redirects=True)
if head.status_code == 200 and ("xml" in head.headers.get("Content-Type", "").lower()):
return test_url
except Exception:
continue
except Exception as e:
logger.warning("Error discovering RSS feed for %s: %s", page_url, e)
return None
def fetch_content_from_rss(article_url: str) -> Dict[str, Any]:
"""
Tries to find the RSS feed for the article's domain and extract the
content/description for that specific article.
"""
try:
# Determine the domain to look for the feed
parsed = urlparse(article_url)
base_url = f"{parsed.scheme}://{parsed.netloc}"
feed_url = find_rss_feed_url(base_url)
if not feed_url:
return {"success": False, "error": "Could not discover an RSS feed for this domain."}
logger.info("Attempting fallback fetch via RSS feed: %s", feed_url)
headers = {"User-Agent": USER_AGENT}
response = requests.get(feed_url, headers=headers, timeout=10.0)
if response.status_code != 200:
return {"success": False, "error": f"RSS feed returned HTTP {response.status_code}"}
doc = lxml.html.fromstring(response.content)
# RSS feeds usually have or tags
items = doc.xpath('//item') or doc.xpath('//entry')
for item in items:
# Find the link to the article
link = item.xpath('./link/text()') or item.xpath('./link/@href')
if link and (link[0].strip() == article_url or article_url.strip() in link[0]):
# Extract Title
title = item.xpath('./title/text()')
title_text = title[0].strip() if title else "No Title"
# Extract Content/Description
# RSS uses , Atom uses or
content = item.xpath('./description/text()') or \
item.xpath('./content/text()') or \
item.xpath('./summary/text()')
if content:
full_text = content[0].strip()
# Clean HTML tags from the RSS content if present
if "<" in full_text and ">" in full_text:
full_text = lxml.html.fromstring(full_text).text_content().strip()
return {
"success": True,
"title": title_text,
"text": full_text,
"word_count": len(full_text.split()),
"error": None
}
return {"success": False, "error": "Article not found in the RSS feed."}
except Exception as e:
logger.error("Error during RSS fallback fetch: %s", e)
return {"success": False, "error": str(e)}