Tutorial

How to Batch SEO Rank Checks Overnight with an API

Run 4,000+ keyword rank checks overnight using a search API and Python. Schedule, batch, store results in CSV, and wake up to fresh SERP data.

Batch overnight rank checking lets you track thousands of keywords without burning daytime API quota or slowing down production systems. The pattern is straightforward: load keywords from a CSV, loop through them with a short delay, store positions in a results file, and schedule the script with cron. Using the Scavio API at $0.005 per credit, a 4,000-keyword nightly run costs $20 -- compared to $60+ on SerpAPI or $499.95+ on Semrush Business. This tutorial builds a complete overnight batch pipeline in Python.

Prerequisites

  • Python 3.9+ installed
  • requests and csv modules (both in stdlib except requests)
  • A Scavio API key from scavio.dev
  • A CSV file with keywords to track

Walkthrough

Step 1: Prepare your keyword CSV

Create a CSV with columns for keyword and target_url. The script will check each keyword and record where the target URL ranks.

Python
import csv

keywords = [
    {'keyword': 'best crm for startups', 'target_url': 'yoursite.com'},
    {'keyword': 'crm pricing comparison 2026', 'target_url': 'yoursite.com'},
    {'keyword': 'hubspot alternative small business', 'target_url': 'yoursite.com'},
]

with open('keywords.csv', 'w', newline='') as f:
    writer = csv.DictWriter(f, fieldnames=['keyword', 'target_url'])
    writer.writeheader()
    writer.writerows(keywords)
print(f'Wrote {len(keywords)} keywords')

Step 2: Build the rank check function

For each keyword, call the search API and scan organic results for your target domain. Return the position or None if not found in the top results.

Python
import requests, os

API_KEY = os.environ['SCAVIO_API_KEY']

def check_rank(keyword: str, target_domain: str) -> dict:
    resp = requests.post('https://api.scavio.dev/api/v1/search',
        headers={'x-api-key': API_KEY, 'Content-Type': 'application/json'},
        json={'query': keyword, 'country_code': 'us'})
    resp.raise_for_status()
    for r in resp.json().get('organic_results', []):
        if target_domain in r.get('link', ''):
            return {'keyword': keyword, 'position': r['position'], 'url': r['link']}
    return {'keyword': keyword, 'position': None, 'url': None}

Step 3: Batch through all keywords with rate limiting

Read the CSV, check each keyword with a 0.5-second delay to stay well within rate limits, and collect results. For 4,000 keywords this takes about 35 minutes.

Python
import time

def batch_rank_check(csv_path: str) -> list:
    results = []
    with open(csv_path) as f:
        reader = csv.DictReader(f)
        rows = list(reader)
    print(f'Checking {len(rows)} keywords...')
    for i, row in enumerate(rows):
        result = check_rank(row['keyword'], row['target_url'])
        results.append(result)
        if (i + 1) % 100 == 0:
            print(f'  Checked {i + 1}/{len(rows)}')
        time.sleep(0.5)
    return results

Step 4: Save results with timestamp

Write results to a dated CSV so you can track rank changes over time. Each nightly run produces one file.

Python
from datetime import date

def save_results(results: list) -> str:
    filename = f'ranks_{date.today().isoformat()}.csv'
    with open(filename, 'w', newline='') as f:
        writer = csv.DictWriter(f, fieldnames=['keyword', 'position', 'url'])
        writer.writeheader()
        writer.writerows(results)
    ranked = [r for r in results if r['position'] is not None]
    print(f'Saved {len(results)} results to {filename}')
    print(f'  Ranking: {len(ranked)}, Not found: {len(results) - len(ranked)}')
    return filename

Step 5: Schedule with cron for nightly runs

Add a cron entry to run the script at 2 AM daily. The script finishes well before morning so results are ready when you start work.

Python
# Add to crontab (crontab -e):
# 0 2 * * * cd /path/to/project && SCAVIO_API_KEY=your_key python batch_ranks.py

# Full script entry point:
if __name__ == '__main__':
    results = batch_rank_check('keywords.csv')
    save_results(results)
    cost = len(results) * 0.005
    print(f'Total cost: ${cost:.2f}')

Python Example

Python
import os, csv, time, requests
from datetime import date

API_KEY = os.environ['SCAVIO_API_KEY']
ENDPOINT = 'https://api.scavio.dev/api/v1/search'

def check_rank(keyword: str, target: str) -> dict:
    resp = requests.post(ENDPOINT,
        headers={'x-api-key': API_KEY, 'Content-Type': 'application/json'},
        json={'query': keyword, 'country_code': 'us'})
    resp.raise_for_status()
    for r in resp.json().get('organic_results', []):
        if target in r.get('link', ''):
            return {'keyword': keyword, 'position': r['position'], 'url': r['link']}
    return {'keyword': keyword, 'position': None, 'url': None}

def main():
    with open('keywords.csv') as f:
        rows = list(csv.DictReader(f))
    results = []
    for i, row in enumerate(rows):
        results.append(check_rank(row['keyword'], row['target_url']))
        if (i + 1) % 100 == 0:
            print(f'Checked {i + 1}/{len(rows)}')
        time.sleep(0.5)
    filename = f'ranks_{date.today()}.csv'
    with open(filename, 'w', newline='') as f:
        w = csv.DictWriter(f, fieldnames=['keyword', 'position', 'url'])
        w.writeheader()
        w.writerows(results)
    ranked = sum(1 for r in results if r['position'])
    print(f'{ranked}/{len(results)} ranking, cost: ${len(results) * 0.005:.2f}')

if __name__ == '__main__':
    main()

JavaScript Example

JavaScript
const fs = require('fs');
const API_KEY = process.env.SCAVIO_API_KEY;

async function checkRank(keyword, target) {
  const resp = await fetch('https://api.scavio.dev/api/v1/search', {
    method: 'POST',
    headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ query: keyword, country_code: 'us' })
  });
  const data = await resp.json();
  const match = (data.organic_results || []).find(r => r.link?.includes(target));
  return { keyword, position: match?.position || null, url: match?.link || null };
}

async function main() {
  const lines = fs.readFileSync('keywords.csv', 'utf8').trim().split('\n').slice(1);
  const results = [];
  for (const line of lines) {
    const [keyword, target] = line.split(',');
    results.push(await checkRank(keyword, target));
    await new Promise(r => setTimeout(r, 500));
  }
  const ranked = results.filter(r => r.position).length;
  console.log(`${ranked}/${results.length} ranking, cost: $${(results.length * 0.005).toFixed(2)}`);
}

main().catch(console.error);

Expected Output

JSON
Checking 4000 keywords...
  Checked 100/4000
  Checked 200/4000
  ...
  Checked 4000/4000
Saved 4000 results to ranks_2026-05-13.csv
  Ranking: 2847, Not found: 1153
Total cost: $20.00

Related Tutorials

Frequently Asked Questions

Most developers complete this tutorial in 15 to 30 minutes. You will need a Scavio API key (free tier works) and a working Python or JavaScript environment.

Python 3.9+ installed. requests and csv modules (both in stdlib except requests). A Scavio API key from scavio.dev. A CSV file with keywords to track. A Scavio API key gives you 250 free credits per month.

Yes. The free tier includes 250 credits per month, which is more than enough to complete this tutorial and prototype a working solution.

Scavio has a native LangChain package (langchain-scavio), an MCP server, and a plain REST API that works with any HTTP client. This tutorial uses the raw REST API, but you can adapt to your framework of choice.

Start Building

Run 4,000+ keyword rank checks overnight using a search API and Python. Schedule, batch, store results in CSV, and wake up to fresh SERP data.