← all conversations

News Synthesis Tool Setup

2025-01-304 turns19,289 charsgpt-4o
streamlitfeedparserasync

Summary

User is building a Streamlit app using feedparser and async to synthesize news but the final report output is not working.

Messages

from __future__ import annotations from typing import List, Dict, Tuple, Optional, Any import feedparser import asyncio import re import aiohttp from playwright.async_api import async_playwright import datetime import json import streamlit as st from streamlit_extras.colored_header import colored_header from streamlit_extras.add_vertical_space import add_vertical_space from streamlit_lottie import st_lottie import logging import hashlib import sqlite3 import requests from datetime import datetime class NewsSynthesizer: def __init__(self): self.rss_feeds = [ ('BBC News', 'http://feeds.bbci.co.uk/news/rss.xml'), ('Reuters', 'https://www.reutersagency.com/feed/?taxonomy=best-topics&post_type=best'), # Updated Reuters feed ('AP News', 'https://apnews.com/hub/ap-top-news.rss'), ('NY Times', 'https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml'), ('Al Jazeera', 'https://www.aljazeera.com/xml/rss/all.xml') ] self.user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" self.scrape_timeout = 30 self.logger = logging.getLogger(__name__) self.setup_database() def setup_database(self): """Initialize SQLite database with necessary tables""" try: with sqlite3.connect('news_synthesis.db') as conn: cursor = conn.cursor() # Create articles table cursor.execute(''' CREATE TABLE IF NOT EXISTS articles ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, source TEXT NOT NULL, url TEXT UNIQUE NOT NULL, content TEXT, published_date TEXT, hash TEXT UNIQUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') # Create reports table cursor.execute(''' CREATE TABLE IF NOT EXISTS reports ( id INTEGER PRIMARY KEY AUTOINCREMENT, headline TEXT NOT NULL, sources TEXT NOT NULL, urls TEXT NOT NULL, published_dates TEXT NOT NULL, report_content TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') conn.commit() self.logger.info("Database setup completed successfully") except sqlite3.Error as e: self.logger.error(f"Database setup error: {str(e)}") raise Exception(f"Failed to setup database: {str(e)}") async def _analyze_cluster(self, cluster: List[Dict]) -> Optional[str]: """ Analyze a cluster of related articles and generate a synthesized report. Args: cluster: List of article dictionaries containing content from different sources Returns: Optional[str]: A synthesized report or None if analysis fails """ try: # Extract titles and content titles = [article['title'] for article in cluster] contents = [article['content'] for article in cluster] # Combine all content for analysis combined_content = " ".join(contents) # Basic report structure report_sections = [] # Add overview from titles report_sections.append("## Overview\n") report_sections.append(f"This story is being reported by {len(cluster)} news sources. ") report_sections.append(f"The primary headline from {cluster[0]['source']} reads: '{titles[0]}'\n") # Extract key information # Find common locations mentioned location_pattern = r'in ([A-Z][a-zA-Z]+(?: [A-Z][a-zA-Z]+)*)' locations = set(re.findall(location_pattern, combined_content)) if locations: report_sections.append("\n## Location\n") report_sections.append(f"This story takes place in {', '.join(list(locations)[:3])}.\n") # Find dates and times date_pattern = r'(?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}(?:st|nd|rd|th)?(?:,? \d{4})?' dates = set(re.findall(date_pattern, combined_content)) if dates: report_sections.append("\n## Timeline\n") report_sections.append(f"Key dates mentioned: {', '.join(list(dates)[:3])}.\n") # Extract quotes quote_pattern = r'"([^"]*)"' quotes = re.findall(quote_pattern, combined_content) if quotes: report_sections.append("\n## Key Quotes\n") for quote in quotes[:2]: # Limit to 2 most relevant quotes if len(quote) > 20: # Filter out short quotes report_sections.append(f'- "{quote}"\n') # Compare perspectives report_sections.append("\n## Source Comparison\n") for article in cluster: report_sections.append(f"- {article['source']}'s coverage focuses on: {article['title']}\n") # Combine sections into final report final_report = "\n".join(report_sections) return final_report except Exception as e: self.logger.error(f"Cluster analysis error: {str(e)}") return None def _store_article(self, article: Dict) -> bool: """Store article in database if it doesn't exist""" try: content_hash = hashlib.sha256( f"{article['title']}{article['url']}".encode() ).hexdigest() with sqlite3.connect('news_synthesis.db') as conn: cursor = conn.cursor() cursor.execute(''' INSERT OR IGNORE INTO articles (title, source, url, content, published_date, hash) VALUES (?, ?, ?, ?, ?, ?) ''', ( article['title'], article['source'], article['url'], article.get('content', ''), article.get('published', 'N/A'), content_hash )) return cursor.rowcount > 0 except sqlite3.Error as e: self.logger.error(f"Article storage error: {str(e)}") return False def _store_report(self, report: Dict) -> bool: """Store generated report in database""" try: with sqlite3.connect('news_synthesis.db') as conn: cursor = conn.cursor() cursor.execute(''' INSERT INTO reports (headline, sources, urls, published_dates, report_content) VALUES (?, ?, ?, ?, ?) ''', ( report['headline'], json.dumps(report['sources']), json.dumps(report['urls']), json.dumps(report['published_dates']), report.get('report', '') )) return cursor.rowcount > 0 except sqlite3.Error as e: self.logger.error(f"Report storage error: {str(e)}") return False async def _parse_feed_with_retry(self, session: aiohttp.ClientSession, feed_url: str, retries=3) -> Optional[Dict]: """Parse RSS feed with improved error handling and retry logic""" for attempt in range(retries): try: async with session.get(feed_url, timeout=self.scrape_timeout) as response: if response.status == 200: content = await response.text() feed_data = feedparser.parse(content) if feed_data.entries: return feed_data await asyncio.sleep(2 ** attempt) except Exception as e: self.logger.error(f"Feed parsing error (attempt {attempt + 1}): {str(e)}") if attempt < retries - 1: await asyncio.sleep(2 ** attempt) return None async def _scrape_article(self, url: str) -> Optional[str]: try: async with async_playwright() as p: browser = await p.chromium.launch(headless=True) context = await browser.new_context( user_agent=self.user_agent, viewport={'width': 1920, 'height': 1080} ) page = await context.new_page() await page.route("**/*", lambda route: route.continue_() if route.request.resource_type == "document" else route.abort()) try: await page.goto(url, timeout=self.scrape_timeout * 1000) content = await page.evaluate('''() => { const selectors = [ 'article', '[itemprop="articleBody"]', '.article-body', '.story-body' ]; for (const selector of selectors) { const element = document.querySelector(selector); if (element?.textContent?.length > 500) { return element.innerText; } } return document.body.innerText; }''') return re.sub(r'\s+', ' ', content).strip()[:4000] if content else None finally: await browser.close() except Exception as e: self.logger.error(f"Scraping error for {url}: {str(e)}") return None async def process_feeds(self, progress_bar) -> List[Dict]: """Process all feeds with improved concurrency and error handling""" async with aiohttp.ClientSession(headers={'User-Agent': self.user_agent}) as session: all_articles = [] for idx, (feed_name, feed_url) in enumerate(self.rss_feeds): try: progress_bar.progress((idx + 0.5)/len(self.rss_feeds), f"Processing {feed_name}") feed_data = await self._parse_feed_with_retry(session, feed_url) if not feed_data: continue scrape_tasks = [ self._scrape_article(entry.link) for entry in feed_data.entries[:3] ] contents = await asyncio.gather(*scrape_tasks) valid_articles = [ { 'title': entry.title, 'source': feed_name, 'content': content, 'url': entry.link, 'published': getattr(entry, 'published', 'N/A') } for entry, content in zip(feed_data.entries[:3], contents) if content and len(content) > 500 ] # Store valid articles in database for article in valid_articles: if self._store_article(article): all_articles.append(article) st.toast(f"Added article from {feed_name}") progress_bar.progress((idx + 1)/len(self.rss_feeds)) except Exception as e: self.logger.error(f"Error processing {feed_name}: {str(e)}") st.error(f"Failed to process {feed_name}") return await self._generate_reports(all_articles) async def _generate_reports(self, articles: List[Dict]) -> List[Dict]: """Generate reports from collected articles""" # Group articles by source source_map = {source: [] for source, _ in self.rss_feeds} for article in articles: source_map[article['source']].append(article) # Find minimum number of articles across sources min_articles = min(len(articles) for articles in source_map.values() if articles) reports = [] for i in range(min_articles): try: cluster = [] for source in source_map: if i < len(source_map[source]): cluster.append(source_map[source][i]) if len(cluster) >= 2: # Only process if we have at least 2 sources analysis = await self._analyze_cluster(cluster) if analysis: report = { 'headline': cluster[0]['title'], 'sources': [a['source'] for a in cluster], 'urls': [a['url'] for a in cluster], 'published_dates': [a['published'] for a in cluster], 'report': analysis } if self._store_report(report): reports.append(report) except Exception as e: self.logger.error(f"Report generation error: {str(e)}") return reports def display_report(reports: List[Dict]): # Sidebar with st.sidebar: st.image("https://via.placeholder.com/150x150.png?text=NS", width=150) colored_header(label="Report Information", description="", color_name="blue-70") st.write(f"📅 **Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") st.write(f"🤖 **AI Model:** Phi-4") st.write("📰 **News Sources:**") for source in ["BBC News", "Reuters", "AP News", "The New York Times", "Al Jazeera"]: st.write(f" • {source}") add_vertical_space(2) with st.expander("ℹ️ About", expanded=False): st.write(""" This news synthesizer uses advanced AI to analyze and summarize news from multiple sources. It identifies related stories, cross-references facts, and generates comprehensive reports. """) # Main content col1, col2, col3 = st.columns([1,3,1]) with col2: st.title("🌐 News Synthesis Hub") if not reports: # Display loading animation lottie_news = load_lottie_url("https://assets4.lottiefiles.com/packages/lf20_9qshezyl.json") if lottie_news: st_lottie(lottie_news, height=400) st.warning("No reports available. Generate a new report to begin.") return # Display reports # ... [rest of display_report function remains the same] def load_lottie_url(url: str): try: r = requests.get(url) if r.status_code != 200: return None return r.json() except: return None async def main(): st.set_page_config( page_title="News Synthesis Hub", page_icon="🌐", layout="wide", initial_sidebar_state="expanded" ) if st.button("🔄 Generate New Report"): progress_bar = st.progress(0) try: with st.spinner("Analyzing news feeds..."): synthesizer = NewsSynthesizer() reports = await synthesizer.process_feeds(progress_bar) display_report(reports) except Exception as e: st.error(f"Application error: {str(e)[:200]}") finally: progress_bar.empty() else: display_report([]) if __name__ == "__main__": asyncio.run(main())
Looks like you've built a solid news synthesis tool! You're pulling RSS feeds, scraping content, clustering articles, analyzing key details, and storing reports in a database. Is there something specific you'd like to refine or improve? Maybe optimizing the scraping, enhancing clustering logic, or adding AI-powered summarization?
gpt-4o
it does not output the final report
It looks like the function `_generate_reports` is responsible for creating reports but doesn't seem to return or store them properly at the end. A few possible reasons why the final report might not be outputted: 1. **Missing `return reports`** - Ensure `_generate_reports` explicitly returns the list of reports. 2. **Failed Cluster Analysis** - The `_analyze_cluster` method might return `None` if it encounters an issue. Check logs for errors. 3. **Database Storage Issue** - The `_store_report` function might fail due to SQLite constraints (like duplicates). ### Fix: Modify `_generate_reports` to explicitly store reports and return them: ```python async def _generate_reports(self, articles: List[Dict]) -> List[Dict]: """Generate reports from collected articles""" source_map = {source: [] for source, _ in self.rss_feeds} for article in articles: source_map[article['source']].append(article) min_articles = min(len(articles) for articles in source_map.values() if articles) reports = [] for i in range(min_articles): try: cluster = [source_map[source][i] for source in source_map if i < len(source_map[source])] if len(cluster) >= 2: analysis = await self._analyze_cluster(cluster) if analysis: report = { 'headline': cluster[0]['title'], 'sources': [a['source'] for a in cluster], 'urls': [a['url'] for a in cluster], 'published_dates': [a['published'] for a in cluster], 'report': analysis } if self._store_report(report): reports.append(report) except Exception as e: self.logger.error(f"Error generating report: {str(e)}") return reports # Ensure reports are returned ``` After calling `process_feeds()`, explicitly print or display the results: ```python reports = await news_synthesizer.process_feeds(progress_bar) if reports: for report in reports: st.write("### " + report['headline']) st.write(report['report']) else: st.write("No reports generated.") ``` This should ensure the final reports are stored and displayed. Let me know if you're still having issues! 🚀
gpt-4o