Skip to content
CrawlerData
Menu
Guides

Does Google News have an API? What to use instead (with Python)

Google retired its News API in 2016. Two ways to get Google News results as data today: the RSS feed and a JSON API, with working Python for both.

CrawlerData teamPublished 8 min read

Contents

If you searched for a Google News API, you probably hit a wall of articles that say it is gone and then list ten products. This guide is shorter. It gives the history in one paragraph, then two ways to get Google News results as data today, each with Python that we ran in October 2026, and a script that turns either into a news monitor.

The short answer

Google does not offer a News API. The old one was part of the Google AJAX Search API family. Google’s developer blog post of 15 January 2016 says the deprecation of the News, Blog, Video, Image and Patent Search APIs was announced in 2011, and that they would stop working on 15 February 2016. It suggested the Custom Search API instead, which has since been closed to new customers and is scheduled to shut down on 1 January 2027.

So you have two practical options:

Google News RSS CrawlerData google-news
Cost Free 1 credit per request
Key needed No Yes (50 free credits for new accounts)
Results per request About 100 items (99 and 100 in our checks) 10 to 22 per page, pages 1 to 10
Link to the story A news.google.com/rss/articles/... URL that redirects The publisher’s own URL
Date Exact pubDate Age text such as 9 hours ago
Snippet Not a separate field Present for some stories, null for others
Country and language gl, hl, ceid country, language

Neither is a guarantee Google will keep the format. The RSS feed is a feed Google happens to serve, not a documented product, so plan for it to change.

Option 1: the Google News RSS feed

The feed lives at https://news.google.com/rss/search. The search term goes in q, and hl, gl and ceid select the edition. Leave those out and Google redirects you, so set them. The standard library is enough:

import urllib.parse
import urllib.request
import xml.etree.ElementTree as ET
from email.utils import parsedate_to_datetime


def google_news_rss(query, country="US", language="en"):
    params = urllib.parse.urlencode({
        "q": query,
        "hl": f"{language}-{country}",
        "gl": country,
        "ceid": f"{country}:{language}",
    })
    req = urllib.request.Request(
        f"https://news.google.com/rss/search?{params}",
        headers={"User-Agent": "Mozilla/5.0"},
    )
    with urllib.request.urlopen(req, timeout=30) as resp:
        root = ET.fromstring(resp.read())
    return [
        {
            "title": item.findtext("title"),
            "source": item.findtext("source"),
            "link": item.findtext("link"),
            "published": parsedate_to_datetime(item.findtext("pubDate")).isoformat(),
        }
        for item in root.findall("./channel/item")
    ]


items = google_news_rss("openai")
print(len(items), items[0]["title"], items[0]["published"])

For a search for openai, the feed returned 99 items in one check and 100 in another. Each has an exact pubDate, which is the feed’s best feature. Two things to know before you build on it:

  • Links are redirects. link is a news.google.com/rss/articles/... URL, not the publisher’s URL. You can follow it in a browser, but to store or de-duplicate by the real article you need an extra step.
  • Titles end with the source. The title ends with - The New York Times, so strip it if you want a clean headline. The source field has the name on its own.

For a hobby project or a quick prototype, the feed is a good answer. If you need the publisher’s link, snippets, pagination beyond one feed, or a contract with a vendor, use the next option.

Option 2: a JSON API

The Google News API on CrawlerData takes a query and returns what the Google News results page shows, as JSON. It uses the same endpoint as every other CrawlerData API, so if you have used the Google Search API the first part is familiar. Put your key in an environment variable:

export CRAWLERDATA_API_KEY="YOUR_API_KEY"

A request is a POST to https://api.crawlerdata.com/v1/jobs with endpoint set to google-news:

import os
import requests

API_URL = "https://api.crawlerdata.com/v1/jobs"
HEADERS = {"Authorization": f"Bearer {os.environ['CRAWLERDATA_API_KEY']}"}


def google_news(query, country="us", language="en", page=1):
    body = {
        "endpoint": "google-news",
        "params": {"query": query, "country": country, "language": language, "page": page},
        "queue": "live",
    }
    resp = requests.post(API_URL, json=body, headers=HEADERS, timeout=180)
    resp.raise_for_status()
    data = resp.json()
    if data["job"]["status"] != "completed":
        raise RuntimeError(f"News search failed: {data['job']['error']}")
    return data["result"]["news_results"]

Each story in news_results looks like this (trimmed from a real response):

{
  "position": 1,
  "title": "OpenAI Engineer, Who Is Charged With Planting Bug, Advised Saudis",
  "link": "https://www.nytimes.com/2026/10/14/technology/openai-engineer-hidden-device-saudi-government.html",
  "source": "The New York Times",
  "date": "9 hours ago",
  "snippet": null,
  "thumbnail": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/..."
}

Four details that matter in practice:

  • link is the publisher’s URL. In the 32 stories we collected for openai, none pointed to news.google.com.
  • snippet is sometimes null. 16 of those 32 stories had one.
  • thumbnail is an inline base64 image. It makes responses large. Drop it unless you display it.
  • date is text, not a timestamp. More on that next.

Turn 9 hours ago into a timestamp

Google shows the age of a story, not its publish time, and the text follows the language you requested. In English it is 8 minutes ago, 9 hours ago, 2 days ago or 1 month ago. This function converts those, and returns None for anything it does not recognise:

