Keyword Cannibalization Checker for Google Search Console
Pricing
$5.00 / 1,000 cannibalization issues
Keyword Cannibalization Checker for Google Search Console
Keyword cannibalization checker for Google Search Console (GSC): paste a query+page export or a Google Sheets/CSV link. Get pages competing for the same keyword, click split, HIGH/MEDIUM/LOW severity and the safe fix (merge + 301, de-optimize or monitor). First 5 issues free, then $0.005 each.
Pricing
$5.00 / 1,000 cannibalization issues
Rating
0.0
(0)
Developer
Umair Saleem Mohammad Saleem
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
9 days ago
Last modified
Security
Limited permissionsShare
Answer first: this actor is a keyword cannibalization checker for Google Search Console data. Give it a GSC export with query and page columns: paste it, or give a CSV / Google Sheets link. It finds every query where two or more of your own pages compete, shows the click and impression split, rates each issue HIGH / MEDIUM / LOW and tells you the fix (fixType): merge + 301, de-optimize + internal link, or monitor. It never tells you to merge away a page that is the top page for another query, or a login/help/legal page. URL variants of the same page (?srsltid=, utm_*, trailing slash, #fragment, http/https, www) are merged first, so they don't show up as fake cannibalization. The first 5 issues of every run are free, then $0.005 per issue found ($5 per 1,000). Clean queries are never charged.
| Quick facts | |
|---|---|
| Best for | Keyword cannibalization audits, content consolidation, "which of my pages compete for the same query?", pre-migration SEO clean-ups |
| Input | A Search Console CSV with query + page + clicks + impressions (+ position). Paste it into csvText or give a link in csvUrl (Google Sheets share/publish links, Drive and Dropbox links are converted). Comma, semicolon or tab; EU decimal commas (6,2) read correctly |
| Output | One row per cannibalized query: severity, fixType, primaryPage, competingPages, clicksAtRisk, positionGap, pages[] (clicks, impressions, share, position), recommendation |
| Price | First 5 issues free every run, then $0.005 per issue. No start fee. Queries without cannibalization are free |
| Needs | No Google login, no API key, no scraping. It reads only the numbers you give it |
| Not included | Live SERP checks, keyword volumes, crawling your site |
Try it now: the input is prefilled with a 13-row demo export; demo runs are always free. To test the link input, set csvUrl to our public 28-row sample: https://api.apify.com/v2/datasets/9bhKHtQj1Yz1jUfjh/items?format=csv&clean=1
Sample output: 28-row sample export via csvUrl (real run of build 0.1.9, 20 Oct 2026)
Run hYVC6cUXh3xaXihPE, input only {"csvUrl": "https://api.apify.com/v2/datasets/9bhKHtQj1Yz1jUfjh/items?format=csv&clean=1"} (a public 28-row query+page sample for an email-marketing SaaS on example.org). 13 queries analyzed, 10 issues, 2 URL variants merged, 5 free + 5 billed = $0.025. Unedited values:
| Query | Severity | fixType | Primary page | Competing page(s) | Clicks at risk | Position gap | Billing |
|---|---|---|---|---|---|---|---|
| email marketing tips | HIGH | merge-301 | /blog/email-marketing-tips | /guides/email-marketing | 64 | 4.4 | free |
| best email subject lines | HIGH | merge-301 | /blog/subject-lines | /blog/subject-line-examples | 90 | 1.7 | free |
| email automation software | HIGH | de-optimize | /pricing | /features/automation, /blog/best-email-automation-tools | 62 | 0.3 | free |
| cold email template | HIGH | de-optimize | /templates/cold-email | /blog/cold-email-guide | 70 | 2.5 | free |
| email open rate benchmark | HIGH | merge-301 | /blog/open-rate-benchmarks-2026 | /blog/open-rate-benchmarks-2025 | 60 | 2.1 | free |
| double opt in | HIGH | de-optimize | /help/double-opt-in | /blog/what-is-double-opt-in | 35 | 3.4 | billed |
| email list cleaning | HIGH | de-optimize | /features/list-cleaning | /blog/clean-email-list | 41 | 0.4 | billed |
| what is a good click rate | HIGH | de-optimize | /blog/click-rate | /blog/open-rate-benchmarks-2026 | 30 | 2.3 | billed |
| newsletter template | MEDIUM | de-optimize | /templates/newsletter | /blog/newsletter-design-ideas | 25 | 8.3 | billed |
| examplemail login | LOW | monitor | /login | /help/login-issues | 40 | 2.2 | billed |
Why two "same-type" pairs are not merged:
- what is a good click rate:
/blog/open-rate-benchmarks-2026is the top page for "email open rate benchmark", so the row says keep it (alsoTopPageFor) and de-optimize instead of a 301 that would lose that ranking. - double opt in: a
/help/page is involved. Help, login, account, legal and checkout pages are never merged. - email marketing tips:
/blog/email-marketing-tipsand/blog/email-marketing-tips/?utm_source=newsletterwere summed into one page (mergedUrlVariants), and so werewwwandhttp://example.orgspellings of /blog/welcome-emails.
One row from that run:
{"query": "what is a good click rate","severity": "HIGH","fixType": "de-optimize","pageCount": 2,"primaryPage": "https://www.example.org/blog/click-rate","competingPages": ["https://www.example.org/blog/open-rate-benchmarks-2026"],"totalClicks": 85,"totalImpressions": 3600,"clicksAtRisk": 30,"positionGap": 2.3,"pages": [{ "page": "https://www.example.org/blog/click-rate", "clicks": 55, "impressions": 2100, "position": 5, "pageType": "article", "impressionShare": 58.3 },{ "page": "https://www.example.org/blog/open-rate-benchmarks-2026", "clicks": 30, "impressions": 1500, "position": 7.3, "pageType": "article", "impressionShare": 41.7, "alsoTopPageFor": ["email open rate benchmark"] }],"recommendation": "Keep both: https://www.example.org/blog/open-rate-benchmarks-2026 is also the top page for \"email open rate benchmark\", so don't merge or redirect it. De-optimize the secondary page for \"what is a good click rate\" (title/H1/anchors) and link it to https://www.example.org/blog/click-rate.","charged": true}
SUMMARY record of the same run:
{ "demo": false, "source": "csvUrl", "rowsRead": 28, "decimalComma": false, "queriesAnalyzed": 13, "cannibalizedQueries": 10, "issuesDelivered": 10,"stoppedAtSpendCap": false, "high": 8, "medium": 1, "low": 1, "clicksAtRisk": 517, "urlNormalization": true, "urlVariantsMerged": 2,"freeIssues": 5, "billedIssues": 5, "pricePerIssueUsd": 0.005 }
Demo output (the free prefilled example)
Output for the prefilled demo export (13 rows of example.com data: running-shoe store with a blog). Defaults: minImpressions 10, minPageShare 10, normalizeUrls true.
| Query | Severity | Primary page | Competing page(s) | Clicks at risk | Position gap | Fix |
|---|---|---|---|---|---|---|
| best running shoes | HIGH | /best-running-shoes | /blog/running-shoes-guide | 15 | 8.6 | de-optimize: keep both, de-optimize the blog post, link it to the primary page |
| marathon training plan | HIGH | /blog/marathon-plan | /blog/marathon-plan-16-weeks | 30 | 5.6 | merge-301 into /blog/marathon-plan |
| trail running shoes | HIGH | /trail-shoes | /collections/trail, /blog/trail-vs-road | 29 | 4.4 | de-optimize: keep them, de-optimize the secondary pages, link them to /trail-shoes |
| how to clean running shoes | HIGH | /blog/clean-shoes | /blog/shoe-care | 22 | 4.4 | merge-301 into /blog/clean-shoes |
What was not flagged, and why:
- running shoes for flat feet:
/flat-feet-shoesand/flat-feet-shoes/?srsltid=AfmBOoq1are the same page (Google'ssrsltidmerchant parameter). They were merged into one row (urlVariantsMerged: 1), so no false issue. WithnormalizeUrls: falsethe same export returns 5 issues, including this fake one. - best running shoes → /shop/running has only 9% of the impressions, below the 10%
minPageShare, so it isn't counted as a competitor. - running socks has one page only.
How severity works
| Severity | Rule (from code) |
|---|---|
| HIGH | The #2 page has ≥30% of the query's impressions and the position gap is ≤10 (or position unknown) |
| MEDIUM | The #2 page has ≥20% impression share |
| LOW | Competing pages pass the share filter but stay below the MEDIUM threshold |
The primary page is the one with the most clicks (ties: most impressions). The fix (fixType) follows these rules, in order:
| fixType | When |
|---|---|
monitor | Severity LOW: the second page has little share. No merge; point internal links to the primary page and re-check |
de-optimize | A competing page is the top page for another query (alsoTopPageFor), or a utility page is involved (/login, /account, /help, /support, /docs, /faq, /contact, /cart, /checkout, /terms, /privacy...), or the pages are different types |
merge-301 | All pages are the same type and no competing page wins another query |
Page types come from the URL path: /blog/, /news/, /guide(s)/, /learn/, /article(s)/ = article; /shop/, /product(s)/, /category/, /collections/, /store(s)/ = commercial; utility paths above = utility; anything else = page.
Input
{"csvUrl": "https://docs.google.com/spreadsheets/d/<sheet-id>/edit#gid=0","minImpressions": 10,"minPageShare": 10,"excludeQueries": ["mybrand"],"normalizeUrls": true}
| Field | Default | What it does |
|---|---|---|
csvText | (prefilled demo, free) | Paste the CSV / TSV. Columns are auto-detected (query / keyword, page / url / landing page, clicks, impressions, position). Comma, semicolon or tab; decimal commas OK |
csvUrl | A CSV link. Wins over csvText when set. Google Sheets share/edit links (sheet shared as "Anyone with the link"), "Publish to web" links, Google Drive and Dropbox share links are converted to a direct CSV download | |
minImpressions | 10 | A page needs at least this many impressions for the query to count |
minPageShare | 10 | ...and at least this % of the query's impressions |
excludeQueries | [] | Skip queries containing these strings (e.g. your brand) |
normalizeUrls | true | Merge URL variants: tracking parameters (srsltid, utm_*, gclid, fbclid, msclkid ...), #fragment, http/https, www and trailing slash. Merged variants are listed per page in mergedUrlVariants |
What a run costs
First 5 issues in every run are free, then $0.005 per cannibalization issue. No start fee. Queries without cannibalization cost nothing.
| Issues found | You pay |
|---|---|
| 5 | $0 (free in every run) |
| 10 | $0.025 |
| 60 | $0.275 |
| 200 | $0.975 |
| 1,000 | $4.975 |
Set Maximum cost per run in the run options and the actor stops cleanly at that amount: HIGH issues come first, you are never charged past the cap, and the status message and SUMMARY (stoppedAtSpendCap, issuesDelivered vs cannibalizedQueries) say how many were left. Verified: run h1MohW0vlFzNoTQup with a $0.012 cap delivered 7 of 10 issues and charged exactly 2 ($0.010). Demo runs and bad inputs are never charged: a wrong-column file, a broken or private link, a link that returns a web page, or an empty CSV ends the run as SUCCEEDED, $0, with one dataset row that has a status (MISSING_COLUMNS, MISSING_METRICS, CSV_URL_ERROR, INVALID_INPUT, EMPTY_INPUT) and a plain-English fix, so API, MCP and scheduled runs don't break. A clean export gets one NO_ISSUES row. Default memory is 512 MB. Results are written in batches, so a 3,000-row export with 1,500 issues finishes in about a second of compute.
How it compares (live Apify Store prices, verified 30 Oct 2026)
List prices on the Apify Free plan, read from the Apify Store API on 30 October 2026 (unchanged since 20 Oct).
| Actor | What you pay | Input | Notes |
|---|---|---|---|
| cygnix_labs/keyword-cannibalization-finder (this) | $0.005 / issue, first 5 free every run, no start fee | GSC query + page CSV | Severity, click split and a merge/301, de-optimize or monitor fix per query; never merges a page that wins another query; URL variants merged; clean queries free |
| quanmatrix/keyword-cannibalization-seo-collision-intelligence | $0.0115 / result | SERP / ranked-keyword datasets | Recurring "collision" monitoring from third-party ranking data |
| sprightly_blackberry/content-portfolio-audit | $1.00 / report + tiny start fee | Site content | Broad content audit; cannibalization is one part |
| alizarin_refrigerator-owner/gsc-data-analyzer | $0.10 start + $0.08 / analysis + $0.01 / opportunity (+ $0.03 AI insight) | GSC data | General GSC insights, not query-level cannibalization rows |
| siteverdict/siteverdict-gsc-ga4-analyst | $0.35 / property + $0.005 / URL inspected | Live GSC + GA4 API | Property-level analyst, a different job |
| datascoutapi/google-search-console-mcp | $0.005 / result + $0.001 start | Live GSC API | Pulls GSC data for agents; no cannibalization logic |
Honest take. For query-level cannibalization from your own Search Console numbers, this is the lowest per-issue price we found on the Store, and you pay only for real issues. If you have no GSC access and want cannibalization inferred from third-party rankings, quanmatrix is built for that. If you need the raw GSC data pulled for you, use a GSC API actor first and feed its CSV here.
For AI agents / MCP
When an agent should call this actor: the user shares Search Console data (query + page) and asks about keyword cannibalization, pages competing for the same keyword, duplicate-intent content, or which pages to merge or redirect.
Connect the Apify MCP server with only this actor enabled (works in Claude, ChatGPT, Cursor, VS Code, Codex and any MCP client):
https://mcp.apify.com?tools=cygnix_labs/keyword-cannibalization-finder
Or search with the default server: search-actors keywords such as keyword cannibalization, search console cannibalization, competing pages.
Example call-actor input (a link; csvUrl always wins over csvText):
{ "csvUrl": "https://api.apify.com/v2/datasets/9bhKHtQj1Yz1jUfjh/items?format=csv&clean=1", "excludeQueries": ["examplemail"] }
Or inline data:
{"csvText": "query,page,clicks,impressions,position\nbest crm,https://site.com/best-crm,50,2000,5.1\nbest crm,https://site.com/blog/crm-guide,12,1400,9.8","excludeQueries": ["mybrand"]}
What to read from the result: one row per cannibalized query: severity, fixType (merge-301 / de-optimize / monitor), primaryPage, competingPages, clicksAtRisk, positionGap, pages[] and a plain-English recommendation. The SUMMARY record has the totals, urlVariantsMerged and how many issues were billed.
Cost control for agents: exports with 5 or fewer issues are free; 60 issues cost $0.275. Pass maxTotalChargeUsd in callOptions to hard-cap spend. This actor is pay-per-event, so it also works with Apify agentic payments.
Example prompt: "Here is my GSC query+page export. Find keyword cannibalization and tell me which pages to merge and 301."
FAQ
My GSC export has no query+page columns. The Search Console web UI exports queries and pages in separate tabs. Use the Search Console API (dimensions query + page), the Search Analytics for Sheets add-on, Looker Studio or a BigQuery bulk export to get both in one table.
Does it need my Google account or API key? No. Paste the CSV or give a public CSV URL. Nothing logs into Google and nothing is scraped.
What does URL normalization change? Rows for the same page with tracking parameters, a fragment, http vs https or a trailing slash are summed into one page (impression-weighted position). Other query parameters (e.g. ?color=red) are kept, because they can be real separate pages. Set normalizeUrls: false to group URLs exactly as written; that can surface real duplicate-URL problems.
My Google Sheets link returns an error. Share the sheet as "Anyone with the link can view" (or File > Share > Publish to web > CSV). Normal share/edit links are converted to a CSV download automatically. If the link returns a web page or 401/403/404, the run ends with one CSV_URL_ERROR row that says what to change, and nothing is charged.
My export uses semicolons and decimal commas (German, French, Spanish... Excel/Sheets). Supported: ; or tab files are read with 6,2 = 6.2 and 1.204 = 1204 (SUMMARY.decimalComma: true). Builds before 0.1.8 misread these numbers.
Why is a pair of blog posts not marked merge + 301? If the competing page is the top page for another query (alsoTopPageFor), merging it would lose that ranking, so the fix is de-optimize. Login, help, account, legal and checkout pages are never merged either.
What do minImpressions and minPageShare do? A page must meet both for a query to count as competing (defaults 10 impressions and 10% share). Raise them to cut noise on large exports.
Why are totalClicks / totalImpressions larger than the sum of pages[]? Totals include every page that got impressions for the query; pages[] lists only the pages that pass the two thresholds.
My export has clicks but no impressions column. Since 0.1.12 the actor uses clicks to measure each page's share of a query (SUMMARY.shareBasis: "clicks", totalImpressions: null). Before, such files returned 0 issues without saying why. Impressions give better results, so export them if you can. With neither clicks nor impressions you get one MISSING_METRICS row, free.
Why did my run return a single row with a status instead of issues? That row explains why there was nothing to analyse (wrong columns, broken link, empty file) or that no query had 2+ competing pages (NO_ISSUES). The run still shows SUCCEEDED and costs $0. Issue rows have no status field, so filter on severity if you only want issues.
Is it AI or rule-based? Rule-based and deterministic: the same CSV always gives the same output. No LLM, no SERP data.
Do I pay for clean queries? No. Only actual cannibalization issues trigger the issue-found event, and the first 5 per run are free.
Integrations
Run it through the Apify API, schedule weekly runs, or send results to Google Sheets, Make, Zapier, n8n, Slack or a webhook.
Changelog
- 0.1.12 (2026-10-30): Fix: bad input (wrong columns, broken/private CSV link, a link that returns a web page, an empty file) used to end the run as FAILED. It now ends SUCCEEDED, $0, with one explanation row (
status+ fix), aSUMMARYand a status message. Fix: exports with clicks but no impressions column silently returned 0 issues; clicks are now the share basis (shareBasis), and files with neither getMISSING_METRICS. Clean exports get aNO_ISSUESrow. Results are pushed in batches (free issues in one call, billed ones 500 at a time, still trimmed to your max cost). Comparison re-verified 30 Oct 2026. - 0.1.10–0.1.11 (2026-10-20, 2026-10-26): rebuilds; 0.1.11 runs on the Node 22 base image.
- 0.1.9 (2026-10-20): New
fixType(merge-301/de-optimize/monitor). Never recommends merging a page that is the top page for another query (alsoTopPageFor) or a login/help/account/legal/checkout page; LOW issues getmonitorinstead of a merge.pageTypeper page. New 28-row public sample (csvUrl) with a real run in the sample section. - 0.1.8 (2026-10-20): Fix: API/MCP calls that sent only
csvUrlgot the example.com demo, becausecsvTexthad the demo as a schema default and was read first. A CSV URL now always wins, and the demo runs only for empty input or the untouched form example (always free). Fix: semicolon/tab exports with decimal commas (6,2,1.204) were misread.wwwand bare-domain rows merged. Google Sheets/Drive/Dropbox share links converted to CSV; links that return a web page stop with a clear message (no charge). Spend cap: status message +stoppedAtSpendCap,issuesDelivered, and billed counts read from the platform. - 0.1.7 (2026-10-19): Price cut to $0.005 per issue (was $0.007) and 5 free issues per run (was 3). New
normalizeUrlsoption (on by default) mergessrsltid/utm_*/ trailing-slash variants so they aren't reported as cannibalization. Impression-weighted position for merged rows,chargedflag per row, richerSUMMARY. Default memory 512 MB. Bigger demo input; sample output above is from a real run of this build. Comparison re-verified 19 Oct 2026. - 0.1.6 (2026-10-18): README rewritten answer-first with quick facts, comparison and an AI agents / MCP section.
- 0.1.5 (2026-10-16): Named Store comps, full URL-type rules, second sample row, FAQ.
More SEO and AEO tools by Cygnix Labs
- Website Tech Stack Detector: CMS, Pixels, Analytics & Hosting
- Website SEO Audit + AEO/GEO Checker (full-site crawl)
- Content Decay Audit (Search Console)
- Internal Link Audit & Orphan Page Finder
- Keyword Clustering + Content Briefs
- Search Intent Classifier
- Free local SEO MCP server: cygnix-seo-mcp (
npx -y cygnix-seo-mcp)
Questions or a feature request? Open an issue on this actor's Issues tab. We answer within 24 hours.


