很多站长只看后台统计的 PV 和 UV,却发现流量明明涨了、订单却没动。问题常常出在爬虫污染上:爬虫请求被统计工具当成真实访客计入,PV 虚高、跳出率失真、来源分布混乱。本文用 9 个问答讲清爬虫是怎么污染站点统计的、怎么在 Nginx 日志和统计工具两侧把它筛出来,配置与命令均可直接复制。
Q:爬虫为什么会污染网站统计数据?
因为绝大多数统计工具(百度统计、GA4、自建统计等)的默认判定方式是在页面里跑一段 JavaScript,只要请求方执行了 JS,就会被计为一次访问。
- 普通采集爬虫通常不执行 JS,不会污染前端统计,但会大量写进服务器访问日志,把日志口径搞乱。
- 无头浏览器爬虫(Puppeteer、Playwright)会完整执行 JS、加载 CSS 和图片,行为与真实浏览器几乎一致,会被直接计入 PV 和 UV。
- 刷量工具会模拟点击和多页浏览,把跳出率、停留时长、转化路径一起搅乱。
所以污染分两层:日志层污染(请求数虚高)和统计层污染(访客数、来源、行为指标失真)。两层要分别处理。
Q:怎么判断统计里的异常是爬虫造成的?
看四个特征,命中两个以上基本可以定性。
- PV 涨、UV 不涨,订单和咨询不动:正常流量上涨会带来转化,爬虫不会。
- 跳出率异常:要么接近 100%(爬虫打开一个页面就走),要么低得反常(刷量工具模拟了多页浏览)。
- 来源集中:来源几乎全是直接访问,Referer 普遍为空。
- 地域与分辨率集中:同一城市、同一分辨率、同一浏览器版本占了绝大多数。
验证方式很直接:到服务器上比对日志与统计。统计显示 5000 次访问,而同一时段日志里去重 IP 只有 300 个,差值就是被重复计入的部分。
Q:日志里怎么把爬虫请求单独筛出来?
用 UA 特征、Referer 缺失、请求资源类型三个维度组合筛,比只看 UA 准得多。
# 1. UA 里带常见爬虫或工具关键词的请求数
grep -Eic "curl|wget|python|scrapy|spider|bot|headless|phantom" /var/log/nginx/access.log
# 2. Referer 为空且 UA 也异常的请求(正常首次访问也会空,需结合 UA 看)
awk -F'"' '$4=="" {print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# 3. 只请求 HTML 从不请求静态资源 —— 典型的纯采集器
awk '{print $1}' /var/log/nginx/access.log | sort -u > /tmp/allip.txt
grep -Ei "\.(css|js|png|jpg|webp|ico|woff2?)" /var/log/nginx/access.log | awk '{print $1}' | sort -u > /tmp/staticip.txt
comm -23 /tmp/allip.txt /tmp/staticip.txt | wc -l
第三条最值得用。真实浏览器一定会请求 CSS、JS、图片,而纯采集器只拉 HTML,用集合差就能把这类 IP 一网打尽。
Q:Nginx 层面怎么给爬虫单独打标、单独写日志?
用一个 map 把请求分类,再用 access_log 的 if= 参数把爬虫流量写进独立文件,正常日志与爬虫日志彻底分开,统计口径立刻干净。
map $http_user_agent $is_bot {
default 0;
"~*(curl|wget|python|scrapy|spider|bot|headless|phantomjs|okhttp|java/)" 1;
}
log_format human '$remote_addr [$time_local] "$request" $status '
'ref="$http_referer" ua="$http_user_agent"';
access_log /var/log/nginx/bot.log human if=$is_bot;
access_log /var/log/nginx/access.log human;
两个注意点:map 的正则要加 ~* 做不区分大小写匹配;不要用 if 指令去动态决定日志文件(Nginx 不支持在 if 块里写 access_log),用 if= 参数才是正确写法。
Q:统计工具里的爬虫数据怎么排除?
主流工具都支持忽略已知爬虫或自定义过滤,配置难度不高。
- GA4:开启「排除已知机器人和蜘蛛程序」;刷量这类无法自动识别的,用内部流量过滤器配合 IP 段排除。
- 百度统计:在过滤规则里配置「排除 IP」和「排除来源域名」,把爬虫 C 段整段填进去。
- 自建统计:要在采集端就加 UA 与行为判定,别把 bot 流量写进访客表。
关键原则:过滤规则写在采集端,而不是报表端。报表层过滤只能让数字好看,原始数据里脏的部分依然会污染趋势分析和同比计算。
Q:PV 虚高到底有什么实际害处?
不是数字难看的心理问题,而是会直接导致错误决策。
- 容量规划失真:以为有 10 万 PV 就按 10 万 PV 配服务器,实际真实用户只有 2 万,成本白花;反过来,爬虫把资源吃掉导致真实用户变慢,又会被误判成服务器性能不足去盲目升级。
- SEO 判断失真:把爬虫流量当成流量上涨,错误归因到某次内容优化上,之后持续做无效动作。
- 广告投放被带偏:转化率被爬虫流量稀释后看起来很低,容易误杀掉本来能赚钱的渠道。
- 掩盖真实攻击:CC 攻击早期的现象和爬虫污染非常像,统计口径不干净时,很容易把攻击当爬虫放着不管。
Q:robots.txt 和 UA 黑名单能减少统计污染吗?
能减少一部分,但都不彻底。
- robots.txt 只对守规矩的爬虫有效。正规搜索引擎会遵守,恶意采集器基本不看,甚至故意先读一遍 robots.txt 拿到你不想被访问的路径清单。
- UA 黑名单 只挡得住懒得伪装的那批。采集者改一行 UA 字符串就能绕过,所以黑名单适合当第一层粗筛用来降噪,不能当主防线。
更可靠的是行为特征判定:不请求静态资源、无 Cookie、固定间隔匀速请求、翻页页码深得离谱。这些特征比 UA 难伪造得多,也正好能解决统计污染这个具体问题。
Q:怎么把爬虫流量占比做成日常监控?
最简单的方式是每天定时跑一条统计脚本,把爬虫请求占比记下来,比例突然抬升就是信号。
#!/bin/bash
# crawler-ratio.sh —— 统计昨日爬虫请求占比
LOG=/var/log/nginx/access.log
DAY=$(date -d yesterday +%d/%b/%Y)
TOTAL=$(grep "$DAY" $LOG | wc -l)
BOT=$(grep "$DAY" $LOG | grep -Eic "curl|wget|python|scrapy|spider|bot|headless")
RATIO=$(awk -v b=$BOT -v t=$TOTAL 'BEGIN{if(t>0) printf "%.2f", b*100/t; else print 0}')
echo "$(date -d yesterday +%F) total=$TOTAL bot=$BOT ratio=${RATIO}%"
# crontab 每天 07:00 执行
# 0 7 * * * /usr/local/bin/crawler-ratio.sh >> /var/log/crawler-ratio.log
经验阈值:主流搜索引擎蜘蛛的正常占比一般在 5% 到 15%。如果占比超过 40%,或者一夜之间从 8% 跳到 35%,基本可以确定有人在批量采集。
Q:还有什么补充建议?
- 先分层再动手:日志污染用日志侧过滤解决,统计污染用统计侧过滤解决,别指望一条 UA 黑名单把两个问题一起解决。
- 保留原始日志:过滤规则一旦写进采集端就无法回溯,原始日志建议至少保留 7 天,方便事后重算口径。
- 给统计上报单独留通道:统计接口建议与业务接口分离,避免统计请求被限速规则误伤,造成数据断档。
- 把爬虫治理和 SEO 分开评估:拦截恶意爬虫时务必放行主流搜索引擎,用 UA 加官方 IP 段双重验证,别为了让数字干净把收录一起干掉。
- 数据异常先怀疑链路:修改统计代码、更换 CDN、调整缓存策略都会让数字跳变,排查爬虫之前先确认这些改动的时间点是否吻合。