Santaji GadePython, DevelopmentYesterday6 Views

This guide builds google search console data pulls with python from scratch, authenticating with the Search Console API and exporting query, click, and impression data automatically.
Table of Contents
ToggleThe Search Console interface caps most reports at a thousand rows and quietly ages data out after around sixteen months. Google search console data pulls with python remove both limits and turn a manual export habit into a running archive.
A site with a few thousand ranking queries hits that thousand row wall almost immediately. Whatever did not fit in the export just never gets analyzed.
The sixteen month window causes a quieter problem. Ask what changed since a redesign two years ago, and the interface simply cannot answer, because the data behind that point is already gone.
Google search console data pulls with python solve both problems at once. A script does not care whether a query is row four or row forty thousand, and once it starts saving results daily, the sixteen month ceiling stops mattering.
This guide authenticates against the Search Console API, pulls query and page performance data, works past the API's own row limit, and saves everything somewhere that outlives the dashboard's memory.
None of it needs anything exotic. A Google Cloud project, a Python environment, and about forty lines of working code cover the whole thing.
The Search Console dashboard is genuinely useful for a quick look. It becomes a poor research tool the moment you need more history or more rows than the interface is willing to show.
Comparing this quarter to the same quarter two years ago is a common request that the UI simply cannot fulfill once the data has aged out of its visible window.
Exporting by hand every week is also easy to forget. A missed week is not just a gap, it is a permanent gap, since that data is not sitting somewhere waiting to be pulled later.
Pairing this with a wider Search Console configuration review is worth doing once the automated pulls are running, since a script surfaces patterns a manual glance at the dashboard tends to miss.
Before any Python runs, the Search Console API needs to be turned on for a Google Cloud project, and something needs permission to use it.
A service account is the cleanest option for a script that runs unattended, since it never depends on a personal login session expiring.
.gitignore immediately, the same as any other credential.Skipping that third step is the most common reason a first script fails. The API call authenticates fine and then returns an empty result, because the account making the request was never actually added to the property.
# create an isolated environment so package versions don't clash
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install google-api-python-client google-auth pandas
The google-api-python-client package builds the actual API connection, google-auth handles the service account authentication, and pandas holds the results once they come back.
With the service account key in hand, connecting takes a few lines. This function builds a reusable client that every later script in this guide calls.
from google.oauth2 import service_account
from googleapiclient.discovery import build
SCOPES = ["https://www.googleapis.com/auth/webmasters.readonly"]
KEY_FILE = "service_account.json"
def get_gsc_service():
credentials = service_account.Credentials.from_service_account_file(
KEY_FILE, scopes=SCOPES
)
return build("searchconsole", "v1", credentials=credentials)
The read only scope is intentional. A reporting script has no reason to ever modify a property's settings, so limiting what the credential can do keeps the blast radius small if the key ever leaks.
The full setup, including where each button lives inside the Google Cloud console, is also documented step by step on jcchouinard.com if a screenshot heavy walkthrough helps.
Every real implementation of google search console data pulls with python starts at the same endpoint. The searchanalytics.query call accepts a date range and a list of dimensions, then returns clicks, impressions, position, and click through rate for each row.
import pandas as pd
from gsc_client import get_gsc_service
def pull_search_data(site_url: str, start_date: str, end_date: str, row_limit: int = 25000) -> pd.DataFrame:
service = get_gsc_service()
request = {
"startDate": start_date,
"endDate": end_date,
"dimensions": ["query", "page"],
"rowLimit": row_limit,
}
response = service.searchanalytics().query(siteUrl=site_url, body=request).execute()
rows = response.get("rows", [])
records = [{
"query": row["keys"][0],
"page": row["keys"][1],
"clicks": row["clicks"],
"impressions": row["impressions"],
"ctr": row["ctr"],
"position": row["position"],
} for row in rows]
return pd.DataFrame(records)
Splitting queries from branded queries becomes trivial once this data sits in a dataframe instead of an export you have to filter by hand every time.
A single request tops out at 25,000 rows, and a busy site can easily have more query and page combinations than that in a single month. The startRow parameter is how you keep going past it.
import time
import pandas as pd
from gsc_client import get_gsc_service
PAGE_SIZE = 25000
def pull_all_rows(site_url: str, start_date: str, end_date: str) -> pd.DataFrame:
service = get_gsc_service()
all_rows = []
start_row = 0
while True:
request = {
"startDate": start_date,
"endDate": end_date,
"dimensions": ["query", "page"],
"rowLimit": PAGE_SIZE,
"startRow": start_row,
}
response = service.searchanalytics().query(siteUrl=site_url, body=request).execute()
rows = response.get("rows", [])
if not rows:
break
all_rows.extend(rows)
start_row += PAGE_SIZE
time.sleep(1) # stay well under the daily quota
return pd.DataFrame(all_rows)
The loop stops the moment a page comes back empty, which is the API's own signal that there is nothing left to fetch. That is a cleaner stopping condition than guessing at a total row count in advance.
Most agencies and multi site owners are not pulling data for one property. The same function works for many, as long as the service account has been added to each one.
from datetime import date, timedelta
from pull_all_rows import pull_all_rows
SITES = [
"https://brandella.in/",
"sc-domain:example-client.com",
]
def run_daily_pull():
yesterday = (date.today() - timedelta(days=3)).isoformat()
for site in SITES:
df = pull_all_rows(site, yesterday, yesterday)
df["site"] = site
df["pull_date"] = yesterday
df.to_csv(f"data/{site.replace('/', '_')}_{yesterday}.csv", index=False)
Notice the three day offset in the date calculation. Search Console data is not final the moment a click happens, and pulling too close to today usually returns partial numbers that revise upward later.
Estimate how many paginated API calls a pull needs, based on the API's 25,000 row limit per request.
Saving each day to its own CSV works, but it makes trend analysis harder than it needs to be. Once google search console data pulls with python are running daily, appending every pull into one growing table is usually the better long term shape.
import os
import pandas as pd
ARCHIVE_PATH = "data/gsc_archive.csv"
def append_to_archive(new_df: pd.DataFrame):
if os.path.exists(ARCHIVE_PATH):
existing = pd.read_csv(ARCHIVE_PATH)
combined = pd.concat([existing, new_df]).drop_duplicates(
subset=["site", "pull_date", "query", "page"]
)
else:
combined = new_df
combined.to_csv(ARCHIVE_PATH, index=False)
The drop_duplicates call matters more than it looks. A script that reruns after a partial failure should never double count a day it already saved successfully.
name: Daily Search Console Pull
on:
schedule:
- cron: "0 8 * * *" # every day at 08:00 UTC
workflow_dispatch: {}
jobs:
pull_data:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install -r requirements.txt
- run: python multi_property.py
A daily run through GitHub Actions means the archive keeps growing on its own, well past whatever window the Search Console interface happens to keep visible.
A few details separate a script that runs once from one that keeps running reliably every day without someone checking on it.
None of these are difficult to handle once you know to expect them. They are simply the parts that a first script, written in an afternoon, tends to miss. Search Engine Journal has covered similar lag and quota issues across other Google reporting APIs, and the same patience pays off here too.
JC Chouinard, a Python and SEO practitioner whose tutorials at jcchouinard.com are widely referenced in the technical SEO community, published a walkthrough covering exactly the authentication step this guide starts with.
Video credit: JC Chouinard, jcchouinard.com.
The credential setup shown there is the same service account pattern used throughout this guide, just walked through one click at a time inside the Google Cloud console.
No. The Search Console API only needs a verified Search Console property and a Google Cloud project with the API enabled. It has no connection to Google Ads at all.
This almost always means the service account was never added as a user on the Search Console property. Add its email address under Settings and Users and Permissions, and the same request will start returning rows.
By paginating with the startRow parameter. Each request returns up to 25,000 rows, and looping with an increasing startRow value until an empty response comes back collects everything available.
The API generally exposes a similar window to what the interface shows, roughly sixteen months. The value of automating the pull is capturing new data going forward so future gaps never happen again.
A service account is usually better for anything scheduled and unattended, since it never expires the way a personal OAuth session can. OAuth makes more sense for a script you run interactively yourself.
Row caps and a rolling history window make manual exports a poor long term data source.
The service account must be added to the property, or every later request comes back empty.
The 25,000 row cap per request means most real pulls need a loop, not a single call.
Pulling with a few days of delay avoids saving numbers that have not finished settling.
Appending daily pulls into one deduplicated table builds history the dashboard cannot offer.
A daily GitHub Actions run keeps the archive current without anyone remembering to click export.
Pair google search console data pulls with python and a broader reporting workflow to keep every metric that matters within reach.









