DEV Community

Cecilia Hill
Cecilia Hill

Posted on

How to Track Google News Results with Talordata SERP API

Google News results are useful when you want to monitor what is changing on the web.

You can track:

brand mentions
competitor news
industry trends
product launches
market signals
public opinion changes
new articles about a topic
Enter fullscreen mode Exit fullscreen mode

You can do that manually.

You can also manually refresh Google News every morning like the internet is a punishment ritual.

A better approach is to collect Google News results programmatically, save snapshots, and compare what changed over time.

In this tutorial, we will build a Python workflow that uses Talordata SERP API to track Google News results.

The workflow looks like this:

topics.csv
→ Talordata SERP API
→ Google News results
→ clean article data
→ save daily snapshot
→ compare snapshots
→ detect new articles
Enter fullscreen mode Exit fullscreen mode

This is useful for:

brand monitoring
competitor monitoring
PR tracking
market research
news intelligence
AI research assistants
trend detection
Enter fullscreen mode Exit fullscreen mode

What we are building

We will build two scripts.

The first script collects Google News results and saves them to CSV.

The second script compares the latest snapshot with the previous one and detects new articles.

The final output will look like this:

snapshot_date,keyword,location,title,source,article_url,domain,published_date,snippet
2026-01-01,AI search tools,United States,Example article title,Example News,https://example.com/article,example.com,2 hours ago,Example snippet...
Enter fullscreen mode Exit fullscreen mode

Then the comparison file will show:

keyword,location,title,source,article_url,change_type
AI search tools,United States,Example article title,Example News,https://example.com/article,new_article
Enter fullscreen mode Exit fullscreen mode

Simple.

Useful.

Suspiciously rare.

Why use a SERP API for Google News?

Google News results are dynamic.

They change by:

query
location
language
freshness
publisher
time
search settings
Enter fullscreen mode Exit fullscreen mode

A SERP API lets you request structured search data instead of building and maintaining your own scraper.

For a Google News tracking workflow, you usually want fields like:

title
URL
source
published date
snippet
thumbnail
position
Enter fullscreen mode Exit fullscreen mode

Different API providers may return slightly different field names, so the parser in this tutorial is defensive.

The goal is to keep the workflow adaptable.

Install dependencies

Create a new project folder and install the packages:

pip install requests python-dotenv pandas
Enter fullscreen mode Exit fullscreen mode

We will use:

requests → call Talordata SERP API
python-dotenv → load API keys
pandas → read and write CSV files
Enter fullscreen mode Exit fullscreen mode

Create a .env file

Create a .env file:

TALORDATA_API_KEY=your_api_key
TALORDATA_SERP_ENDPOINT=https://your-talordata-serp-api-endpoint.example.com/search
Enter fullscreen mode Exit fullscreen mode

Use the endpoint and parameter names from your Talordata dashboard or API docs.

The code below keeps the API request logic in one function, so if your endpoint or parameters are slightly different, you only need to edit one place.

That is not laziness. That is maintenance trying to survive.

Create the topics file

Create a file called topics.csv.

keyword,location,language
AI search tools,United States,en
SERP API,United States,en
OpenAI competitors,United States,en
Google AI search,United States,en
local SEO tools,United States,en
Enter fullscreen mode Exit fullscreen mode

Each row is one Google News search.

Start small.

Five topics are enough to test the workflow.

Once the output looks clean, you can add more keywords, locations, or languages.

Step 1: Create the news tracker script

Create a file:

track_google_news.py
Enter fullscreen mode Exit fullscreen mode

Add imports and settings:

import os
import re
import time
import hashlib
import requests
import pandas as pd
from datetime import date
from urllib.parse import urlparse, urlunparse, parse_qsl, urlencode
from dotenv import load_dotenv


load_dotenv()

TALORDATA_API_KEY = os.getenv("TALORDATA_API_KEY")
TALORDATA_SERP_ENDPOINT = os.getenv("TALORDATA_SERP_ENDPOINT")


