Building a Daily Google News API Monitor in Python Here is a factual summary of the article: The article describes a Python-based tool that monitors Google News for brand mentions using the SearchApi.io API. Each morning, the tool fetches news articles for specified keywords, enriches them with sentiment analysis and summaries via OpenAI, stores the data in a SQLite database, and sends results through Slack or a web dashboard. The project consists of approximately 1,000 lines of Python code across ten files, structured as a Flask app with a single HTML dashboard. I wanted a small, local tool that would search the news for brand mentions. I didnt want to pay over $100 a month so I decided to build my own. What I created was a tool that would search the news with a Google News API every morning for a list of keywords, run each result through a LLM for sentiment and a one-sentence summary, save everything to SQLite, and ping me the results on Slack/a web app. The whole project came out to about 1,000 lines of Python across ten files. It is a Flask app with a SQLite database and a single HTML dashboard. Here's how it's wired together, with the code that matters from each layer. Repo: google-news-monitor on GitHub . Install instructions at the bottom. The pipeline The whole tool is one pipeline: keyword → Google News API → OpenAI enrichment → SQLite → dashboard | REST | CLI | Slack Every interface the dashboard form, the REST API, the CLI, the daily cron ends up calling the same process keyword function. Here is the entire core loop, from monitor/pipeline.py : python def process keyword keyword, num=30, when="1d", gl=None : keyword = keyword.strip fetched = search.fetch google news keyword, num=num, when=when, gl=gl new count = 0 for art in fetched: if not art.get "url" : continue ai result = ai.enrich article keyword, art.get "title" or "", art.get "snippet" or "" row = { "keyword": keyword, "title": art "title" , "url": art "url" , "source": art.get "source" , "snippet": art.get "snippet" , "published at": art.get "published at" , "sentiment": ai result "sentiment" , "ai summary": ai result "summary" , } article id = db.save article row if article id is not None: new count += 1 alerts.check article keyword, row, article id return {"keyword": keyword, "fetched": len fetched , "new": new count} Fetch, enrich, save, alert. That is the whole tool, minus the interfaces wrapped around it. Fetching from the Google News API I'm using SearchApi.io https://www.searchapi.io as the entry point to the Google News API. One issues i ran into was, google news matching is loose. search "niche company 1" and half the results are for the similar niche company 2, nothing to do with you. So there's a per-article flag for whether your keyword actually appears in the article body, with a toggle to hide everything else. From monitor/search.py : python SEARCHAPI URL = "https://www.searchapi.io/api/v1/search" def fetch google news keyword, num=30, when=None, gl=None : params = { "engine": "google news", "q": keyword.strip , "nfpr": 1, turn off "did you mean..." "num": num, "api key": os.environ "SEARCHAPI KEY" , } if when: params "when" = when 1h, 1d, 7d, 1m, 1y if gl: params "gl" = gl.lower 2-letter country code resp = requests.get SEARCHAPI URL, params=params, timeout=30 resp.raise for status data = resp.json articles = for item in data.get "organic results", or : articles.append { "title": item.get "title" , "url": item.get "link" , "source": item.get "source", {} .get "name" , "snippet": item.get "snippet" , "published at": parse date item.get "date" , } return articles One thing the Google News API will trip you up on: the date field arrives as free-form strings like "1 week ago" , "May 30, 2023" , or "Yesterday" . Not ISO timestamps. If you store those verbatim, your SQL filters will silently break and your charts will sort "May 30" alphabetically next to "2026-05-14" . I wrote a small parser monitor/dates.py that normalizes everything to YYYY-MM-DD on the way in. OpenAI enrichment with a JSON guardrail For every article I want two things: a sentiment label positive, negative, neutral and a one-sentence summary. The trick is to force OpenAI to return parseable JSON so the database ingestion never sees free-form text. From monitor/ai.py : ARTICLE SYSTEM PROMPT = "You are a media-monitoring analyst. For each article you receive, " "classify the sentiment toward the tracked brand/keyword and write a " "one-sentence summary. You MUST return a single JSON object - no prose, " "no markdown, no code fences. " 'Schema: {"sentiment": "positive"|"negative"|"neutral", ' '"summary": "