Keyword Cannibalization Checker for Google Search Console avatar

Keyword Cannibalization Checker for Google Search Console

Pricing

$5.00 / 1,000 cannibalization issues

Go to Apify Store
Keyword Cannibalization Checker for Google Search Console

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

Umair Saleem Mohammad Saleem

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

9 days ago

Last modified

Security

Limited permissions

Share

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 forKeyword cannibalization audits, content consolidation, "which of my pages compete for the same query?", pre-migration SEO clean-ups
InputA 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
OutputOne row per cannibalized query: severity, fixType, primaryPage, competingPages, clicksAtRisk, positionGap, pages[] (clicks, impressions, share, position), recommendation
PriceFirst 5 issues free every run, then $0.005 per issue. No start fee. Queries without cannibalization are free
NeedsNo Google login, no API key, no scraping. It reads only the numbers you give it
Not includedLive 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:

QuerySeverityfixTypePrimary pageCompeting page(s)Clicks at riskPosition gapBilling
email marketing tipsHIGHmerge-301/blog/email-marketing-tips/guides/email-marketing644.4free
best email subject linesHIGHmerge-301/blog/subject-lines/blog/subject-line-examples901.7free
email automation softwareHIGHde-optimize/pricing/features/automation, /blog/best-email-automation-tools620.3free
cold email templateHIGHde-optimize/templates/cold-email/blog/cold-email-guide702.5free
email open rate benchmarkHIGHmerge-301/blog/open-rate-benchmarks-2026/blog/open-rate-benchmarks-2025602.1free
double opt inHIGHde-optimize/help/double-opt-in/blog/what-is-double-opt-in353.4billed
email list cleaningHIGHde-optimize/features/list-cleaning/blog/clean-email-list410.4billed
what is a good click rateHIGHde-optimize/blog/click-rate/blog/open-rate-benchmarks-2026302.3billed
newsletter templateMEDIUMde-optimize/templates/newsletter/blog/newsletter-design-ideas258.3billed
examplemail loginLOWmonitor/login/help/login-issues402.2billed

Why two "same-type" pairs are not merged:

  • what is a good click rate: /blog/open-rate-benchmarks-2026 is 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-tips and /blog/email-marketing-tips/?utm_source=newsletter were summed into one page (mergedUrlVariants), and so were www and http://example.org spellings 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.

QuerySeverityPrimary pageCompeting page(s)Clicks at riskPosition gapFix
best running shoesHIGH/best-running-shoes/blog/running-shoes-guide158.6de-optimize: keep both, de-optimize the blog post, link it to the primary page
marathon training planHIGH/blog/marathon-plan/blog/marathon-plan-16-weeks305.6merge-301 into /blog/marathon-plan
trail running shoesHIGH/trail-shoes/collections/trail, /blog/trail-vs-road294.4de-optimize: keep them, de-optimize the secondary pages, link them to /trail-shoes
how to clean running shoesHIGH/blog/clean-shoes/blog/shoe-care224.4merge-301 into /blog/clean-shoes

What was not flagged, and why:

  • running shoes for flat feet: /flat-feet-shoes and /flat-feet-shoes/?srsltid=AfmBOoq1 are the same page (Google's srsltid merchant parameter). They were merged into one row (urlVariantsMerged: 1), so no false issue. With normalizeUrls: false the 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

SeverityRule (from code)
HIGHThe #2 page has ≥30% of the query's impressions and the position gap is ≤10 (or position unknown)
MEDIUMThe #2 page has ≥20% impression share
LOWCompeting 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:

fixTypeWhen
monitorSeverity LOW: the second page has little share. No merge; point internal links to the primary page and re-check
de-optimizeA 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-301All 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
}
FieldDefaultWhat 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
csvUrlA 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
minImpressions10A page needs at least this many impressions for the query to count
minPageShare10...and at least this % of the query's impressions
excludeQueries[]Skip queries containing these strings (e.g. your brand)
normalizeUrlstrueMerge 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 foundYou 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).

ActorWhat you payInputNotes
cygnix_labs/keyword-cannibalization-finder (this)$0.005 / issue, first 5 free every run, no start feeGSC query + page CSVSeverity, 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 / resultSERP / ranked-keyword datasetsRecurring "collision" monitoring from third-party ranking data
sprightly_blackberry/content-portfolio-audit$1.00 / report + tiny start feeSite contentBroad 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 dataGeneral GSC insights, not query-level cannibalization rows
siteverdict/siteverdict-gsc-ga4-analyst$0.35 / property + $0.005 / URL inspectedLive GSC + GA4 APIProperty-level analyst, a different job
datascoutapi/google-search-console-mcp$0.005 / result + $0.001 startLive GSC APIPulls 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), a SUMMARY and 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 get MISSING_METRICS. Clean exports get a NO_ISSUES row. 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 get monitor instead of a merge. pageType per 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 csvUrl got the example.com demo, because csvText had 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. www and 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 normalizeUrls option (on by default) merges srsltid / utm_* / trailing-slash variants so they aren't reported as cannibalization. Impression-weighted position for merged rows, charged flag per row, richer SUMMARY. 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

Questions or a feature request? Open an issue on this actor's Issues tab. We answer within 24 hours.