def validate_settings():
    if not TALORDATA_API_KEY:
        raise ValueError("Missing TALORDATA_API_KEY")

    if not TALORDATA_SERP_ENDPOINT:
        raise ValueError("Missing TALORDATA_SERP_ENDPOINT")
Enter fullscreen mode Exit fullscreen mode

This keeps secrets out of the code.

Tiny security victory. Take them where you can.

Step 2: Load topics from CSV

Add a function to read topics.csv.

def load_topics(filename="topics.csv"):
    df = pd.read_csv(filename)

    required_columns = {"keyword", "location", "language"}
    missing_columns = required_columns - set(df.columns)

    if missing_columns:
        raise ValueError(f"Missing columns in {filename}: {missing_columns}")

    return df.to_dict("records")
Enter fullscreen mode Exit fullscreen mode

This gives us input like:

[
    {
        "keyword": "AI search tools",
        "location": "United States",
        "language": "en"
    }
]
Enter fullscreen mode Exit fullscreen mode

Step 3: Call Talordata SERP API

Now create the request function.

Depending on your Talordata setup, the exact parameter names may differ.

This example uses a common SERP API pattern:

def fetch_google_news(keyword, location="United States", language="en"):
    params = {
        "api_key": TALORDATA_API_KEY,
        "engine": "google",
        "q": keyword,
        "type": "news",
        "location": location,
        "language": language,
        "output": "json",
    }

    response = requests.get(
        TALORDATA_SERP_ENDPOINT,
        params=params,
        timeout=30,
    )

    response.raise_for_status()
    return response.json()
Enter fullscreen mode Exit fullscreen mode

Some APIs may use:

search_type=news
tbm=nws
engine=google_news
type=news
Enter fullscreen mode Exit fullscreen mode

If your Talordata docs use a different option, update this function.

Do not change the whole script.

Future you already has enough problems.

Step 4: Extract news results

SERP APIs may return news results under different keys.

Common examples include:

news_results
news
top_stories
articles
results
Enter fullscreen mode Exit fullscreen mode

Add a defensive extractor:

def get_news_items(data):
    possible_keys = [
        "news_results",
        "news",
        "top_stories",
        "articles",
        "results",
    ]

    for key in possible_keys:
        value = data.get(key)

        if isinstance(value, list):
            return value

    serp = data.get("serp", {})

    if isinstance(serp, dict):
        for key in possible_keys:
            value = serp.get(key)

            if isinstance(value, list):
                return value

    return []
Enter fullscreen mode Exit fullscreen mode

This makes the script easier to adapt if the response shape changes.

APIs love naming the same thing five different ways. A grand tradition of tiny suffering.

Step 5: Clean text and URLs

News URLs often include tracking parameters.

We want clean URLs for deduplication and reporting.

TRACKING_PARAMS = {
    "utm_source",
    "utm_medium",
    "utm_campaign",
    "utm_term",
    "utm_content",
    "fbclid",
    "gclid",
    "mc_cid",
    "mc_eid",
}


def clean_text(value):
    if value is None:
        return ""

    value = str(value)
    value = re.sub(r"<[^>]*>", " ", value)
    value = re.sub(r"\s+", " ", value)

    return value.strip()


def clean_url(url):
    if not url:
        return ""

    url = str(url).strip()

    try:
        parsed = urlparse(url)

        query_pairs = parse_qsl(
            parsed.query,
            keep_blank_values=True,
        )

        filtered_pairs = [
            (key, value)
            for key, value in query_pairs
            if key.lower() not in TRACKING_PARAMS
        ]

        cleaned_query = urlencode(filtered_pairs)

        cleaned = parsed._replace(
            query=cleaned_query,
            fragment="",
        )

        return urlunparse(cleaned)

    except Exception:
        return url


def extract_domain(url):
    if not url:
        return ""

    try:
        parsed = urlparse(url)
        domain = parsed.netloc.lower()

        if domain.startswith("www."):
            domain = domain[4:]

        return domain

    except Exception:
        return ""
Enter fullscreen mode Exit fullscreen mode

Clean URLs make comparison easier.

Messy URLs make duplicates breed in your CSV like spreadsheet mold.

Step 6: Normalize one news article

Now convert one raw news result into a consistent schema.