import re
from datetime import datetime, timedelta, timezone

UNITS = {"minute": "minutes", "hour": "hours", "day": "days", "week": "weeks"}


def published_at(age, now):
    """'3 hours ago' -> an approximate datetime. Returns None for anything else."""
    m = re.fullmatch(r"(\d+) (minute|hour|day|week|month)s? ago", age or "")
    if not m:
        return None
    n, unit = int(m.group(1)), m.group(2)
    delta = timedelta(days=30 * n) if unit == "month" else timedelta(**{UNITS[unit]: n})
    return now - delta

The result is only as precise as the text: 9 hours ago is somewhere in a one-hour window, and 1 month ago is a rough 30 days. That is fine for sorting and for “last 24 hours” filters. It is not a substitute for an exact timestamp, so if you need one, fetch the article and read its own metadata.

With language="de" the text comes back as vor 12 Stunden or vor 1 Tag, and the function above returns None for all of it. In a German test, none of the ages parsed. If you work in other languages, keep the raw date string and add a lookup for the units you need.

Collect several pages and remove duplicates

Page 1 returned between 10 and 22 stories for the queries we tried, and pages 2 and 3 returned 10 each. Deduplicate by link when you combine them, and drop the thumbnail while you are at it:

def fetch_news(query, pages=2, **kwargs):
    now = datetime.now(timezone.utc)
    seen, rows = set(), []
    for page in range(1, pages + 1):
        for r in google_news(query, page=page, **kwargs):
            if r["link"] in seen:
                continue
            seen.add(r["link"])
            when = published_at(r["date"], now)
            rows.append({
                "title": r["title"],
                "source": r["source"],
                "link": r["link"],
                "snippet": r["snippet"],
                "age": r["date"],
                "published_approx": when.isoformat(timespec="minutes") if when else None,
            })
    return rows


rows = fetch_news("openai", pages=2)

Two pages for openai gave 32 unique stories for 2 credits, and every age parsed. Results change by the minute, so your numbers will differ.

Build a news monitor

The useful version of all this is “tell me about new stories”. Keep a CSV of the links you have seen, and treat anything not in it as new:

import csv


def save_new(rows, path="mentions.csv"):
    known = set()
    if os.path.exists(path):
        with open(path, newline="", encoding="utf-8") as f:
            known = {row["link"] for row in csv.DictReader(f)}
    fresh = [r for r in rows if r["link"] not in known]
    new_file = not os.path.exists(path)
    with open(path, "a", newline="", encoding="utf-8") as f:
        w = csv.DictWriter(f, fieldnames=list(rows[0]))
        if new_file:
            w.writeheader()
        w.writerows(fresh)
    return fresh


for story in save_new(fetch_news("your brand name", pages=2)):
    print(story["age"], story["source"], story["title"], story["link"])

On the first run every story is new. On a second run straight after, save_new returned 0, because everything was already in the file. That is the behaviour you want from a scheduled job: run it from cron or an n8n workflow every hour and send fresh to Slack or email.

encoding="utf-8" is not optional here. Headlines contain characters such as zero-width spaces and curly quotes, and on Windows the default encoding fails on them with a UnicodeEncodeError when you write the file.

What it costs

Every request to google-news is 1 credit, on every queue. Credit packs work out to $1.00 per 1,000 requests on the Starter pack, down to $0.30 per 1,000 on the largest, see pricing. Watching 20 brand terms every hour for a month at one page each is 20 × 24 × 30 = 14,400 credits. Failed requests are not charged.

The RSS feed costs nothing, so if one feed of about 100 items per query covers your case and you don’t need publisher links, it is the cheaper route. Go to the API when you want direct links, snippets, a defined country and language per request, and a vendor you can ask when something changes.

Next steps

You have a feed-based version and an API-based version of Google News in Python, and a monitor that remembers what it has already shown you. The Google News API page lists every parameter with a full example response. If you also want the regular results page for the same terms, the Google Search API returns top_stories and organic results in one request.

Frequently asked questions

Does Google News have an official API?
No. Google announced the deprecation of its News Search API in 2011 and shut it down on 15 February 2016. Google pointed developers to the Custom Search API at the time, and that API is itself closed to new customers and scheduled to shut down on 1 January 2027. There is no official Google News API today.
Is there a free Google News API?
The Google News RSS feed is free and needs no key. It returns about 100 items per query, with links that go through news.google.com instead of straight to the publisher. It is a feed Google serves, not a documented API with a service guarantee.
How do I get Google News results in Python?
Either parse the RSS feed at news.google.com/rss/search with the standard library, or POST to https://api.crawlerdata.com/v1/jobs with endpoint google-news and read result.news_results. Both are shown with working code in this guide.
How many Google News results can I get per request?
With the CrawlerData google-news endpoint, page 1 returned between 10 and 22 stories in our October 2026 checks, and pages 2 and 3 returned 10 each. The page parameter goes from 1 to 10, and each page costs 1 credit.
Does the API give me an exact publish date?
It returns the age text Google shows, such as 9 hours ago, in the language you request. This guide turns English ages into approximate timestamps. The RSS feed has an exact pubDate instead.
Can I filter Google News by country and language?
Yes. The google-news endpoint takes country and language parameters, and the RSS feed takes hl, gl and ceid. Results differ by country, so request the market you care about.
  • Google News API
  • Google News RSS
  • Python
  • News monitoring
  • Tutorial