# Pytrends keyword research: 429 ceiling at 12 queries

> Google Trends keyword research with pytrends: the urllib3 method_whitelist trap, a 429 on the 13th query, and two findings that flipped the plan.

**Canonical URL**: https://agentcookbooks.com/blog/pytrends-keyword-research-12-queries-before-429/

**Published**: 2026-05-21

**Tags**: claude-code, seo

---

Wanted Google Trends to help pick the next two blog posts. `pytrends` installed clean. Script wrote in a few minutes. First run: all four batches failed identically with a `urllib3` `TypeError` that the error message doesn't explain. Patched it. Second run: three batches succeeded, the fourth got 429'd on the thirteenth HTTP request. And the data we did get flipped two of our prior SEO recommendations — one of which we'd been planning to write a post around.

## What I ran

Invoked a project-local `pytrends-seo` skill (the `SKILL.md` lives in `.claude/skills/pytrends-seo/` — gitignored, project-private). The skill wraps four common pytrends use cases: comparison via `interest_over_time()`, long-tail discovery via `related_queries()`, seasonality, and regional comparison.

Four-batch plan, scoped to gaps identified in an internal SEO findings memo from launch day:

- **Batch A** (wiki verticals): `claude code hooks`, `claude code skills`, `claude code mcp`, `claude code subagents`, `claude code agent`
- **Batch B** (MCP-specific): `mcp server`, `model context protocol`, `claude mcp`, `mcp claude code`
- **Batch C** (marketing/CRO): `claude code marketing`, `claude code cro`, `claude code copywriting`
- **Batch D** (competitive): `cursor vs claude code`, `claude code vs copilot`, `claude code review`

Defaults: `hl='en-US'`, `tz=0`, `geo='US'`, `timeframe='today 12-m'`, `SLEEP=10` between calls. JSON outputs go to `data/.seo-research/<slug>.json` (gitignored — research is local, doesn't belong in the repo).

## What happened

### Run 1 — all four batches died on the same line

```
[batch-a-wiki-verticals] ERROR: TypeError: Retry.__init__() got an unexpected keyword argument 'method_whitelist'
[batch-b-mcp-specific]   ERROR: TypeError: Retry.__init__() got an unexpected keyword argument 'method_whitelist'
[batch-c-marketing-cro]  ERROR: TypeError: Retry.__init__() got an unexpected keyword argument 'method_whitelist'
[batch-d-competitive]    ERROR: TypeError: Retry.__init__() got an unexpected keyword argument 'method_whitelist'
```

Root cause: `pytrends` 4.9.2 (the latest released version) passes the deprecated `method_whitelist=` keyword to `urllib3.util.Retry`. `urllib3` ≥2.0 renamed it to `allowed_methods=` and removed the old name entirely. The error message names the parameter but doesn't explain the version mismatch.

Fix is a six-line monkey-patch shim placed **before** the `from pytrends.request import TrendReq` import:

```python
from urllib3.util import Retry as _Retry
_orig_retry_init = _Retry.__init__
def _patched_retry_init(self, *args, **kwargs):
    if 'method_whitelist' in kwargs:
        kwargs.setdefault('allowed_methods', kwargs.pop('method_whitelist'))
    return _orig_retry_init(self, *args, **kwargs)
_Retry.__init__ = _patched_retry_init
```

The alternative is pinning `urllib3<2.0` in the venv, which risks breaking other tools that already moved to urllib3 2.x. The shim is non-invasive — keep it until pytrends ships a fix.

### Run 2 — three batches landed, the fourth hit 429 on the thirteenth call

After the shim, Batches A, B, and C completed cleanly with 53 weeks of `interest_over_time` data each plus `related_queries()` dicts. Batch D failed on its final HTTP roundtrip:

```
RetryError: HTTPSConnectionPool(host='trends.google.com', port=443):
Max retries exceeded ... (Caused by ResponseError('too many 429 error responses'))
```

The skill notes claimed `~1400 consecutive queries within 4h triggers a block`. Today's ceiling was tighter by two orders of magnitude: twelve HTTP requests across ~2 minutes of sustained activity from one IP. Batch D had completed `build_payload` and `interest_over_time` — the 429 fired on the `related_queries()` call that came thirteenth.

The 1400/4h figure is a ceiling under fresh-IP conditions. The working budget is much smaller when your IP has any recent Trends traffic. Plan in single-batch sessions if you need to run more than ~10 queries.

### The data that survived the run

Three batches' worth of `interest_over_time` and `related_queries`, 12-month mean per keyword:

| Keyword | Mean (0-100) | Batch |
|---|---|---|
| `mcp server` | 47.1 | B |
| `claude code mcp` | 35.4 | A |
| `claude code agent` | 30.5 | A |
| `claude code marketing` | 29.9 | C |
| `claude code skills` | 21.6 | A |
| `claude mcp` | 21.8 | B |
| `model context protocol` | 16.1 | B |
| `mcp claude code` | 10.1 | B |
| `claude code hooks` | 7.1 | A |
| `claude code subagents` | 5.1 | A |
| `claude code cro` | 0.1 | C |
| `claude code copywriting` | 0.0 | C |

Rising queries (breakout, 1M%+ growth) clustered around two themes: "best claude code skills" listicle intent (`best claude skills` +2,109,250%, `best claude code skills` +2,014,600%) and specific MCP tool integrations (`playwright mcp claude code` +412,300%, `figma mcp` +411,550%, `chrome devtools mcp server` +25,700%, `sentry mcp server` +21,900%).

## Where it drifted — two findings flipped the editorial plan

The launch-day SEO memo had recommended two specific next posts based on SERP gap analysis (no demand data). Both got flipped by the actual numbers.

**Finding 1: CRO is uncontested in SERPs because demand is near-zero.** The memo flagged "Claude Code CRO" as a "nearly uncontested keyword" based on the empty page-one SERP. Trends shows `claude code cro` = 0.1 mean, `claude code copywriting` = 0.0. The uncontested-keyword + zero-searcher combination is not an opportunity — it's a graveyard. There's no traffic to win because no one is searching. Revised pick: write under `claude code marketing` (29.9 mean) instead, where there are actually searchers.

**Finding 2: Hooks are lower-demand than the memo assumed.** The memo recommended a hooks case study as the next post. `claude code hooks` is only 7.1 mean — 3-5× less than MCP, skills, or agents (35.4, 21.6, 30.5 respectively). Hooks posts still work as depth-of-engagement plays for practitioners who land via direct or referrer traffic. But they won't be traffic engines via search.

## What I'd change

**Add the urllib3 shim to the skill's quickstart block.** Anyone running pytrends 4.9.2 against urllib3 2.x today fails on call one with no diagnostic hint. The skill should ship with the shim inline (or pin `urllib3<2.0` in setup notes) so the first run works.

**Bound the batch size by IP-traffic history, not the 1400/4h docs figure.** For an IP with any recent Trends activity, plan ~10 queries per session and put 60-second sleeps between calls instead of 10. Spread larger sweeps across multiple sessions across multiple hours.

**Validate SERP-gap hypotheses with demand data before committing to a post topic.** The CRO finding is the cautionary tale. SERP gap analysis tells you what's *not there*. Trends tells you whether anyone is *looking for it*. Both signals matter. Either alone is a coin flip.