
我维护着一个跨境电商平台,站点频繁遭遇大规模爬虫攻击——短时间内的高频请求直接拖垮了香港服务器前端的Nginx反向代理。传统的User-Agent或Referer黑名单策略早已失效,爬虫越来越“类人”,甚至模拟真实浏览器行为,连CDN的基础防护也被绕过。在这个背景下,我开始着手构建一套基于行为识别的反爬虫机制,并结合动态限速算法,有效减缓服务器压力,保障正常用户访问体验。
一、整体架构设计思路
目标是通过香港边缘服务器实施如下策略:
- 实时检测异常请求行为,判定是否为爬虫;
- 对可疑请求打标签,进入限速通道或直接拦截;
- 使用Nginx + Lua动态响应机制,不依赖后端逻辑;
- 所有行为数据写入Redis进行短期状态管理;
- 配合WAF、JS挑战和IP reputation,实现联动防御。
架构流程如下:
[公网访问]
|
[香港CDN边缘] (WAF + JS挑战)
|
[Nginx + Lua行为识别] ──> Redis行为状态缓存
|
后端服务(API、页面)
二、关键模块实操部署
1. 基于Nginx + OpenResty行为识别
首先在香港服务器上部署OpenResty(Nginx + LuaJIT集成版本):
apt update
apt install -y openresty redis-server
编写Lua模块:behavior_detect.lua
local cjson = require "cjson.safe"
local redis = require "resty.redis"
-- 请求行为分析:UA合法性 + Referer + 访问频率
local function analyze_behavior()
local headers = ngx.req.get_headers()
local ua = headers["User-Agent"]
local referer = headers["Referer"] or ""
local ip = ngx.var.remote_addr
local uri = ngx.var.uri
-- 简单User-Agent合法性判断
if not ua or ua == "" or ua:find("python") or ua:find("curl") then
return "bad_ua"
end
-- Redis连接
local red = redis:new()
red:set_timeout(100)
assert(red:connect("127.0.0.1", 6379))
-- 请求频率检测(每10秒超过10次则标记)
local key = "freq:" .. ip .. ":" .. uri
local count = red:incr(key)
if count == 1 then red:expire(key, 10) end
if count > 10 then
return "rate_limit"
end
return "ok"
end
local status = analyze_behavior()
if status == "bad_ua" or status == "rate_limit" then
ngx.exit(429)
end
在OpenResty中引用此模块:
server {
listen 80;
server_name example.com;
location / {
access_by_lua_file /etc/openresty/lua/behavior_detect.lua;
proxy_pass http://backend;
}
}
2. Redis行为状态缓存结构设计
使用Redis作为行为状态存储,支持以下几类Key:
| Key类型 | 示例 | 功能说明 |
|---|---|---|
freq:{ip}:{uri} |
freq:1.2.3.4:/api/goods |
用于滑动时间窗内的访问计数 |
flag:badua:{ip} |
flag:badua:1.2.3.4 |
标记非法UA访问行为 |
jscheck:{ip} |
jscheck:1.2.3.4 |
是否通过JavaScript挑战验证 |
通过 EXPIRE 设置失效时间(例如10分钟),实现临时惩罚机制。
三、加入JavaScript挑战验证机制(可选)
对高频触发的IP,通过设置 jscheck:{ip}=false 标记后,跳转至挑战页:
location / {
access_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:connect("127.0.0.1", 6379)
local ip = ngx.var.remote_addr
local passed = red:get("jscheck:" .. ip)
if passed == ngx.null then
return ngx.redirect("/challenge")
end
}
proxy_pass http://backend;
}
location = /challenge {
default_type text/html;
content_by_lua_block {
ngx.say("<script>local s=Date.now(); while(Date.now()-s<500){}</script>")
ngx.say("<script>location='/';</script>")
}
# 后端设置jscheck:{ip}=true
log_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:connect("127.0.0.1", 6379)
red:setex("jscheck:" .. ngx.var.remote_addr, 600, "true")
}
}
四、结合nginx rate-limiting模块进一步限速
http {
limit_req_zone $binary_remote_addr zone=limit_per_ip:10m rate=5r/s;
server {
location /api/ {
limit_req zone=limit_per_ip burst=10 nodelay;
access_by_lua_file /etc/openresty/lua/behavior_detect.lua;
proxy_pass http://api_backend;
}
}
}
五、与WAF/地理IP/ASN黑名单联动防御(增强策略)
建议对接以下辅助模块进一步强化识别精度:
- MaxMind IP库判断是否来自IDC机房(爬虫伪装常见);
- ASN识别(如AS37963、AS14061等云爬虫集群);
- JS指纹绑定识别(如cookie+UA绑定、字体探针等)。
六、实战效果与运维经验
实施该套方案后,原本日常遭遇的爬虫请求从 每小时8万次压测 降到 1万次以内,关键API负载明显回落,正常用户访问延迟控制在50ms以内。
运维层面建议:
- 每周复查Redis Key使用情况,避免泄露;
- Lua逻辑建议抽象为模块,便于A/B测试;
- 对接Zabbix或Prometheus观察被限频IP数、429返回数。
传统WAF已无法应对日益智能化的爬虫行为,必须将防御从静态黑名单转向动态行为识别与联动限速机制。基于香港边缘服务器部署此类防护,既贴近海外流量入口,又能有效分担后端压力,是我在实战中验证有效的一套技术解决方案。后续我也在尝试将机器学习模型引入Redis行为判断模块,进一步提高识别准确率。











