图片站、壁纸站、下载站最常遇到的故障不是「被打挂」,而是出向带宽被爬虫拉满:后台看 CPU、内存、磁盘全部正常,网站就是打不开,监控也没报错。本文用 9 个问答讲清爬虫占满带宽的识别、限速配置、按流量熔断和应急处置顺序,命令与配置可直接复制。
Q:怎么判断是爬虫跑满带宽,而不是被攻击?
先把三类资源消耗分开看,方向完全不同:
- 入向请求数:CC 或高频爬虫,表现是 QPS 飙升。
- 出向带宽:大文件被批量拉取,表现是网卡出口跑满。
- 并发连接数:慢速攻击,表现是连接表被占满。
图片站与下载站的爬虫有个鲜明特征:请求数不高,但每个请求都很「重」。一个爬虫 IP 每分钟只发 60 个请求,每个请求拉一张 2MB 的原图,就是 2MB/s,一天接近 170GB。
# 1. 看实时出向带宽:哪块网卡、哪些连接在跑
iftop -nNP -i eth0
# 2. 看接口出向总量(5 分钟粒度)
vnstat -5
# 3. 按「出向字节」排序,而不是按请求数
awk '{print $1, $10}' /var/log/nginx/access.log \
| awk '{s[$1]+=$2} END {for(i in s) print s[i], i}' \
| sort -rn | head -20
判读关键:把「请求数 Top IP」和「出向流量 Top IP」两份名单对比,如果基本不重合,说明存在典型的带宽型消耗(请求数榜上是扫描器,流量榜上才是拉图的爬虫)。只盯请求数排行,永远找不到这些人。
Q:怎么从日志里认出爬虫?只看User-Agent够吗?
不够。UA 一行就能改,正规爬虫框架也允许自定义 UA。可靠的判据是行为组合:
- 只请求图片、压缩包、附件,从不请求 HTML 首页和 CSS、JS。
- 从不带
Referer,或 Referer 是固定站外值。 - 请求间隔极规律,连续几十个请求的间隔标准差接近 0。
- 对同一张图带随机参数反复请求,绕过 CDN 缓存。
# 找出「只拉静态资源、从不访问首页」的 IP(集合差比 UA 判断可靠)
grep -E '\.(jpg|jpeg|png|gif|webp|zip|rar|pdf)$' access.log \
| awk '{print $1}' | sort -u > /tmp/static_only.txt
awk '$7 ~ /^\/(index|$)/ {print $1}' access.log \
| sort -u > /tmp/visited_home.txt
comm -23 /tmp/static_only.txt /tmp/visited_home.txt | head -50
这个「集合差」方法的逻辑很干净:正常访客一定先看页面再点开图片,只有采集脚本会直接拉资源。命中的 IP 集合可以直接拿去建封禁名单,误伤率极低。
Q:Nginx怎么对出向带宽限速?limit_rate怎么配?
核心两条指令:limit_rate 控制每秒传输字节数,limit_rate_after 让前 N 字节全速传输,保住正常用户的首屏体验。
location ~* \.(jpg|jpeg|png|gif|webp|zip|rar|pdf)$ {
limit_rate_after 512k; # 前 512KB 不限速,小图不卡
limit_rate 512k; # 之后单连接限到 512KB/s
expires 30d;
}
必须记住的关键点:limit_rate 是「按连接」而不是「按 IP」。一个 IP 开 20 个并发连接拉图,实际速率就是 20 倍。所以限速必须和限并发配合,单靠一条 limit_rate 形同虚设。
limit_conn_zone $binary_remote_addr zone=dl_conn:10m;
location ~* \.(jpg|jpeg|png|gif|webp|zip|rar|pdf)$ {
limit_conn dl_conn 8; # 单 IP 最多 8 个并发下载
limit_conn_status 429;
limit_rate_after 512k;
limit_rate 512k;
expires 30d;
}
Q:爬虫开并发绕过限速怎么办?三层怎么叠加?
限次数、限并发、限速率三层一起上,缺一层就漏。给出可直接落地的完整配置:
# 全局区:三个 zone
limit_req_zone $binary_remote_addr zone=img_req:10m rate=20r/s;
limit_conn_zone $binary_remote_addr zone=img_conn:10m;
# 注意:limit_req 与 limit_conn 不能写在 if 块里,只能放 server 或 location
server {
location ~* \.(jpg|jpeg|png|gif|webp)$ {
limit_req zone=img_req burst=40 nodelay;
limit_req_status 429;
limit_conn img_conn 8;
limit_conn_status 429;
limit_rate_after 512k;
limit_rate 512k;
}
}
如果想对正常用户更友好,可以给搜索引擎蜘蛛放行限次数但保留限速:
map $http_user_agent $img_limit_key {
default $binary_remote_addr;
"~*(Baiduspider|Googlebot|bingbot)" "";
}
map $http_user_agent $img_conn_key {
default $binary_remote_addr;
"~*(Baiduspider|Googlebot|bingbot)" $server_protocol;
}
limit_req_zone $img_limit_key zone=img_req2:10m rate=20r/s;
limit_conn_zone $img_conn_key zone=img_conn2:10m;
空字符串 key 在 limit_req 里不计入限速,所以蜘蛛不会被限次数;limit_conn 不接受空 key,这里换成一个固定值把蜘蛛归到同一组,速率限制仍然生效,等于免拦截但不免限速。
Q:请求数阈值对大文件站没用,怎么按流量封禁?
确实没用。一个 IP 拉 100 张原图就能吃掉 1GB,请求数可能只有三位数,怎么也触发不了阈值。必须按累计出向字节数封禁:
# 每 5 分钟跑一次:统计最近 5 分钟单 IP 出向流量,超 500MB 封 1 小时
awk -v d="$(date -d '5 min ago' '+%d/%b/%Y:%H:%M')" \
'$4 >= "["d {print $1, $10}' /var/log/nginx/access.log \
| awk '{s[$1]+=$2} END {for(i in s) if (s[i] > 500*1024*1024) print i}' \
| while read ip; do
ipset add dlban "$ip" timeout 3600 -exist
echo "$(date '+%F %T') ban $ip" >> /var/log/ipban.log
done
# 挂到 crontab,每 5 分钟一次
# */5 * * * * /usr/local/bin/ban_by_traffic.sh
两个关键细节:一是阈值要按站点正常用户出向流量的 P99 值来定,不是拍脑袋,因为图片站的正常用户一次也可能拉几十张图;二是封禁一律带 timeout,让误封自动消解,不要写永久黑名单。
Q:Range请求(断点续传)会不会被用来绕过限速?
不会绕过,但会放大并发。limit_rate 是按连接计速的,与请求的大小和是否分段无关,所以逐段拉取同样受限。真正需要防的是把一个文件切成上千个并发 Range 请求同时拉:
# 对支持 Range 的大文件,并发阈值可以压得更低
location ~* \.(zip|rar|iso|mp4)$ {
limit_conn dl_conn 2; # 单 IP 同时最多 2 个大文件
limit_rate_after 1m; # 前 1MB 全速,验证是否能正常下载
limit_rate 1m;
add_header Accept-Ranges bytes;
}
另外提醒一点:开启 sendfile on 后 limit_rate 依然有效,Nginx 会在内核路径上做速率控制,不需要为了限速去关掉 sendfile。如果发现限速没生效,先检查是不是被 CDN 缓存命中直接返回了,请求根本没到源站。
Q:CDN能帮忙扛住带宽吗?回源段要注意什么?
能,而且图片站的带宽成本结构应该是「CDN 承担 90%,源站只服务未命中和低频内容」。三个配置要点:
- 缓存 TTL 拉长:图片 URL 含内容哈希或时间戳时,TTL 可以设 30 天以上,命中率能到 95% 以上。
- 防盗链双保险:Referer 白名单加签名 URL。只做 Referer 白名单会被伪造,签名 URL 带过期时间才治本。
- 回源段同样要限速:很多人把 CDN 回源段加进白名单后就不管了,结果缓存被穿透时回源段成了无限通道。
# 源站侧:按服务名限制总回源并发,防止单个 CDN 节点把源站拉满
limit_conn_zone $server_name zone=origin_conn:10m;
server {
limit_conn origin_conn 200; # 全站回源并发上限
limit_conn_status 503;
}
需要监控的关键指标是回源请求数与回源带宽的比值:正常情况下带宽高、请求数低(都是大图);如果请求数异常上涨,说明有爬虫在带随机参数穿缓存,此时应该先去收拢缓存键,而不是加带宽。
Q:带宽已经被拉满、网站打不开了,先做什么?
按见效速度排序,前四步都是在省钱,最后一步才是花钱:
- 先限速,不封禁:改
limit_rate后 reload,秒级生效,且不会误伤任何正常访客。 - 给静态目录单独压限:把大文件目录的
limit_conn临时调到 2,立刻释放出带宽。 - 把流量最大的目录临时加鉴权或下线:优先保业务页面能打开。
- 按流量批量封禁:用上面那段按字节统计的脚本,带 timeout 执行。
- 最后才考虑切 CDN 或加带宽:这一步成本最高,而且如果不限速,加了带宽同样会被吃满。
# 改完配置先测语法再热加载,避免打不开的情况雪上加霜
nginx -t && nginx -s reload
最忌讳的动作是「一上来就封 IP」:带宽满的时候,大量请求已经在连接队列里,封 IP 的效果要等一个生效周期才显现,而限速是立刻在连接上生效的。而且大量封禁会带来后续的误封治理成本。
Q:还有什么补充建议?
- 给图片 URL 加签名或 TTL 是最治本的手段。抓取面被限制成「只能拿站内页面出现过、且未过期的链接」,批量拉取的收益直接归零。
- 分层看监控:只监控总带宽没有意义,要监控单 IP 出向流量的 P95、P99 分位数,才能提前发现个别 IP 的异常吃量。
- 定期审计出向带宽 Top 名单:把「连续多天稳定高频」的 IP 段沉淀为长期黑名单,比每次攻击临时统计高效。
- 源站带宽和 CDN 流量分开计量:优先让图片访问走 CDN,源站只保留必要的回源,爬虫就很难直接打到源站带宽。
一句话总结:带宽型爬虫防不住是因为「按请求数思考、按字节消耗」。把统计口径、限速对象、封禁维度全部换成字节,问题就清晰了。