DEV Community

Cecilia Hill
Cecilia Hill

Posted on

How to Save SERP API Results to CSV with Python

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:

  1. Sends a query to a SERP API
  2. Extracts organic search results
  3. Normalizes title, URL, snippet, and ranking position
  4. Saves the results to a CSV file
  5. Supports multiple keywords
  6. Adds basic error handling

The workflow looks like this:

Keyword list → SERP API → JSON response → normalized rows → CSV file
Enter fullscreen mode Exit fullscreen mode

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...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

The exact parameters depend on your provider.

Some APIs may use:

q
query
gl
hl
country
location
locale
device
engine
Enter fullscreen mode Exit fullscreen mode

The main idea is simple:

send keyword + search settings → receive SERP JSON
Enter fullscreen mode Exit fullscreen mode

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..."
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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 []
Enter fullscreen mode Exit fullscreen mode

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 "",
    }
Enter fullscreen mode Exit fullscreen mode

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..."
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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()
        ]
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

Run the script:

python serp_to_csv.py
Enter fullscreen mode Exit fullscreen mode

You should get a file named:

serp_results.csv
Enter fullscreen mode Exit fullscreen mode

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...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 "",
    }
Enter fullscreen mode Exit fullscreen mode

Then update your CSV fieldnames:

fieldnames = [
    "keyword",
    "position",
    "title",
    "url",
    "domain",
    "snippet",
]
Enter fullscreen mode Exit fullscreen mode

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 "",
    }
Enter fullscreen mode Exit fullscreen mode

Then call it like this:

row = normalize_organic_result(
    keyword=keyword,
    item=item,
    location=location,
    language=language,
)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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 "",
    }
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

Then include:

"date": current_date()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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".
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)