$npx -y skills add AgriciDaniel/claude-blog --skill blog-cannibalizationDetect keyword cannibalization across blog posts by extracting primary keywords from titles and headings, clustering semantically similar targets, and flagging posts competing for the same search intent. Supports local-only mode (grep-based) and DataForSEO API mode (Page Intersec
| 1 | # Blog Cannibalization - Keyword Overlap Detection |
| 2 | |
| 3 | Detect when multiple blog posts compete for the same search keywords. Two modes: |
| 4 | local-only analysis (default) and DataForSEO API mode for SERP-level data. |
| 5 | |
| 6 | ## Two Modes |
| 7 | |
| 8 | | Mode | Flag | Cost | Data Source | |
| 9 | |------|------|------|-------------| |
| 10 | | Local | (default) | Free | File content analysis via Grep/Read | |
| 11 | | API | `--api` | ~$0.01/call | DataForSEO Page Intersection + Ranked Keywords | |
| 12 | |
| 13 | Local mode works without any API keys. API mode requires DataForSEO credentials |
| 14 | set as environment variables: `DATAFORSEO_LOGIN` and `DATAFORSEO_PASSWORD`. |
| 15 | |
| 16 | ## Local Mode Workflow |
| 17 | |
| 18 | ### Step 1: Scan Blog Files |
| 19 | |
| 20 | Use Glob to find all content files in the target directory: |
| 21 | - Patterns: `**/*.md`, `**/*.mdx`, `**/*.html` |
| 22 | - Skip files in `node_modules/`, `.git/`, `drafts/` |
| 23 | |
| 24 | ### Step 2: Extract Primary Keywords |
| 25 | |
| 26 | For each file, read and extract keyword signals from: |
| 27 | - **Title tag** or H1 heading (highest weight) |
| 28 | - **H2 headings** (medium weight) |
| 29 | - **First paragraph** (supporting signal) |
| 30 | - **Meta description** if present in frontmatter |
| 31 | |
| 32 | Primary keyword extraction method: |
| 33 | 1. Tokenize title and H1 into 1-gram, 2-gram, and 3-gram phrases |
| 34 | 2. Score each phrase by frequency across title + H2s + first paragraph |
| 35 | 3. Select the top-scoring 2-3 word phrase as the primary keyword |
| 36 | 4. Record secondary keywords from H2 headings |
| 37 | |
| 38 | ### Step 3: Cluster by Similarity |
| 39 | |
| 40 | Group posts into clusters using these matching rules (in priority order): |
| 41 | |
| 42 | 1. **Exact match** - identical primary keyword across 2+ posts |
| 43 | 2. **Stem match** - same root word (e.g., "optimize" vs "optimization") |
| 44 | 3. **Semantic overlap** - Claude determines that two keywords target the same |
| 45 | search intent (e.g., "best CRM software" vs "top CRM tools 2026") |
| 46 | 4. **Subset match** - one keyword contains another (e.g., "email marketing" |
| 47 | vs "email marketing for startups") |
| 48 | |
| 49 | ### Step 4: Score and Flag |
| 50 | |
| 51 | For each cluster with 2+ posts, assess severity and generate a recommendation. |
| 52 | |
| 53 | ### Step 5: Output Report |
| 54 | |
| 55 | Display the results table and per-cluster recommendations. |
| 56 | |
| 57 | ## API Mode Workflow (DataForSEO) |
| 58 | |
| 59 | Requires the `--api` flag. Uses WebFetch to call DataForSEO endpoints. |
| 60 | |
| 61 | ### Endpoints Used |
| 62 | |
| 63 | **Page Intersection** - find keywords where multiple URLs rank: |
| 64 | ``` |
| 65 | POST https://api.dataforseo.com/v3/dataforseo_labs/google/page_intersection/live |
| 66 | Authorization: Basic <base64(login:password)> |
| 67 | |
| 68 | { |
| 69 | "pages": { |
| 70 | "1": "https://example.com/post-a", |
| 71 | "2": "https://example.com/post-b" |
| 72 | }, |
| 73 | "language_code": "en", |
| 74 | "location_code": 2840 |
| 75 | } |
| 76 | ``` |
| 77 | Cost: ~$0.01 per call. Returns overlapping keywords with position, volume, CPC. |
| 78 | |
| 79 | **Ranked Keywords** - get all keywords a single URL ranks for: |
| 80 | ``` |
| 81 | POST https://api.dataforseo.com/v3/dataforseo_labs/google/ranked_keywords/live |
| 82 | |
| 83 | { |
| 84 | "target": "https://example.com/post-a", |
| 85 | "language_code": "en", |
| 86 | "location_code": 2840 |
| 87 | } |
| 88 | ``` |
| 89 | |
| 90 | ### API Analysis Steps |
| 91 | |
| 92 | 1. Collect all published URLs from the user (or sitemap) |
| 93 | 2. Run Ranked Keywords for each URL to build keyword profiles |
| 94 | 3. Run Page Intersection for URL pairs that share keyword clusters |
| 95 | 4. Calculate severity using the formula below |
| 96 | 5. Output enriched report with search volume and position data |
| 97 | |
| 98 | ## Severity Scoring |
| 99 | |
| 100 | Four severity levels based on overlap signals: |
| 101 | |
| 102 | | Level | Criteria | Action Urgency | |
| 103 | |-------|----------|----------------| |
| 104 | | Critical | Same exact keyword, both pages in top 20 | Immediate | |
| 105 | | High | Same keyword cluster, one page outranks the other | This week | |
| 106 | | Medium | Related keywords with partial SERP overlap | This month | |
| 107 | | Low | Semantic similarity but different confirmed intents | Monitor | |
| 108 | |
| 109 | ### Severity Formula (API Mode) |
| 110 | |
| 111 | ``` |
| 112 | severity_score = overlap_count x avg_search_volume x (1 / position_gap) |
| 113 | ``` |
| 114 | |
| 115 | Where: |
| 116 | - `overlap_count` = number of shared ranking keywords |
| 117 | - `avg_search_volume` = mean monthly volume of shared keywords |
| 118 | - `position_gap` = absolute difference in average ranking position (min 1) |
| 119 | |
| 120 | Higher score = more urgent cannibalization problem. |
| 121 | |
| 122 | ### Severity Heuristic (Local Mode) |
| 123 | |
| 124 | Without SERP data, use a simplified scoring: |
| 125 | - **Critical**: Exact primary keyword match between posts |
| 126 | - **High**: Stem match on primary keyword, or 3+ shared H2 keywords |
| 127 | - **Medium**: Semantic overlap on primary keyword |
| 128 | - **Low**: Subset match only, or shared secondary keywords |
| 129 | |
| 130 | ## Output Format |
| 131 | |
| 132 | ### Summary Table |
| 133 | |
| 134 | ``` |
| 135 | | Post A | Post B | S |