← News
/PLAYBOOK · 2026-09-13

How to Catch Crawlers Posing as Real Users: Behavioral Detection in Practice

Modern scrapers forge UA, rotate proxies, and throttle. IP and User-Agent alone no longer separate them. Eight Q&As cover detecting disguised crawlers via request cadence, path patterns, and resource loading, with Nginx log fields, awk analysis commands, and a graded response playbook.

Many operators find crawlers keep scraping after IP and User-Agent bans — collectors now rotate proxy IPs, forge browser UAs, even mimic click intervals. Catching crawlers that pose as real users requires behavioral features. Eight Q&As cover the full practical method.

Q: Why do crawlers impersonate real users?

Because the simplest blocks are identity-based, and changing identity bypasses them: ban IP, switch proxy pool; ban UA, copy a Chrome UA string; rate-limit, slow down until a single IP looks normal. Identity is forgeable. Behavioral regularity is not — humans jump, randomize, skip; programs traverse, uniform, complete. Behavior detection targets that gap.

Q: Which dimensions separate humans from disguised crawlers?

Three. Hit two or more and you can usually confirm:

Never loading static assets is the hardest evidence of the three, and the easiest to see in logs.

Q: Which log fields do you need for behavior detection?

Default logs have IP, time, request, UA, status, size — but not request interval or response time, which makes analysis painful. Add these two fields in Nginx:

`log_format behavior '$remote_addr | $time_iso8601 | $request | '
                    '$status | $body_bytes_sent | '
                    '$request_time | $upstream_response_time | '
                    '\"$http_user_agent\" | \"$http_referer\"';
access_log /var/log/nginx/access.log behavior;`

Field notes: $request_time is this request's duration; scraped pages tend to be slow. $upstream_response_time is backend processing time; shows whether the DB is being hit. Empty $http_referer means a direct URL; humans navigating in-site usually carry a referrer. Apply with nginx -s reload.

Q: How do you quickly find crawlers with overly uniform cadence?

Count requests per IP, take the head of the list:

`awk -F' \\\\ | ' '{print $1}' access.log | sort | uniq -c | sort -rn | head -20`

Then inspect one suspect IP's timestamps for uniform gaps:

`grep \"1.2.3.4\" access.log | awk -F' \\\\ | ' '{print $2}' | head -40`

If second-level timestamp deltas are nearly constant (always 0.3 or 1.0 s), that is classic programmatic throttle. Human deltas are messy. Add a static-resource check:

`# check whether this IP has requested any static assets
grep \"1.2.3.4\" access.log | grep -cE '\\\\.(css|js|png|jpg|gif)'
# result of 0 or extremely low: almost certainly a collector`

Q: Can User-Agent and Referer still help?

Yes, as supporting evidence only. Do not conclude from them alone. Three anomalies in practice:

Once confirmed, stop the bleed with a blacklist (put it at the top of the server block; match-and-deny is cheapest):

`if ($http_user_agent ~* \"(python-requests|Go-http-client|curl|wget|scrapy|java/)\") {
    return 403;
}
# or ban the CIDR directly (use a real prefix; do not copy this example)
deny 1.2.3.0/24;`

Note: if in Nginx is inefficient. High-traffic sites should pre-decide with map or geo, or put the rule on the CDN/WAF, so every request does not run a regex.

Q: Once identified, how do you respond?

Do not 403 everything. Graded response is kinder to SEO and the business:

`limit_req_zone $binary_remote_addr zone=botzone:10m rate=5r/m;
location / {
    limit_req zone=botzone burst=10 nodelay;
    limit_req_status 429;
}`

The point of grading: you are stopping bulk, high-frequency, full-corpus collection, not occasional one-off fetches. Over-blocking raises your own ops cost.

Q: Can behavior alone catch collectors spoofing Baidu spider?

Behavior helps; identity verification finishes it. Filter suspects by behavior, then reverse-check identity: real search-engine spiders reverse-DNS to an official hostname (e.g. baiduspider's official domain) and forward-resolve back to the same IP. Spoofers typically only change the UA string; reverse DNS will not match. Example:

`# reverse-DNS to hostname
dig -x 1.2.3.4 +short
# then forward-resolve that hostname; check it maps back to the same IP
dig +short <hostname from previous step>`

Both directions must match before you whitelist as a real spider. Mismatch: treat as a normal crawler even if UA is identical. Re-run verification in batch on a schedule; spoofed UAs keep changing.

Q: Any extra advice?

Three lessons. First, quantify before you act: banning IPs without reading logs hits real users and search engines. Run a week of stats — Top IPs, UA mix, static-resource request rate — then write rules. Second, push blocking to the edge: origin-side behavior detection costs server CPU; CDN/WAF nodes can do rate, fingerprint, and behavior at the edge so origin only sees clean traffic, and scraping does not hurt real visitors. Third, accept imperfect coverage: collectors with proxy pools and fingerprint pools are hard to stop completely. A sane goal is make collection slower, more expensive, and incomplete — not 100% block. Permission-tier core data and cache hot pages usually beat more complex rules.