Common honeypot patterns
The classic patterns are all visually invisible but DOM-present:
<input name="email" type="text" style="display:none">
<input name="email" type="text" style="visibility:hidden">
<input name="email" type="text" style="opacity:0">
<input name="email" type="text" style="width:0; height:0">
<input name="email" type="text" tabindex="-1">
<input name="phone" style="position:absolute; left:-9999px">
<a href="/admin/honeypot" style="display:none">Admin</a>
<!-- after </body> — never rendered, only seen by parsers -->
</body>
<a href="/honeypot-trap">Trap</a>A bot that fills every input or follows every href triggers the trap. A human never sees these elements at all because the browser does not render them.
Mitigation in practice
Always check element visibility before interacting:
def is_visible(element):
box = element.bounding_box()
if not box or box["width"] == 0 or box["height"] == 0:
return False
style = element.evaluate("el => getComputedStyle(el)")
if style["display"] == "none": return False
if style["visibility"] == "hidden": return False
if float(style["opacity"]) == 0: return False
return True
for link in page.query_selector_all("a"):
if is_visible(link):
# safe to click
...For HTML-parser-based scrapers (Scrapy, BeautifulSoup), parse and respect inline style="display:none" and hidden attributes. Filter elements positioned off-screen (left: -9999px) and skip anything that appears outside or after the <body>.
Where this is deployed
Honeypots are most aggressive on login forms, account-registration flows, contact-us forms, and comment sections — anywhere automation is expected to brute-force or scrape. They are deployed by every major anti-bot vendor as a free first-line defence on top of fingerprint-based scoring. They are also common on independent sites that build their own anti-scraping (since they cost nothing) — which is why they are the failure mode that catches the most "I have a perfect TLS fingerprint, why am I still blocked?" scrapers.
