How to Check a Website for Black Hat SEO: Cloaking, Hidden Text, Doorway Pages
By Seth Black · Published
To check a website for black hat SEO, compare what the site serves to Googlebot with what it serves to a browser (cloaking), look for text and links that are in the HTML but invisible to visitors (hidden text), and look for batches of near-identical pages built to rank for variations of one query (doorway pages). Then check Search Console’s Manual actions and Security issues reports, and the site’s backlinks. No tool can prove intent, but each of these leaves evidence you can see.
What does the name Black SEO Analyzer mean?
This comes up because “black seo tools” sends this site thousands of search impressions a quarter, so I’ll answer it plainly. Black SEO Analyzer, or bseoa, is a technical SEO auditing tool. It crawls a site, reads the HTML, renders JavaScript when you ask it to, and reports problems: broken links, hreflang errors, duplicate descriptions, slow pages, content that crawlers and AI assistants can’t read. The “Black” is my last name.
What it isn’t: a black hat toolkit. It doesn’t build links, spin or generate content, cloak pages, submit to link networks, scrape search results for content, or do anything to a site except read it. If you came here looking for tools that manipulate rankings, this isn’t one, and the rest of this post is about finding those tactics, not using them. If you came here because you suspect a site, yours or a competitor’s, is using them, read on.
What counts as black hat SEO?
Google’s spam policies are the reference. These are the tactics you can check for from the outside:
| Tactic | Google’s description | How to check |
|---|---|---|
| Cloaking | “presenting different content to users and search engines” | Fetch as Googlebot and as a browser, compare |
| Hidden text and links | content placed “solely to manipulate search engines”, like white text on white | Rendered page, computed styles |
| Doorway pages | pages “created to rank for specific, similar search queries” | Crawl, group pages by template and title pattern |
| Keyword stuffing | “filling a web page with keywords or numbers” | Read it; repeated phrases jump out |
| Sneaky redirects | sending users somewhere different from what search engines saw | Fetch with different user agents and referrers |
| Link spam | links “primarily for the purpose of manipulating search rankings” | Backlink and outbound link review |
| Hacked content | content “placed on a site without permission” | site: searches, Search Console Security issues |
How do you check for cloaking?
Start with the crude version, which catches crude cloaking: fetch the page with Googlebot’s user agent and with a browser’s, and compare.
UA_GOOGLE='Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'
UA_BROWSER='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0 Safari/537.36'
curl -sL -A "$UA_GOOGLE" https://example.com/page > as-googlebot.html
curl -sL -A "$UA_BROWSER" https://example.com/page > as-browser.html
wc -c as-googlebot.html as-browser.html
grep -o '<title>.*</title>' as-googlebot.html as-browser.html
A large size difference or a different title is worth a closer look. To do it across a whole site, crawl twice with bseoa, once per user agent, and compare titles and word counts page by page:
black-seo-analyzer --url-to-begin-crawl https://example.com --user-agent "$UA_GOOGLE" \
--output-type csv --output-file as-googlebot.csv --db-path googlebot.db
black-seo-analyzer --url-to-begin-crawl https://example.com --user-agent "$UA_BROWSER" \
--output-type csv --output-file as-browser.csv --db-path browser.db
import csv
def load(path):
with open(path, encoding="utf-8") as f:
return {r["URL"]: (r["Title"], int(r["Word Count"] or 0)) for r in csv.DictReader(f)}
g, b = load("as-googlebot.csv"), load("as-browser.csv")
for url in sorted(g.keys() & b.keys()):
(tg, wg), (tb, wb) = g[url], b[url]
if tg != tb or abs(wg - wb) > max(50, wb * 0.2):
print(url, "|", tg, "|", tb, "|", wg, "vs", wb)
The limit of this method: serious cloaking keys on IP address, not user agent. Google tells site owners to verify Googlebot with a reverse and forward DNS lookup, and cloakers use the same check. A fake Googlebot user agent from your laptop gets the human version. For your own site, URL Inspection in Search Console shows the HTML Google actually fetched; compare that with what you see. For someone else’s site, the Rich Results Test fetches with Google’s infrastructure and shows the rendered HTML.
How do you find hidden text and links?
Open the page, press F12, and run this in the Console. It lists elements whose own text is long enough to matter but that a visitor can’t see:
const invisible = [...document.querySelectorAll('body *')].filter(el => {
const own = [...el.childNodes].filter(n => n.nodeType === 3).map(n => n.textContent.trim()).join(' ');
if (own.length < 30) return false;
const s = getComputedStyle(el), r = el.getBoundingClientRect();
return s.display === 'none' || s.visibility === 'hidden' || parseFloat(s.opacity) === 0
|| parseFloat(s.fontSize) < 2 || r.right < 0 || r.bottom < 0 || s.color === s.backgroundColor;
});
console.table(invisible.map(el => ({ tag: el.tagName, text: el.textContent.trim().slice(0, 100) })));
Expect false positives. Mobile menus, tabs, accordions and modals are hidden until clicked, and that’s normal. What you’re looking for is a block of keyword-heavy text or a list of outbound links that no interaction ever reveals, often in the footer or positioned off-screen.
A related, quieter version is structured data describing content that isn’t on the page. Google’s structured data guidelines say “Don’t mark up content that is not visible to readers of the page.” bseoa flags FAQ markup that isn’t visible on the page, which is the most common case. For other structured data problems, see structured data errors that kill rich results.
How do you spot doorway pages?
Crawl the site and sort the CSV export by title. Doorway pages show up as dozens or hundreds of rows with the same title pattern and almost the same word count: “Plumber in Austin”, “Plumber in Round Rock”, “Plumber in Pflugerville”, each a copy with the city swapped. Location pages aren’t spam by themselves. They become doorways when they exist only to catch the query and funnel everyone to the same place, with nothing specific to each location.
How do you check for hacked content and sneaky redirects?
Search Google for spam terms restricted to the site:
site:example.com casino OR viagra OR "replica" OR loan
Results in a language the site doesn’t publish in, or pages you didn’t create, usually mean injected content. Search Console’s Security issues report confirms it for sites you own. For redirects, fetch the page with a mobile user agent and with -e https://www.google.com/ as the referrer; some hacks only redirect visitors who arrive from search.
What about links?
Paid links, link networks and private blog networks show up in the backlink profile: sudden spikes, unrelated sites, identical anchor text. How to find links to your website covers pulling the list. Check outbound links too, with the crawl’s link export, for links to unrelated commercial sites you didn’t place. The technical SEO audit checklist puts these checks in order with the rest of an audit.
If you want to run the crawl-twice comparison on a site, bseoa’s website crawler takes any user agent string and exports titles, word counts and links per page to CSV, on your own machine.
-Sethers