def normalize_news_item(item, keyword, location, language, index):
    raw_url = (
        item.get("link")
        or item.get("url")
        or item.get("article_url")
        or item.get("href")
        or ""
    )

    article_url = clean_url(raw_url)

    title = clean_text(
        item.get("title")
        or item.get("headline")
        or ""
    )

    source = clean_text(
        item.get("source")
        or item.get("publisher")
        or item.get("site")
        or ""
    )

    snippet = clean_text(
        item.get("snippet")
        or item.get("description")
        or item.get("summary")
        or ""
    )

    published_date = clean_text(
        item.get("date")
        or item.get("published_date")
        or item.get("published_at")
        or item.get("time")
        or ""
    )

    thumbnail = clean_text(
        item.get("thumbnail")
        or item.get("image")
        or ""
    )

    position = (
        item.get("position")
        or item.get("rank")
        or index
    )

    article_id = create_article_id(
        title=title,
        source=source,
        article_url=article_url,
    )

    return {
        "snapshot_date": date.today().isoformat(),
        "keyword": keyword,
        "location": location,
        "language": language,
        "article_id": article_id,
        "position": position,
        "title": title,
        "source": source,
        "article_url": article_url,
        "domain": extract_domain(article_url),
        "published_date": published_date,
        "snippet": snippet,
        "thumbnail": thumbnail,
    }
Enter fullscreen mode Exit fullscreen mode

This uses create_article_id(), which we will add next.

Step 7: Create a stable article ID

We need a way to detect whether an article is new or already seen.

Use a fingerprint based on:

title
source
URL
Enter fullscreen mode Exit fullscreen mode
def normalize_for_id(value):
    value = clean_text(value).lower()
    value = re.sub(r"[^a-z0-9\s]", " ", value)
    value = re.sub(r"\s+", " ", value)
    return value.strip()


def create_article_id(title, source, article_url):
    raw_key = "|".join([
        normalize_for_id(title),
        normalize_for_id(source),
        clean_url(article_url),
    ])

    return hashlib.sha256(raw_key.encode("utf-8")).hexdigest()
Enter fullscreen mode Exit fullscreen mode

This is not perfect.

Publishers can update headlines. URLs can change. Syndicated articles can appear across multiple domains.

But it works well for a first version.

Perfect deduplication is where simple scripts go to become unpaid research projects.

Step 8: Collect news for one topic

Now combine request, extraction, and normalization.

def collect_news_for_topic(topic):
    keyword = topic["keyword"]
    location = topic["location"]
    language = topic["language"]

    data = fetch_google_news(
        keyword=keyword,
        location=location,
        language=language,
    )

    raw_items = get_news_items(data)

    rows = [
        normalize_news_item(
            item=item,
            keyword=keyword,
            location=location,
            language=language,
            index=index,
        )
        for index, item in enumerate(raw_items, start=1)
    ]

    return rows
Enter fullscreen mode Exit fullscreen mode

This turns one topic into clean article rows.

Step 9: Collect all topics

Now loop through every topic in the CSV.

def collect_all_news(topics, delay=1):
    all_rows = []

    for topic in topics:
        keyword = topic["keyword"]
        location = topic["location"]

        print(f"Collecting news: {keyword} | {location}")

        try:
            rows = collect_news_for_topic(topic)
            all_rows.extend(rows)

            print(f"Found {len(rows)} articles")

        except Exception as exc:
            print(f"Failed: {keyword} | {location}")
            print(f"Error: {exc}")

            all_rows.append({
                "snapshot_date": date.today().isoformat(),
                "keyword": keyword,
                "location": location,
                "language": topic.get("language", ""),
                "article_id": "",
                "position": "",
                "title": "",
                "source": "",
                "article_url": "",
                "domain": "",
                "published_date": "",
                "snippet": "",
                "thumbnail": "",
                "error": str(exc),
            })

        time.sleep(delay)

    return all_rows
Enter fullscreen mode Exit fullscreen mode

The delay keeps requests controlled.

Automation should be steady, not a caffeine-powered swarm of HTTP regret.

