SERP API data is useful when you want to collect search results programmatically.
You can use it for:
- SEO rank tracking
- competitor monitoring
- content research
- market analysis
- AI search context
- local SEO reports
- automated SERP snapshots
Most SERP APIs return search results in JSON.
That is great for applications, but sometimes you need a simple CSV file.
A CSV file is easy to open in Excel, Google Sheets, Notion, Airtable, or any reporting tool.
In this tutorial, we will build a Python script that:
- Sends a query to a SERP API
- Extracts organic search results
- Normalizes title, URL, snippet, and ranking position
- Saves the results to a CSV file
- Supports multiple keywords
- Adds basic error handling
The workflow looks like this:
Keyword list → SERP API → JSON response → normalized rows → CSV file
Why save SERP API results to CSV?
JSON is great for developers.
CSV is great for reporting.
A SERP API response may include nested data such as organic results, ads, maps, images, videos, People Also Ask, shopping results, and related searches.
But for many workflows, you only need a flat structure like this:
keyword,position,title,url,snippet
best project management software,1,Example Title,https://example.com,Example snippet...
Once the data is in CSV, you can:
- sort rankings by position
- filter by domain
- compare keyword results
- share reports with non-technical teammates
- import data into dashboards
- feed cleaned search data into an LLM
- keep historical SERP snapshots
Install dependencies
We only need two Python packages:
pip install requests python-dotenv
requests is used to call the SERP API.
python-dotenv is used to load your API key from a .env file.
Create a .env file
Create a file named .env:
SERP_API_KEY=your_api_key_here
SERP_API_URL=https://your-serp-api-endpoint.example.com/search
Do not hardcode API keys directly in your Python file.
In this tutorial, we will use a generic SERP API request format. Different providers may use different endpoint URLs or parameter names, so adjust the code based on the API you use.
You can test this pattern with providers such as SerpApi, Serper, SearchAPI, Bright Data, DataForSEO, or Talordata.
Create a keyword list
Create a file named keywords.txt:
best project management software
project management tools
task management app
team collaboration software
Each line is one keyword.
Later, our Python script will read this file and run one SERP API request for each keyword.
Basic SERP API request
Create a file named serp_to_csv.py.
import os
import requests
from dotenv import load_dotenv
load_dotenv()
SERP_API_KEY = os.getenv("SERP_API_KEY")
SERP_API_URL = os.getenv("SERP_API_URL")
def fetch_serp_results(query, location="United States", language="en"):
if not SERP_API_KEY:
raise ValueError("Missing SERP_API_KEY environment variable")
if not SERP_API_URL:
raise ValueError("Missing SERP_API_URL environment variable")
params = {
"api_key": SERP_API_KEY,
"engine": "google",
"q": query,
"location": location,
"language": language,
"output": "json",
}
response = requests.get(SERP_API_URL, params=params, timeout=30)
response.raise_for_status()
return response.json()
The exact parameters depend on your provider.
Some APIs may use:
q
query
gl
hl
country
location
locale
device
engine
The main idea is simple:
send keyword + search settings → receive SERP JSON
Understand the JSON response
A typical SERP API response may look like this:
{
"query": "best project management software",
"organic_results": [
{
"position": 1,
"title": "Best Project Management Software Tools",
"link": "https://example.com",
"snippet": "Compare project management tools, pricing, and features..."
},
{
"position": 2,
"title": "Top Project Management Apps",
"link": "https://example.org",
"snippet": "A guide to project management apps for teams..."
}
]
}
The response structure may vary.
Some APIs use organic_results.
Some use organic.
Some use results.
We can make our parser flexible.
Extract organic results
Add this helper function:
def get_organic_results(data):
possible_keys = [
"organic_results",
"organic",
"results",
]
for key in possible_keys:
value = data.get(key)
if isinstance(value, list):
return value
return []
Now we can extract organic results even if the provider uses slightly different field names.
Normalize each result
Before saving to CSV, we should normalize every result into the same structure.
def normalize_organic_result(keyword, item):
return {
"keyword": keyword,
"position": item.get("position") or item.get("rank") or "",
"title": item.get("title") or "",
"url": item.get("link") or item.get("url") or "",
"snippet": item.get("snippet") or item.get("description") or "",
}
This turns provider-specific JSON into clean rows.
Example output:
{
"keyword": "best project management software",
"position": 1,
"title": "Best Project Management Software Tools",
"url": "https://example.com",
"snippet": "Compare project management tools, pricing, and features..."
}
Save results to CSV
Python has a built-in csv module, so we do not need pandas for this tutorial.
import csv
def save_to_csv(rows, filename="serp_results.csv"):
fieldnames = [
"keyword",
"position",
"title",
"url",
"snippet",
]
with open(filename, mode="w", newline="", encoding="utf-8") as file:
writer = csv.DictWriter(file, fieldnames=fieldnames)
writer.writeheader()
for row in rows:
writer.writerow(row)
This creates a CSV file with one row per organic result.
Load keywords from a file
Now let’s load the keyword list from keywords.txt.
def load_keywords(filename="keywords.txt"):
with open(filename, "r", encoding="utf-8") as file:
return [
line.strip()
for line in file
if line.strip()
]
Full script
Here is the complete version:
import os
import csv
import time
import requests
from dotenv import load_dotenv
load_dotenv()
SERP_API_KEY = os.getenv("SERP_API_KEY")
SERP_API_URL = os.getenv("SERP_API_URL")
def fetch_serp_results(query, location="United States", language="en"):
if not SERP_API_KEY:
raise ValueError("Missing SERP_API_KEY environment variable")
if not SERP_API_URL:
raise ValueError("Missing SERP_API_URL environment variable")
params = {
"api_key": SERP_API_KEY,
"engine": "google",
"q": query,
"location": location,
"language": language,
"output": "json",
}
response = requests.get(SERP_API_URL, params=params, timeout=30)
response.raise_for_status()
return response.json()
def get_organic_results(data):
possible_keys = [
"organic_results",
"organic",
"results",
]
for key in possible_keys:
value = data.get(key)
if isinstance(value, list):
return value
return []
def normalize_organic_result(keyword, item):
return {
"keyword": keyword,
"position": item.get("position") or item.get("rank") or "",
"title": item.get("title") or "",
"url": item.get("link") or item.get("url") or "",
"snippet": item.get("snippet") or item.get("description") or "",
}
def load_keywords(filename="keywords.txt"):
with open(filename, "r", encoding="utf-8") as file:
return [
line.strip()
for line in file
if line.strip()
]
def save_to_csv(rows, filename="serp_results.csv"):
fieldnames = [
"keyword",
"position",
"title",
"url",
"snippet",
]
with open(filename, mode="w", newline="", encoding="utf-8") as file:
writer = csv.DictWriter(file, fieldnames=fieldnames)
writer.writeheader()
for row in rows:
writer.writerow(row)
def collect_serp_data(keywords, location="United States", language="en", delay=1):
all_rows = []
for keyword in keywords:
print(f"Fetching SERP results for: {keyword}")
try:
data = fetch_serp_results(
query=keyword,
location=location,
language=language,
)
organic_results = get_organic_results(data)
for item in organic_results:
row = normalize_organic_result(keyword, item)
all_rows.append(row)
except requests.RequestException as error:
print(f"Request failed for keyword: {keyword}")
print(error)
all_rows.append({
"keyword": keyword,
"position": "",
"title": "",
"url": "",
"snippet": "",
})
time.sleep(delay)
return all_rows
if __name__ == "__main__":
keywords = load_keywords("keywords.txt")
rows = collect_serp_data(
keywords=keywords,
location="United States",
language="en",
delay=1,
)
save_to_csv(rows, "serp_results.csv")
print("Saved SERP results to serp_results.csv")
Run the script:
python serp_to_csv.py
You should get a file named:
serp_results.csv
Example CSV output
The CSV file may look like this:
keyword,position,title,url,snippet
best project management software,1,Best Project Management Software Tools,https://example.com,Compare tools and pricing...
best project management software,2,Top Project Management Apps,https://example.org,A guide for teams...
project management tools,1,Project Management Tools Compared,https://example.net,Find the right tool...
Now your SERP API results are ready for Excel, Google Sheets, or further processing.
Add domain extraction
For SEO workflows, it is often useful to extract the domain from each URL.
Add this function:
from urllib.parse import urlparse
def extract_domain(url):
if not url:
return ""
parsed = urlparse(url)
domain = parsed.netloc.lower()
if domain.startswith("www."):
domain = domain[4:]
return domain
Update the normalized row:
def normalize_organic_result(keyword, item):
url = item.get("link") or item.get("url") or ""
return {
"keyword": keyword,
"position": item.get("position") or item.get("rank") or "",
"title": item.get("title") or "",
"url": url,
"domain": extract_domain(url),
"snippet": item.get("snippet") or item.get("description") or "",
}
Then update your CSV fieldnames:
fieldnames = [
"keyword",
"position",
"title",
"url",
"domain",
"snippet",
]
This makes the CSV more useful for competitor analysis.
Add location and language columns
SERP results change by location and language.
A keyword may show different rankings in the United States, the United Kingdom, Canada, or Singapore.
Add location and language to each row:
def normalize_organic_result(keyword, item, location, language):
url = item.get("link") or item.get("url") or ""
return {
"keyword": keyword,
"location": location,
"language": language,
"position": item.get("position") or item.get("rank") or "",
"title": item.get("title") or "",
"url": url,
"domain": extract_domain(url),
"snippet": item.get("snippet") or item.get("description") or "",
}
Then call it like this:
row = normalize_organic_result(
keyword=keyword,
item=item,
location=location,
language=language,
)
Your CSV now becomes more useful for international SEO and local SEO reporting.
Save top 10 results only
Many rank tracking workflows only need the top 10 organic results.
You can limit the results before writing rows:
for item in organic_results[:10]:
row = normalize_organic_result(keyword, item)
all_rows.append(row)
For deeper competitor monitoring, you may want the top 20, top 50, or top 100 results depending on your API settings.
Save multiple SERP sections
Organic results are only one part of the SERP.
Depending on your provider, the response may also include:
- ads
- local results
- maps
- shopping results
- news
- images
- videos
- related questions
- related searches
You can save a result_type column to distinguish different sections.
Example:
def normalize_result(keyword, item, result_type):
return {
"keyword": keyword,
"result_type": result_type,
"position": item.get("position") or item.get("rank") or "",
"title": item.get("title") or item.get("question") or "",
"url": item.get("link") or item.get("url") or "",
"snippet": item.get("snippet") or item.get("description") or item.get("answer") or "",
}
Then collect multiple sections:
sections = {
"organic": data.get("organic_results", []),
"ads": data.get("ads", []),
"local": data.get("local_results", []),
"related_questions": data.get("related_questions", []),
}
for result_type, items in sections.items():
if not isinstance(items, list):
continue
for item in items:
row = normalize_result(keyword, item, result_type)
all_rows.append(row)
This gives you a wider SERP snapshot.
Append data instead of overwriting
The earlier save_to_csv function overwrites the CSV every time.
For daily tracking, you may want to append new rows.
def append_to_csv(rows, filename="serp_results.csv"):
fieldnames = [
"keyword",
"position",
"title",
"url",
"snippet",
]
file_exists = os.path.isfile(filename)
with open(filename, mode="a", newline="", encoding="utf-8") as file:
writer = csv.DictWriter(file, fieldnames=fieldnames)
if not file_exists:
writer.writeheader()
for row in rows:
writer.writerow(row)
Now the script can add new results without deleting old data.
For historical tracking, add a date column:
from datetime import datetime
def current_date():
return datetime.utcnow().strftime("%Y-%m-%d")
Then include:
"date": current_date()
This lets you track ranking changes over time.
Using CSV data with an LLM
Once SERP data is saved to CSV, you can also use it in AI workflows.
For example, you can summarize competitor visibility:
You are an SEO analyst.
Here is a CSV of Google SERP results for our target keywords.
Summarize:
- which domains appear most often
- which keywords are dominated by competitors
- which pages rank in the top 3
- which keywords may need new content
- what actions we should take next
The key is not to send raw HTML to the model.
Clean CSV or structured JSON gives the LLM better context.
Common improvements
This script is a starting point.
You can improve it by adding:
- retry logic
- rate limit handling
- proxy or provider-specific settings
- SQLite or Postgres storage
- scheduled daily runs
- competitor domain matching
- local SEO city tracking
- Google Maps result extraction
- dashboard visualization
- AI-generated summaries
- Slack or email alerts
For example, a daily report could say:
example.com moved from position 8 to position 4 for "project management tools".
That is much more useful than a one-time SERP export.
What to check before choosing a SERP API
Before choosing a SERP API provider, test it with your real queries.
Check:
- Does it return clean JSON?
- Are title, URL, snippet, and position available?
- Does it support country or city targeting?
- Can you request HTML for debugging?
- Are rich SERP features included when needed?
- Are failed requests billed?
- How much cleanup does your code need?
- Does the response format fit your reporting workflow?
Different providers have different strengths.
Some are better for simple Google Search JSON.
Some are better for SEO rank tracking.
Some are better for enterprise-scale data collection.
Some are better for AI agents and RAG workflows that need fresh search context.
Final thoughts
Saving SERP API results to CSV is one of the simplest ways to turn search results into useful data.
The basic process is:
SERP API → JSON response → normalized rows → CSV file
Once the data is in CSV, you can analyze it in spreadsheets, build reports, compare domains, monitor rankings, or feed cleaned search context into an LLM.
You do not need a full SEO platform to get started.
A Python script, a keyword list, and a SERP API are enough to build a practical search data workflow.
For testing this kind of workflow, Talordata is one SERP API worth comparing. It supports structured SERP data, JSON / HTML response formats, geo-targeted results, and search workflows for SEO monitoring, competitor tracking, market research, and AI agents.
Talordata also offers 1,000 free API responses after signup, which is enough to test real keywords before choosing a provider.
Top comments (0)