应组合校验http_referer、accept、accept-language、sec-fetch-*字段及ip请求频次等多特征,仅对高风险路径启用毫秒级轻量判断,避免依赖单一user-agent或消耗请求体。

怎么在 Gin 中间件里识别爬虫特征而不是只看 User-Agent
单靠 User-Agent 字符串拦截爬虫基本无效——requests、Playwright、甚至浏览器插件都能随意伪造。真正有效的识别必须组合多个请求特征,且不能拖慢正常请求。
关键点在于:只对高风险路径(如 /api/、/search)做轻量校验;跳过静态资源和白名单 IP;所有判断必须在毫秒级完成。
-
HTTP_REFERER为空或明显异常(比如是https://google.com却在请求/user/profile) -
Accept头缺失text/html或application/json,而真实浏览器访问页面时几乎总带text/html -
Accept-Language格式不规范(如只有en而不是en-US,en;q=0.9) - 请求头中缺少
Sec-Fetch-*系列字段(现代 Chrome/Firefox 自动携带,脚本通常不发) - 同一 IP 在 1 秒内连续发起 3+ 个非静态路径的 GET 请求(用
sync.Map+ 时间戳缓存,不查 DB)
c.Request.URL.Path 怎么安全用于路由级防爬逻辑
直接写 c.Request.URL.Path == "/api/data" 看似简单,但要注意路径未解码、大小写、尾部斜杠等陷阱。Gin 的路由匹配已做归一化,中间件里应复用它而非自己解析。
更稳妥的做法是:在注册路由时显式打标签,或用 Gin 的 c.FullPath()(需确保路由已命名)。否则容易漏判 /api/data/ 和 /api/data 这类变体。
- 优先用
c.FullPath()而非c.Request.URL.Path,它返回注册时定义的原始路径模式(如/api/:id) - 若必须用原始路径,先调用
strings.TrimSuffix(c.Request.URL.Path, "/")统一处理尾部斜杠 - 避免对
/static/、/favicon.ico、/healthz等路径触发防爬逻辑 - 对 POST/PUT 请求,可额外检查
Content-Type是否为application/json,爬虫常忽略这个头
为什么不能在中间件里调用 c.ShouldBindJSON() 做防爬校验
因为 c.ShouldBindJSON() 会读取并消耗请求体(Body),后续业务 Handler 再调用就会读到空内容,导致接口报错 invalid request body。
防爬中间件的目标是“观察”,不是“消费”。要检查请求体字段,得用更底层的方式,且必须保证可重放。
- 改用
ioutil.ReadAll(c.Request.Body)读一次,再用bytes.NewBuffer()重新塞回去:c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - 或者干脆跳过 JSON 解析,只检查
Content-Length是否过大(如 >512KB)、Content-Type是否匹配预期 - 更推荐方案:只校验请求头 + 行为特征,把字段级校验留给业务 Handler(它本就要解析)
- 注意:如果用了
gin.Recovery(),它内部也会读 Body,顺序错乱会导致 panic
IP 限流和 Cookie 校验怎么配合才不误杀真人
单纯按 IP 限流极易误伤——公司出口 NAT、校园网、4G/5G 共享 IP 都可能让几十人共用一个 REMOTE_ADDR。Cookie 校验能补足这个缺口,但必须设计成“有则加强,无也不拦”。
典型做法是:首次访问给客户端种一个带签名的临时 Cookie(如 _sp=ts=1724052120;sig=abc123),后续请求验证签名和时间差。没 Cookie 的请求走宽松限流(如 30r/m),有且有效的走严格限流(如 5r/m)。
- Cookie 签名必须含时间戳和密钥,防止被伪造:
hmac.Sum256([]byte(fmt.Sprintf("%d:%s", time.Now().Unix(), secret))) - 不要依赖
session或后端存储——Gin 中间件里没现成 session 支持,硬加会拖慢所有请求 - 限流计数器建议用
map[string]int64+sync.RWMutex,每分钟清空,避免内存泄漏 - 对已登录用户,可结合
Authorization头里的 token 用户 ID 做二级限流,比 IP 更精准