Step 10: Save a daily snapshot

Now save results to a dated CSV file.

def save_snapshot(rows):
    today = date.today().isoformat()
    filename = f"google_news_snapshot_{today}.csv"

    df = pd.DataFrame(rows)

    if not df.empty and "article_id" in df.columns:
        df = df.drop_duplicates(subset=["article_id"])

    df.to_csv(filename, index=False)

    print(f"Saved snapshot: {filename}")
    print(f"Rows saved: {len(df)}")

    return filename
Enter fullscreen mode Exit fullscreen mode

Each run creates a file like:

google_news_snapshot_2026-01-01.csv
Enter fullscreen mode Exit fullscreen mode

Full tracking script

Here is the complete track_google_news.py file.

import os
import re
import time
import hashlib
import requests
import pandas as pd
from datetime import date
from urllib.parse import urlparse, urlunparse, parse_qsl, urlencode
from dotenv import load_dotenv


load_dotenv()

TALORDATA_API_KEY = os.getenv("TALORDATA_API_KEY")
TALORDATA_SERP_ENDPOINT = os.getenv("TALORDATA_SERP_ENDPOINT")


TRACKING_PARAMS = {
    "utm_source",
    "utm_medium",
    "utm_campaign",
    "utm_term",
    "utm_content",
    "fbclid",
    "gclid",
    "mc_cid",
    "mc_eid",
}


def validate_settings():
    if not TALORDATA_API_KEY:
        raise ValueError("Missing TALORDATA_API_KEY")

    if not TALORDATA_SERP_ENDPOINT:
        raise ValueError("Missing TALORDATA_SERP_ENDPOINT")


def load_topics(filename="topics.csv"):
    df = pd.read_csv(filename)

    required_columns = {"keyword", "location", "language"}
    missing_columns = required_columns - set(df.columns)

    if missing_columns:
        raise ValueError(f"Missing columns in {filename}: {missing_columns}")

    return df.to_dict("records")


def fetch_google_news(keyword, location="United States", language="en"):
    params = {
        "api_key": TALORDATA_API_KEY,
        "engine": "google",
        "q": keyword,
        "type": "news",
        "location": location,
        "language": language,
        "output": "json",
    }

    response = requests.get(
        TALORDATA_SERP_ENDPOINT,
        params=params,
        timeout=30,
    )

    response.raise_for_status()
    return response.json()


def get_news_items(data):
    possible_keys = [
        "news_results",
        "news",
        "top_stories",
        "articles",
        "results",
    ]

    for key in possible_keys:
        value = data.get(key)

        if isinstance(value, list):
            return value

    serp = data.get("serp", {})

    if isinstance(serp, dict):
        for key in possible_keys:
            value = serp.get(key)

            if isinstance(value, list):
                return value

    return []


def clean_text(value):
    if value is None:
        return ""

    value = str(value)
    value = re.sub(r"<[^>]*>", " ", value)
    value = re.sub(r"\s+", " ", value)

    return value.strip()


def clean_url(url):
    if not url:
        return ""

    url = str(url).strip()

    try:
        parsed = urlparse(url)

        query_pairs = parse_qsl(
            parsed.query,
            keep_blank_values=True,
        )

        filtered_pairs = [
            (key, value)
            for key, value in query_pairs
            if key.lower() not in TRACKING_PARAMS
        ]

        cleaned_query = urlencode(filtered_pairs)

        cleaned = parsed._replace(
            query=cleaned_query,
            fragment="",
        )

        return urlunparse(cleaned)

    except Exception:
        return url


def extract_domain(url):
    if not url:
        return ""

    try:
        parsed = urlparse(url)
        domain = parsed.netloc.lower()

        if domain.startswith("www."):
            domain = domain[4:]

        return domain

    except Exception:
        return ""


def normalize_for_id(value):
    value = clean_text(value).lower()
    value = re.sub(r"[^a-z0-9\s]", " ", value)
    value = re.sub(r"\s+", " ", value)
    return value.strip()


def create_article_id(title, source, article_url):
    raw_key = "|".join([
        normalize_for_id(title),
        normalize_for_id(source),
        clean_url(article_url),
    ])

    return hashlib.sha256(raw_key.encode("utf-8")).hexdigest()


