Kushal commited on
Commit
1064345
·
1 Parent(s): 03de79f

Enhance: Real News Scraper via RSS & removal of mock fallbacks

Browse files
Files changed (2) hide show
  1. app/routes/news.py +13 -22
  2. app/services/news_service.py +34 -37
app/routes/news.py CHANGED
@@ -43,30 +43,21 @@ async def search_news(
43
  articles = fetch_google_news(query, location)
44
 
45
  if not articles:
46
- print("[NEWS] No articles returned from Google, using fallback")
47
- raise ValueError("No articles found")
 
 
 
 
 
 
48
 
49
  except Exception as scrape_error:
50
- print(f"[NEWS] Scraping failed: {str(scrape_error)}, using fallback data")
51
- # Fallback to mock data if scraping fails
52
- articles = [
53
- {
54
- "title": f"Construction Industry Update - {location}",
55
- "snippet": f"Latest developments in {query} sector show positive trends with new regulations and infrastructure projects.",
56
- "sentiment": "Positive",
57
- "published_date": datetime.now().isoformat(),
58
- "link": "https://example.com/fallback1",
59
- "source": "Construction News"
60
- },
61
- {
62
- "title": f"{query.title()} Regulations Updated",
63
- "snippet": "New guidelines introduced to streamline processes and improve safety standards in the construction industry.",
64
- "sentiment": "Neutral",
65
- "published_date": datetime.now().isoformat(),
66
- "link": "https://example.com/fallback2",
67
- "source": "Industry Watch"
68
- }
69
- ]
70
 
71
  # Limit results
72
  articles = articles[:limit]
 
43
  articles = fetch_google_news(query, location)
44
 
45
  if not articles:
46
+ print(f"[NEWS] No articles found for query='{query}', location='{location}'")
47
+ return {
48
+ "articles": [],
49
+ "count": 0,
50
+ "query": query,
51
+ "location": location,
52
+ "timestamp": datetime.now().isoformat()
53
+ }
54
 
55
  except Exception as scrape_error:
56
+ print(f"[NEWS] Search failed: {str(scrape_error)}")
57
+ raise HTTPException(
58
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
59
+ detail=f"Failed to fetch real-time news: {str(scrape_error)}"
60
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
  # Limit results
63
  articles = articles[:limit]
app/services/news_service.py CHANGED
@@ -34,7 +34,8 @@ def clean_google_url(google_url: str) -> str:
34
 
35
  def fetch_google_news(query: str, state: str) -> list:
36
  """
37
- Fetch news articles from Google News by scraping search results.
 
38
 
39
  Args:
40
  query: Search query (e.g., 'construction law')
@@ -44,51 +45,47 @@ def fetch_google_news(query: str, state: str) -> list:
44
  List of news article dictionaries with title, link, snippet, sentiment, date
45
  """
46
  search_query = quote(f"{query} {state}")
47
- url = f"https://www.google.com/search?q={search_query}&tbm=nws"
 
48
 
49
  try:
50
  res = requests.get(url, headers=HEADERS, timeout=10)
51
  res.raise_for_status()
52
  except requests.RequestException as e:
53
- print(f"❌ Request failed: {e}")
54
  return []
55
 
56
- soup = BeautifulSoup(res.text, "html.parser")
 
 
57
 
58
  results = []
59
- articles = soup.find_all("div", class_="SoaBEf")
60
-
61
- for article in articles:
62
- # Extract title
63
- title_tag = article.find("div", class_="n0jPhd ynAwRc MBeuO nDgy9d")
64
- title = title_tag.text.strip() if title_tag else ""
65
-
66
- # Extract URL and clean it
67
- a_tag = article.find("a")
68
- link = clean_google_url(a_tag["href"]) if a_tag and a_tag.get("href") else ""
69
-
70
- # Extract snippet
71
- snippet_tag = article.find("div", class_="GI74Re nDgy9d")
72
- snippet = snippet_tag.text.strip() if snippet_tag else ""
73
-
74
- # Extract date from the correct div
75
- date_div = article.find("div", class_="OSrXXb")
76
- date_text = ""
77
- if date_div:
78
- span = date_div.find("span")
79
- if span:
80
- date_text = span.text.strip()
81
-
82
- # Parse it to datetime
83
- parsed_date = dateparser.parse(date_text) if date_text else None
84
-
85
- # Analyze sentiment using BOTH title and snippet for better accuracy
86
- # Combine title and snippet as news snippets alone are often neutral
87
  combined_text = f"{title}. {snippet}"
88
  sentiment = analyze_sentiment(combined_text)
89
 
90
- print(f"[SENTIMENT] {sentiment}: {title[:50]}...")
91
-
92
  if title and link:
93
  results.append({
94
  "title": title,
@@ -96,13 +93,13 @@ def fetch_google_news(query: str, state: str) -> list:
96
  "snippet": snippet,
97
  "sentiment": sentiment,
98
  "published_date": parsed_date.isoformat() if parsed_date else None,
99
- "source": "Google News"
100
  })
101
-
102
  # Sort by date (newest first)
103
  results.sort(key=lambda x: x["published_date"] or "", reverse=True)
104
 
105
- print(f"✅ Extracted {len(results)} news articles")
106
  return results
107
 
108
 
 
34
 
35
  def fetch_google_news(query: str, state: str) -> list:
36
  """
37
+ Fetch news articles from Google News via RSS feed.
38
+ This is much more robust than scraping HTML as it's a structured format.
39
 
40
  Args:
41
  query: Search query (e.g., 'construction law')
 
45
  List of news article dictionaries with title, link, snippet, sentiment, date
46
  """
47
  search_query = quote(f"{query} {state}")
48
+ # Google News RSS search URL
49
+ url = f"https://news.google.com/rss/search?q={search_query}&hl=en-IN&gl=IN&ceid=IN:en"
50
 
51
  try:
52
  res = requests.get(url, headers=HEADERS, timeout=10)
53
  res.raise_for_status()
54
  except requests.RequestException as e:
55
+ print(f"❌ RSS Request failed: {e}")
56
  return []
57
 
58
+ # Parse RSS XML
59
+ soup = BeautifulSoup(res.text, "xml")
60
+ items = soup.find_all("item")
61
 
62
  results = []
63
+
64
+ for item in items:
65
+ title = item.title.text if item.title else ""
66
+ link = item.link.text if item.link else ""
67
+
68
+ # Snippets in RSS are usually just the source, but we can extract a bit more
69
+ # or just use the title if snippet is missing.
70
+ # RSS usually doesn't have a dedicated snippet field like scraping does,
71
+ # so we'll use description if available, else blank.
72
+ description = item.description.text if item.description else ""
73
+ # Clean up description (it often contains HTML in RSS)
74
+ snippet = BeautifulSoup(description, "html.parser").get_text().strip() if description else ""
75
+
76
+ # Extract source from title usually formatted as "Title - Source"
77
+ source = "Google News"
78
+ if " - " in title:
79
+ source = title.split(" - ")[-1]
80
+
81
+ # Extract date
82
+ pub_date = item.pubDate.text if item.pubDate else ""
83
+ parsed_date = dateparser.parse(pub_date) if pub_date else None
84
+
85
+ # Analyze sentiment using title and snippet
 
 
 
 
 
86
  combined_text = f"{title}. {snippet}"
87
  sentiment = analyze_sentiment(combined_text)
88
 
 
 
89
  if title and link:
90
  results.append({
91
  "title": title,
 
93
  "snippet": snippet,
94
  "sentiment": sentiment,
95
  "published_date": parsed_date.isoformat() if parsed_date else None,
96
+ "source": source
97
  })
98
+
99
  # Sort by date (newest first)
100
  results.sort(key=lambda x: x["published_date"] or "", reverse=True)
101
 
102
+ print(f"✅ Extracted {len(results)} news articles from RSS")
103
  return results
104
 
105