本文目录导读:

- 第一层:基础检测与拦截(防小白/低端爬虫)
- 第二层:频率控制与限流(防高并发暴力抓取)
- 第三层:行为分析(防模拟浏览器的高级爬虫)
- 第四层:签名与混淆(动态请求令牌)
- 第五层:被动验证(强制人机验证)
- 生产环境落地建议(PHP 代码示例)
- 核心误区与警告(必读)
- 应对不同场景的策略选择
在 PHP 项目中应对爬虫,需要根据爬虫的友好程度(善意搜索引擎 vs 恶意采集)和攻击强度(简单请求 vs 高并发)采取分层防御策略。
以下是针对 PHP 项目的实战防御方案,从“温和”到“强硬”分为五个层级:
第一层:基础检测与拦截(防小白/低端爬虫)
这一层主要针对不伪装 UA(User-Agent)或明显异常的请求。
-
校验 User-Agent(UA):
- 封禁空 UA 或包含
python-requests、Go-http-client、Scrapy等关键字。 - 注意: 高段位爬虫会伪装成 Chrome,所以这只是基础门槛。
- 封禁空 UA 或包含
-
IP 黑白名单:
- 在
nginx或 应用层封禁已知的恶意 IP 段(如某些 IDC 机房 IP)。 - 使用 PHP 获取
$_SERVER['REMOTE_ADDR']进行数据库查询判断。
- 在
-
HTTP 请求头校验:
- 检查
Accept-Language、Referer是否符合常规浏览器逻辑。
- 检查
-
JS 渲染检测:
- 这是区分浏览器和 Curl 最有效的方式之一。
- PHP 输出页面时,通过
Set-Cookie设置一个由 JS 生成的加密值,如果后续请求没有携带这个 Cookie,判定为爬虫(meta refresh跳转或 返回 403)。
第二层:频率控制与限流(防高并发暴力抓取)
单纯靠 UA 和 IP 封禁不够,因为爬虫可以拨号换 IP,最核心的机制是 限流。
-
Redis 计数器(滑动窗口):
- 针对
IP + URL设定单位时间访问上限。 - 代码逻辑(伪代码):
$key = "rate_limit:{$ip}:{$url}"; $count = $redis->incr($key); if ($count == 1) { $redis->expire($key, 60); } if ($count > 30) { http_response_code(429); exit('Too Many Requests'); }
- 针对
-
并发数控制(令牌桶):
- 防止爬虫开启 100 线程并发请求。
- 使用 Redis 的
INCR记录当前正在处理的单 IP 请求数,超过阈值(如 5)直接拒绝。
-
Tarpit(慢速陷阱):
- 识别出疑似爬虫的 IP 后,不直接拒绝,而是让 PHP
sleep(5)再返回,消耗爬虫的线程池和网络连接数,降低其抓取效率。
- 识别出疑似爬虫的 IP 后,不直接拒绝,而是让 PHP
第三层:行为分析(防模拟浏览器的高级爬虫)
高级爬虫会携带正确的 Cookie、模拟浏览器行为,但其点击路径与人类不同。
-
鼠标轨迹与点击事件:
- 在前端埋点,记录用户从
mousedown到mouseup的轨迹坐标和时长。 - 将加密的数据 POST 给后端 PHP 接口,如果请求数据为空或坐标杂乱,判定为爬虫。
- 在前端埋点,记录用户从
-
Cookie 存活期追踪:
- 人类用户访问 A 页面后,会隔几秒再访问 B 页面。
- 爬虫通常是毫秒级连续请求,PHP 可以计算两次请求的时间差(
$_SERVER['REQUEST_TIME_FLOAT']差值),如果单 IP 的请求间隔普遍小于 200ms,且无mousemove事件,则标记为爬虫。
-
蜜罐页面(Honeypot):
- 在页面 HTML 中放置一个 CSS
display:none且visibility:hidden的链接(/hidden-link-123)。 - 正常用户看不到,永远不会点击,爬虫抓取该页面时,会顺着链接爬到蜜罐。
- 在
robots.txt中不要屏蔽这个链接,一旦有 IP 请求了蜜罐链接,PHP 记录该 IP 并永久封禁。
- 在页面 HTML 中放置一个 CSS
第四层:签名与混淆(动态请求令牌)
这一层主要防止爬虫直接调用你的 API 接口(接口被扒)。
-
动态 Token(HMAC 签名):
- 前端每次请求前,通过 JS 生成一个
sign(包含时间戳 + 随机数 + 通过 JS 算法处理后的密钥)。 - 后端 PHP 校验
sign是否有效以及时间戳是否过期(允许 5 分钟误差)。 - 防御对象: 能有效阻止只会用
file_get_contents拉取 HTML 的爬虫。
- 前端每次请求前,通过 JS 生成一个
-
CSS 字体反爬(针对文本内容):
- 如果网站是小说站或招聘站,将数字或特定字符映射到自定义字体文件(
@font-face)。 - 浏览器显示正常,但爬虫拿到的 HTML 源码是乱码。
- 如果网站是小说站或招聘站,将数字或特定字符映射到自定义字体文件(
第五层:被动验证(强制人机验证)
当服务器已经“高度怀疑”对方是爬虫时,API 不应直接返回数据,而是强制下发验证码。
- 滑块验证 / 点击验证:
- 在 PHP 后端实现一个接口逻辑:
/api/need_captcha。 - 当异常流量阈值被触发,PHP 接口返回 JSON 字段
"need_captcha": true,前端必须展示验证码。 - 使用 Google reCAPTCHA v3 或 极验等云服务商,PHP 端只需要调用他们的验证 API,无需自己实现图形识别。
- 在 PHP 后端实现一个接口逻辑:
生产环境落地建议(PHP 代码示例)
入口文件 index.php 拦截逻辑:
// 引入检测类
require_once 'src/BotDetector.php';
$detector = new BotDetector($redis);
$result = $detector->check();
if ($result === 'block') {
header('HTTP/1.1 403 Forbidden', true, 403);
header('Content-Type: text/plain; charset=utf-8');
exit('拒绝访问');
}
if ($result === 'captcha') {
// 前端 JS 展示验证码,并携带 token 重新请求
header('Content-Type: application/json');
echo json_encode(['need_captcha' => true, 'msg' => '请完成人机验证']);
exit;
}
// 正常逻辑...
require 'router.php';
核心 BotDetector 类实现(伪代码):
class BotDetector
{
public function check()
{
$ip = $_SERVER['REMOTE_ADDR'];
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
$now = time();
// 1. 封禁已知恶意 IP
if ($this->isBlackListed($ip)) return 'block';
// 2. 基础 UA 过滤
if (empty($ua) || preg_match('/Python|curl|wget|Go-http-client/i', $ua)) {
$this->recordMark($ip, 3); // 加重标记
}
// 3. 蜜罐 IP 检测
if ($this->isHoneypotHit($ip)) return 'block';
// 4. 高并发限流(Redis)
$count = $this->redis->incr("rate:{$ip}");
if ($count == 1) $this->redis->expire("rate:{$ip}", 60);
if ($count > 50) return 'block'; // 60秒内50次请求,太可疑
// 5. 判断是否需要验证码(异常标记 >= 2 分)
if ($this->getRiskScore($ip) >= 2) return 'captcha';
return 'pass';
}
}
核心误区与警告(必读)
- 不要完全依赖 IP 封禁: 爬虫多使用代理池,误封会导致整个局域网的正常用户无法访问。
- 防御重点在于“接口”而非“HTML”: App 端或前端 JS 调用的是同一个
/api/get_data.php,建议在 API 层(可能是MVC的 Controller 前)加Request Sign验证,而不要只防页面展示层。 - 区分搜索引擎蜘蛛: 记得放行
Googlebot、Baiduspider、Bingbot,否则网站 SEO 会受损,放行时反向验证 DNS 解析(通过gethostbyaddr反查 IP 是否属于 Google/百度官方 IP 段),防止伪造蜘蛛 UA。
应对不同场景的策略选择
| 攻击类型 | 推荐策略 | 实施成本 |
|---|---|---|
| 数据被全量爬走 | 行为分析 + 蜜罐 + 字体反爬 | 中高 |
| 服务器 CPU/带宽被打满 | Nginx 层 IP 限流(limit_req_zone)+PHP 层 Redis 计数 |
低 |
| 登录接口被撞库 | 极验/腾讯防水墙(第三方验证码) | 低 |
| 接口被刷(无页面) | HMAC 签名 + 时间戳校验 | 中 |
最简方案: 如果你只有半天时间,建议先做 UA 拦截 + Redis 限流 + 强制 JS Cookie 验证,这能挡住市面上 90% 的初级爬虫。