def normalize_news_item(item, keyword, location, language, index):
    raw_url = (
        item.get("link")
        or item.get("url")
        or item.get("article_url")
        or item.get("href")
        or ""
    )

    article_url = clean_url(raw_url)

    title = clean_text(
        item.get("title")
        or item.get("headline")
        or ""
    )

    source = clean_text(
        item.get("source")
        or item.get("publisher")
        or item.get("site")
        or ""
    )

    snippet = clean_text(
        item.get("snippet")
        or item.get("description")
        or item.get("summary")
        or ""
    )

    published_date = clean_text(
        item.get("date")
        or item.get("published_date")
        or item.get("published_at")
        or item.get("time")
        or ""
    )

    thumbnail = clean_text(
        item.get("thumbnail")
        or item.get("image")
        or ""
    )

    position = (
        item.get("position")
        or item.get("rank")
        or index
    )

    article_id = create_article_id(
        title=title,
        source=source,
        article_url=article_url,
    )

    return {
        "snapshot_date": date.today().isoformat(),
        "keyword": keyword,
        "location": location,
        "language": language,
        "article_id": article_id,
        "position": position,
        "title": title,
        "source": source,
        "article_url": article_url,
        "domain": extract_domain(article_url),
        "published_date": published_date,
        "snippet": snippet,
        "thumbnail": thumbnail,
    }


def collect_news_for_topic(topic):
    keyword = topic["keyword"]
    location = topic["location"]
    language = topic["language"]

    data = fetch_google_news(
        keyword=keyword,
        location=location,
        language=language,
    )

    raw_items = get_news_items(data)

    rows = [
        normalize_news_item(
            item=item,
            keyword=keyword,
            location=location,
            language=language,
            index=index,
        )
        for index, item in enumerate(raw_items, start=1)
    ]

    return rows


def collect_all_news(topics, delay=1):
    all_rows = []

    for topic in topics:
        keyword = topic["keyword"]
        location = topic["location"]

        print(f"Collecting news: {keyword} | {location}")

        try:
            rows = collect_news_for_topic(topic)
            all_rows.extend(rows)

            print(f"Found {len(rows)} articles")

        except Exception as exc:
            print(f"Failed: {keyword} | {location}")
            print(f"Error: {exc}")

            all_rows.append({
                "snapshot_date": date.today().isoformat(),
                "keyword": keyword,
                "location": location,
                "language": topic.get("language", ""),
                "article_id": "",
                "position": "",
                "title": "",
                "source": "",
                "article_url": "",
                "domain": "",
                "published_date": "",
                "snippet": "",
                "thumbnail": "",
                "error": str(exc),
            })

        time.sleep(delay)

    return all_rows


def save_snapshot(rows):
    today = date.today().isoformat()
    filename = f"google_news_snapshot_{today}.csv"

    df = pd.DataFrame(rows)

    if not df.empty and "article_id" in df.columns:
        df = df.drop_duplicates(subset=["article_id"])

    df.to_csv(filename, index=False)

    print(f"Saved snapshot: {filename}")
    print(f"Rows saved: {len(df)}")

    return filename


def main():
    validate_settings()

    topics = load_topics("topics.csv")

    rows = collect_all_news(
        topics=topics,
        delay=1,
    )

    save_snapshot(rows)


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Run it:

python track_google_news.py
Enter fullscreen mode Exit fullscreen mode

You should get a CSV file like:

google_news_snapshot_2026-01-01.csv
Enter fullscreen mode Exit fullscreen mode

Step 11: Compare snapshots and detect new articles

A single snapshot is useful.

But news tracking becomes much more useful when you compare snapshots.

Create another file:

compare_google_news.py
Enter fullscreen mode Exit fullscreen mode
import glob
import pandas as pd


def load_latest_snapshots():
    files = sorted(glob.glob("google_news_snapshot_*.csv"))

    if len(files) < 2:
        raise ValueError("Need at least two snapshot files to compare")

    previous_file = files[-2]
    current_file = files[-1]

    previous_df = pd.read_csv(previous_file)
    current_df = pd.read_csv(current_file)

    return previous_file, current_file, previous_df, current_df


