ScavioScavio
ProductPricingDocs
Sign InGet Started
  1. Home
  2. Tutorials
  3. How to Run a Bulk SERP Ranking Study with a Search API
Tutorial

How to Run a Bulk SERP Ranking Study with a Search API

Stop arguing ranking factors from anecdote. Pull thousands of real Google SERPs via API, measure a feature per position, and compute the correlation yourself.

Get Free API KeyAPI Docs

To run a bulk SERP ranking study, collect a list of keywords, fetch the organic results for each one with POST /api/v1/google, pull the feature you care about (say URL character length) at every ranking position, then aggregate by position bucket and compute a correlation. That's it. A German SEO once tested whether short URLs rank better across 27,000 SERPs and the effect was tiny. You can settle questions like that with real data instead of a hot take. This tutorial walks the whole loop: keyword list, paced API calls, feature extraction, aggregation, and a Pearson correlation. Correlation isn't causation, and SERPs have confounders, so read the number as a hint, not proof.

Prerequisites

  • A Scavio API key (50 free credits on signup; each Google call with light_request:true costs 1 credit)
  • Python 3.9+ or Node 18+ with the requests/fetch ability to POST JSON
  • A list of 50 to a few thousand keywords in your niche (a CSV or plain text file)
  • Basic comfort reading a correlation coefficient (a number between -1 and 1)

Walkthrough

Step 1: Build your keyword list

A study is only as honest as its sample. Pull real keywords from your niche rather than cherry-picking. Load them from a file so the run is reproducible. Aim for at least a few hundred if you want a stable correlation.

Python
# keywords.txt has one query per line
with open('keywords.txt') as f:
    keywords = [line.strip() for line in f if line.strip()]
print(f'Loaded {len(keywords)} keywords')

Step 2: Fetch one SERP and inspect the shape

Before looping over thousands, call the endpoint once and look at the response. Use light_request:true to keep each call at 1 credit. You get organic results, each with position, title, link, and snippet.

Python
import requests

API_KEY = 'YOUR_API_KEY'
resp = requests.post(
    'https://api.scavio.dev/api/v1/google',
    headers={'Authorization': f'Bearer {API_KEY}'},
    json={'query': 'best running shoes', 'light_request': True},
)
data = resp.json()
for r in data['organic_results'][:3]:
    print(r['position'], r['link'])

Step 3: Extract the feature per position

Here the feature is URL character length, but swap in title length, domain depth, or HTTPS presence as your hypothesis demands. Record one (position, value) pair per result so you can correlate later.

Python
def url_length(result):
    return len(result['link'])

def extract_pairs(serp):
    return [(r['position'], url_length(r)) for r in serp['organic_results']]

Step 4: Loop over keywords and respect the rate limit

Free keys allow 1 request per second; the $30 plan allows 2. Sleep between calls so you don't get throttled. Wrap each call in try/except so one bad query doesn't kill the whole run.

Python
import time

all_pairs = []
for kw in keywords:
    try:
        resp = requests.post(
            'https://api.scavio.dev/api/v1/google',
            headers={'Authorization': f'Bearer {API_KEY}'},
            json={'query': kw, 'light_request': True},
        )
        all_pairs += extract_pairs(resp.json())
    except Exception as e:
        print(f'skip {kw}: {e}')
    time.sleep(1.0)  # 1 req/sec on the free plan

Step 5: Aggregate by position bucket

Group results into buckets (1-3, 4-6, 7-10) and average the feature in each. This shows the trend at a glance before you trust any single number.

Python
from collections import defaultdict

buckets = defaultdict(list)
for pos, val in all_pairs:
    if pos <= 3: buckets['1-3'].append(val)
    elif pos <= 6: buckets['4-6'].append(val)
    else: buckets['7-10'].append(val)

for name in ['1-3', '4-6', '7-10']:
    vals = buckets[name]
    print(name, round(sum(vals)/len(vals), 1))

Step 6: Compute the correlation

A Pearson coefficient between position and the feature tells you the direction and strength. Near 0 means no relationship; the German URL-length study landed close to 0. Remember: confounders abound and correlation isn't causation.

Python
import statistics

def pearson(xs, ys):
    mx, my = statistics.mean(xs), statistics.mean(ys)
    num = sum((x-mx)*(y-my) for x, y in zip(xs, ys))
    den = (sum((x-mx)**2 for x in xs) * sum((y-my)**2 for y in ys)) ** 0.5
    return num/den if den else 0.0

positions = [p for p, _ in all_pairs]
values = [v for _, v in all_pairs]
print('correlation:', round(pearson(positions, values), 3))

Python Example

Python
import time
import statistics
from collections import defaultdict

import requests

API_KEY = 'YOUR_API_KEY'
ENDPOINT = 'https://api.scavio.dev/api/v1/google'


def fetch_serp(query):
    resp = requests.post(
        ENDPOINT,
        headers={'Authorization': f'Bearer {API_KEY}'},
        json={'query': query, 'light_request': True},
    )
    resp.raise_for_status()
    return resp.json()