def detect_new_articles(previous_df, current_df):
    previous_ids = set(
        previous_df["article_id"]
        .dropna()
        .astype(str)
        .tolist()
    )

    current_df["change_type"] = current_df["article_id"].apply(
        lambda article_id: "existing_article"
        if str(article_id) in previous_ids
        else "new_article"
    )

    new_articles = current_df[
        current_df["change_type"] == "new_article"
    ].copy()

    return current_df, new_articles


def main():
    previous_file, current_file, previous_df, current_df = load_latest_snapshots()

    compared_df, new_articles_df = detect_new_articles(
        previous_df=previous_df,
        current_df=current_df,
    )

    compared_file = "google_news_compared.csv"
    new_articles_file = "google_news_new_articles.csv"

    compared_df.to_csv(compared_file, index=False)
    new_articles_df.to_csv(new_articles_file, index=False)

    print(f"Previous snapshot: {previous_file}")
    print(f"Current snapshot: {current_file}")
    print(f"Saved comparison: {compared_file}")
    print(f"Saved new articles: {new_articles_file}")

    print("\nSummary:")
    print(compared_df["change_type"].value_counts())


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Run it after you have at least two snapshot files:

python compare_google_news.py
Enter fullscreen mode Exit fullscreen mode

This creates:

google_news_compared.csv
google_news_new_articles.csv
Enter fullscreen mode Exit fullscreen mode

Now you can see what appeared since the previous run.

That is the actual monitoring part.

Without comparison, you are just collecting files with dates on them and calling it strategy.

What you can analyze next

Once you have Google News snapshots, you can analyze:

which publishers appear most often
which topics are getting more coverage
which companies are mentioned repeatedly
which articles are new today
which sources dominate a keyword
how coverage differs by country or language
Enter fullscreen mode Exit fullscreen mode

For example, load the latest snapshot:

import pandas as pd

df = pd.read_csv("google_news_snapshot_2026-01-01.csv")

top_sources = (
    df.groupby("source")
    .size()
    .reset_index(name="article_count")
    .sort_values("article_count", ascending=False)
)

print(top_sources.head(20))
Enter fullscreen mode Exit fullscreen mode

Analyze by keyword:

keyword_counts = (
    df.groupby("keyword")
    .size()
    .reset_index(name="article_count")
    .sort_values("article_count", ascending=False)
)

print(keyword_counts)
Enter fullscreen mode Exit fullscreen mode

Filter articles from a specific domain:

domain_articles = df[df["domain"] == "example.com"]

print(domain_articles[["keyword", "title", "article_url"]])
Enter fullscreen mode Exit fullscreen mode

Add brand monitoring

To monitor brand mentions, use keywords like:

YourBrand
YourBrand pricing
YourBrand reviews
YourBrand alternative
YourBrand competitor
Enter fullscreen mode Exit fullscreen mode

For competitor monitoring, use:

CompetitorName
CompetitorName funding
CompetitorName launch
CompetitorName partnership
CompetitorName reviews
Enter fullscreen mode Exit fullscreen mode

For industry tracking, use:

AI search
SERP API
RAG tools
local SEO software
search data API
Enter fullscreen mode Exit fullscreen mode

This turns the script into a lightweight news intelligence workflow.

Not a full media monitoring platform.

But enough to know what changed without staring into Google News like it owes you money.

Add Slack alerts

If you want alerts, send google_news_new_articles.csv to Slack.

A simple message format:

New Google News articles detected

Keyword: AI search tools
Source: Example News
Title: Example article title
URL: https://example.com/article
Enter fullscreen mode Exit fullscreen mode

You can add Slack later using:

Slack webhook
n8n
Zapier
Make
GitHub Actions
Enter fullscreen mode Exit fullscreen mode

Keep the first version simple.

Collect first. Alert later.

Otherwise you build an alert system that loudly reports broken data. Inspiring, in the worst way.

Add LLM summaries

You can also summarize new articles with an LLM.

A simple prompt:

You are a news analyst.

Summarize the following new Google News results.

Rules:
- Group articles by topic.
- Mention important sources.
- Do not invent facts beyond the title and snippet.
- Keep the summary concise.
- Include article URLs.

News results:
{news_results}
Enter fullscreen mode Exit fullscreen mode

This is useful for:

daily briefings
competitor updates
market research
PR monitoring
executive summaries
Enter fullscreen mode Exit fullscreen mode

But keep the raw CSV.

LLM summaries are convenient.

Raw data is evidence.

Do not replace evidence with a paragraph that sounds confident and owns a blazer.

Schedule the tracker

You can run the tracker manually.

For monitoring, schedule it.

On macOS or Linux, use cron:

crontab -e
Enter fullscreen mode Exit fullscreen mode

Run every day at 8 AM:

0 8 * * * /usr/bin/python3 /path/to/track_google_news.py
Enter fullscreen mode Exit fullscreen mode

Run comparison at 8:10 AM:

10 8 * * * /usr/bin/python3 /path/to/compare_google_news.py
Enter fullscreen mode Exit fullscreen mode

For GitHub Actions, create:

.github/workflows/google-news-tracker.yml
Enter fullscreen mode Exit fullscreen mode
name: Google News Tracker

on:
  schedule:
    - cron: "0 8 * * *"
  workflow_dispatch:

jobs:
  track-news:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - run: pip install requests python-dotenv pandas

      - run: python track_google_news.py
        env:
          TALORDATA_API_KEY: ${{ secrets.TALORDATA_API_KEY }}
          TALORDATA_SERP_ENDPOINT: ${{ secrets.TALORDATA_SERP_ENDPOINT }}
Enter fullscreen mode Exit fullscreen mode

For production, use a database instead of CSV.

Good options:

SQLite
PostgreSQL
BigQuery
Supabase
S3
Google Sheets
Enter fullscreen mode Exit fullscreen mode

CSV is fine for the first version.

CSV is not a destiny.

Common mistakes

Not saving snapshots

If you only keep the latest results, you cannot detect change.

Save every run.

Deduplicating only by title

News titles can change.

Use a combination of title, source, and URL.

Ignoring location and language

Google News results can change by country and language.

Always store location and language with each row.

Treating snippets as full articles

A snippet is not the full article.

Use it for discovery and lightweight summaries.

For deeper analysis, fetch the article page and extract full text.

Tracking too many keywords too soon

Start with five to ten topics.

Make sure the output is clean before scaling.

Forgetting source quality

Not all news sources are equal.

For serious monitoring, add source categories or trusted source lists.

Provider note

This tutorial uses Talordata SERP API as the search data layer. Start a 7-day free trial now>>

The important requirement is structured Google News data that your workflow can parse into fields like:

title
URL
source
published date
snippet
position
Enter fullscreen mode Exit fullscreen mode

When testing any SERP API, inspect the response body first.

Check:

Are titles complete?
Are URLs clean?
Are sources included?
Are dates available?
Are snippets useful?
Does location targeting work?
Are empty responses common?
Is the JSON easy to normalize?
Enter fullscreen mode Exit fullscreen mode

The homepage is not where your tracker runs.

Your tracker runs on the response body.

Terrible news for marketing adjectives, but excellent news for developers.

Final thoughts

A Google News tracker does not need to start as a large media monitoring product.

Start with:

topics.csv
Talordata SERP API request
news result parser
daily CSV snapshot
snapshot comparison
new article detection
Enter fullscreen mode Exit fullscreen mode

Then improve it with:

Slack alerts
database storage
LLM summaries
source scoring
brand monitoring
competitor monitoring
multi-location tracking
full article extraction
Enter fullscreen mode Exit fullscreen mode

The core idea is simple:

query + location + language
→ Google News results
→ clean article data
→ compare over time
Enter fullscreen mode Exit fullscreen mode

Once you have snapshots, you can stop manually refreshing Google News and start working with actual change data.

Which is a small but meaningful upgrade from pretending browser tabs are a monitoring system.

Top comments (0)