def url_length(result):
    return len(result['link'])


def pearson(xs, ys):
    mx, my = statistics.mean(xs), statistics.mean(ys)
    num = sum((x - mx) * (y - my) for x, y in zip(xs, ys))
    den = (sum((x - mx) ** 2 for x in xs) * sum((y - my) ** 2 for y in ys)) ** 0.5
    return num / den if den else 0.0


def main():
    with open('keywords.txt') as f:
        keywords = [line.strip() for line in f if line.strip()]
    print(f'Loaded {len(keywords)} keywords')

    pairs = []
    for kw in keywords:
        try:
            serp = fetch_serp(kw)
            pairs += [(r['position'], url_length(r)) for r in serp['organic_results']]
        except Exception as e:
            print(f'skip {kw}: {e}')
        time.sleep(1.0)  # 1 req/sec on the free plan; 0.5 on the $30 plan

    buckets = defaultdict(list)
    for pos, val in pairs:
        if pos <= 3:
            buckets['1-3'].append(val)
        elif pos <= 6:
            buckets['4-6'].append(val)
        else:
            buckets['7-10'].append(val)

    print('avg URL length by position bucket:')
    for name in ['1-3', '4-6', '7-10']:
        vals = buckets[name]
        if vals:
            print(f'  {name}: {round(sum(vals) / len(vals), 1)}')

    positions = [p for p, _ in pairs]
    values = [v for _, v in pairs]
    print('correlation (position vs url length):', round(pearson(positions, values), 3))


if __name__ == '__main__':
    main()

JavaScript Example

JavaScript
import fs from 'node:fs';

const API_KEY = 'YOUR_API_KEY';
const ENDPOINT = 'https://api.scavio.dev/api/v1/google';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function fetchSerp(query) {
  const resp = await fetch(ENDPOINT, {
    method: 'POST',
    headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ query, light_request: true }),
  });
  return resp.json();
}

function pearson(xs, ys) {
  const mx = xs.reduce((a, b) => a + b, 0) / xs.length;
  const my = ys.reduce((a, b) => a + b, 0) / ys.length;
  let num = 0, dx = 0, dy = 0;
  for (let i = 0; i < xs.length; i++) {
    num += (xs[i] - mx) * (ys[i] - my);
    dx += (xs[i] - mx) ** 2;
    dy += (ys[i] - my) ** 2;
  }
  const den = Math.sqrt(dx * dy);
  return den ? num / den : 0;
}

const keywords = fs.readFileSync('keywords.txt', 'utf8').split('\n').map((s) => s.trim()).filter(Boolean);
const pairs = [];
for (const kw of keywords) {
  try {
    const serp = await fetchSerp(kw);
    for (const r of serp.organic_results) pairs.push([r.position, r.link.length]);
  } catch (e) {
    console.log(`skip ${kw}: ${e}`);
  }
  await sleep(1000); // 1 req/sec on the free plan
}

const positions = pairs.map((p) => p[0]);
const values = pairs.map((p) => p[1]);
console.log('correlation:', pearson(positions, values).toFixed(3));

Expected Output

JSON
Loaded 500 keywords
avg URL length by position bucket:
  1-3: 48.2
  4-6: 51.7
  7-10: 53.9
correlation (position vs url length): 0.072

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.

    A Scavio API key (50 free credits on signup; each Google call with light_request:true costs 1 credit). Python 3.9+ or Node 18+ with the requests/fetch ability to POST JSON. A list of 50 to a few thousand keywords in your niche (a CSV or plain text file). Basic comfort reading a correlation coefficient (a number between -1 and 1). A Scavio API key gives you 50 free credits on signup.

    Yes. The free tier includes 50 credits on signup, 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.

    Related Resources

    Best Of

    Best Google Maps Business Data APIs (May 2026)

    Read more
    Best Of

    Best Queue-Based SERP API in 2026

    Read more
    Solution

    Google Ads Data from SERP APIs

    Read more
    Use Case

    SERP API Procurement Comparison

    Read more
    Comparison

    Google Places API vs SERP Local Pack API

    Read more
    Comparison

    Semrush API vs Raw SERP API

    Read more

    Start Building

    Stop arguing ranking factors from anecdote. Pull thousands of real Google SERPs via API, measure a feature per position, and compute the correlation yourself.

    Get Free API KeyRead the Docs
    ScavioScavio

    Real-time search API for AI agents. Search every platform, not just Google.

    Product

    • Features
    • Pricing
    • Dashboard
    • Affiliates

    Developers

    • Documentation
    • API Reference
    • Quickstart
    • MCP Integration
    • Python SDK

    Alternatives

    • Tavily Alternative
    • SerpAPI Alternative
    • Firecrawl Alternative
    • Exa Alternative

    Tools

    • JSON Formatter
    • cURL to Code
    • Token Counter
    • All Tools

    © 2026 Scavio. All rights reserved.

    Featured on TAAFT
    Terms of ServicePrivacy Policy