TAOCARTS 知识

跨境站点机器人风控开发:拦截代购系统恶意刷单与爬虫攻击

2026-06-26 系统功能介绍

跨境站点的风控难点和国内站点不同,用户IP遍布全球、大量用户使用代理IP、动态IP,传统单一IP封禁、频次限制的风控规则很容易误杀正常用户。很多新手开发直接套用国内风控逻辑,导致大量海外正常用户无法注册、下单,严重影响业务转化,这也是跨境风控开发的核心难点。

我在设计Taocarts风控体系时,摒弃了粗暴的IP封禁模式,采用「行为校验+设备指纹+频次限流」的多维风控逻辑,精准区分真实用户与机器人脚本。

一、设备指纹:识别"谁"在访问

设备指纹是风控体系的第一道防线。跨境场景下,用户IP频繁变化,单纯依赖IP根本无法锁定同一设备。我采用服务端与客户端双重指纹采集方案,生成稳定的设备唯一标识。

服务端采集HTTP请求中的稳定特征字段,通过加权哈希生成设备指纹示例代码::

class DeviceFingerprint

{

private $secretKey = 'taocarts_device_secret';

public function generate(array $requestData): string

{

// 清洗UA:去除版本号、随机token等易变部分,保留浏览器类型、内核、OS大类

$ua = $this->cleanUserAgent($requestData['user_agent'] ?? '');

// 提取真实IP(需校验可信代理列表,避免被伪造)

$ip = $this->getRealClientIp($requestData);

// 稳定字段组合:UA + Accept-Language + Accept-Encoding

$fingerprintData = $ua

. ($requestData['accept_language'] ?? '')

. ($requestData['accept_encoding'] ?? '');

// 使用hash_hmac替代md5,防篡改也防暴力反查

return hash_hmac('sha256', $fingerprintData, $this->secretKey);

}

private function cleanUserAgent(string $ua): string

{

// 正则清洗:去掉版本号、随机token等易变部分

$ua = preg_replace('/\d+\.\d+\.\d+/', '', $ua);

$ua = preg_replace('/[a-f0-9]{32}/', '', $ua);

return trim($ua);

}

private function getRealClientIp(array $request): string

{

// 可信代理IP白名单

$trustedProxies = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'];

// 优先取HTTP_X_FORWARDED_FOR,取最左非白名单IP

if (!empty($request['http_x_forwarded_for'])) {

$ips = array_map('trim', explode(',', $request['http_x_forwarded_for']));

foreach ($ips as $ip) {

if (!$this->isPrivateIp($ip)) {

return $ip;

}

}

}

// fallback到HTTP_X_REAL_IP,最后才用REMOTE_ADDR

return $request['remote_addr'] ?? '0.0.0.0';

}

}

客户端侧配合JavaScript采集更丰富的浏览器特征(屏幕分辨率、时区、Canvas指纹、WebGL信息等),与服务端指纹交叉验证。Session ID和Cookie不能作为设备标识——它们反映的是"会话状态"而非"设备特征",容易被篡改或共享。

二、行为校验:判断"行为"是否像人

设备指纹解决了"谁在访问"的问题,行为校验则解决"访问是否正常"的问题。通过记录用户浏览轨迹、点击习惯、停留时长、操作间隔等行为数据,精准区分真实用户与机器人脚本。

前端埋点采集用户行为数据示例代码::

// 前端行为采集

class BehaviorCollector {

constructor() {

this.events = [];

this.startTime = Date.now();

this.init();

}

init() {

// 记录鼠标移动轨迹

document.addEventListener('mousemove', (e) => {

this.record('mousemove', {

x: e.clientX,

y: e.clientY,

timestamp: Date.now()

});

});

// 记录点击事件

document.addEventListener('click', (e) => {

this.record('click', {

target: e.target.tagName,

x: e.clientX,

y: e.clientY,

timestamp: Date.now()

});

});

// 记录键盘输入间隔

document.addEventListener('keydown', (e) => {

this.record('keydown', {

key: e.key.length === 1 ? '*' : e.key,

timestamp: Date.now()

});

});

// 页面离开时上报

window.addEventListener('beforeunload', () => {

this.report();

});

}

record(type, data) {

this.events.push({ type, ...data });

// 每50条批量上报一次

if (this.events.length >= 50) {

this.report();

}

}

report() {

if (this.events.length === 0) return;

navigator.sendBeacon('/api/behavior/report', JSON.stringify({

events: this.events,

sessionDuration: Date.now() - this.startTime,

screenWidth: window.screen.width,

screenHeight: window.screen.height

}));

this.events = [];

}

}

new BehaviorCollector();

三、频次限流:控制"频率"防刷单

针对高频风险场景设置专属防护规则,注册、下单、支付、铺货采集四大核心节点重点风控。限流算法我采用滑动窗口而非固定窗口——固定窗口在窗口切换时存在"临界突增"风险(例如第59秒和第60秒各发100次请求,实际1秒内涌入了200次)。

使用Redis有序集合实现滑动窗口限流示例代码::

class SlidingWindowRateLimiter

{

private $redis;

public function __construct($redis)

{

$this->redis = $redis;

}

/**

* 检查是否超过限流阈值

* @param string $key 限流key(如 device_id:123:order)

* @param int $limit 窗口内最大请求数

* @param int $window 时间窗口(秒)

* @return bool true=通过,false=被限流

*/

public function check(string $key, int $limit, int $window): bool

{

$now = microtime(true);

$windowStart = $now - $window;

// 移除窗口外的旧请求记录

$this->redis->zRemRangeByScore($key, 0, $windowStart);

// 获取当前窗口内的请求数

$count = $this->redis->zCard($key);

if ($count >= $limit) {

return false; // 被限流

}

// 记录本次请求(使用微秒时间戳作为member,保证唯一性)

$this->redis->zAdd($key, $now, $now . '_' . uniqid());

$this->redis->expire($key, $window + 1);

return true;

}

}

// 使用示例:限制单设备每日下单不超过50次

$limiter = new SlidingWindowRateLimiter($redis);

$key = "rate:device:" . $deviceId . ":order";

if (!$limiter->check($key, 50, 86400)) {

http_response_code(429);

echo json_encode(['error' => '下单过于频繁,请稍后再试']);

exit;

}

四、爬虫检测:拦截恶意数据抓取

针对高频商品采集请求,自动触发限流与验证码校验。我集成CrawlerDetect库检测已知爬虫——它能够识别超过1000种不同的机器人、爬虫和蜘蛛示例代码:。

// 通过Composer安装: composer require jaybizzle/crawler-detect

require_once 'vendor/autoload.php';

use Jaybizzle\CrawlerDetect\CrawlerDetect;

class CrawlerGuard

{

private $detect;

public function __construct()

{

$this->detect = new CrawlerDetect();

}

public function intercept(): bool

{

// 1. 检测已知爬虫User-Agent

if ($this->detect->isCrawler()) {

$this->logBlock('known_crawler', $_SERVER['HTTP_USER_AGENT']);

return true;

}

// 2. 检测无User-Agent或异常UA(恶意爬虫常伪造或留空)

$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';

if (empty($ua) || strlen($ua) < 10) {

$this->logBlock('empty_ua', $ua);

return true;

}

// 3. 检测高频请求(与限流器联动)

$ip = $_SERVER['REMOTE_ADDR'];

if (!$this->rateLimiter->check("crawler:ip:" . $ip, 100, 60)) {

$this->logBlock('high_frequency', $ip);

return true;

}

return false;

}

// 4. 商品详情页额外防护:校验Referer

public function validateProductAccess(): bool

{

$referer = $_SERVER['HTTP_REFERER'] ?? '';

$host = $_SERVER['HTTP_HOST'];

// 无Referer或非本站来源,触发验证码

if (empty($referer) || !strpos($referer, $host)) {

return $this->requireCaptcha();

}

return true;

}

}

五、风控网关:统一拦截入口

所有风控逻辑集成到统一的中间件/网关层,在请求进入业务逻辑之前完成拦截判断示例代码::

class RiskControlGateway

{

private $deviceFingerprint;

private $behaviorAnalyzer;

private $rateLimiter;

private $crawlerGuard;

/**

* 风控网关入口 - 所有请求先过此处

*/

public function check(string $action, array $context): array

{

// 1. 生成/获取设备指纹

$deviceId = $this->deviceFingerprint->generate($_SERVER);

// 2. 爬虫检测(优先拦截)

if ($this->crawlerGuard->intercept()) {

return ['passed' => false, 'reason' => 'crawler_detected', 'code' => 403];

}

// 3. 频次限流

$rateKey = "rate:" . $action . ":" . $deviceId;

if (!$this->rateLimiter->check($rateKey, $this->getLimit($action), $this->getWindow($action))) {

return ['passed' => false, 'reason' => 'rate_limited', 'code' => 429];

}

// 4. 行为风险评分(敏感操作如注册、支付必检)

if (in_array($action, ['register', 'payment', 'order'])) {

$behavior = $this->getBehaviorData($deviceId);

$analysis = $this->behaviorAnalyzer->analyze($behavior);

if ($analysis['is_risky']) {

// 高风险操作触发二次验证

return [

'passed' => false,

'reason' => 'behavior_risky',

'risk_score' => $analysis['risk_score'],

'require_captcha' => true,

'code' => 428

];

}

}

// 5. 全部通过,记录访问日志

$this->logAccess($deviceId, $action, $context);

return ['passed' => true, 'device_id' => $deviceId];

}

private function getLimit(string $action): int

{

$limits = [

'register' => 3,

'order' => 50,

'payment' => 10,

'product_list' => 100,

'transship' => 20

];

return $limits[$action] ?? 100;

}

private function getWindow(string $action): int

{

$windows = [

'register' => 3600,

'order' => 86400,

'payment' => 600,

'product_list' => 60,

'transship' => 3600

];

return $windows[$action] ?? 60;

}

}

六、适配代购集运业务专属风控

针对代购集运、代购转运业务,系统还需识别以下异常操作示例代码::

class TransshipRiskGuard

{

public function checkTransshipOrder(array $order): array

{

$risks = [];

// 1. 短时间大量提交转运订单

$recentCount = $this->countRecentOrders($order['user_id'], 3600);

if ($recentCount > 20) {

$risks[] = '一小时内提交转运订单超过20单';

}

// 2. 重复提交合包/拆包申请

$duplicateCount = $this->countDuplicatePackageRequests($order['user_id'], 600);

if ($duplicateCount > 5) {

$risks[] = '十分钟内重复提交合包/拆包申请超过5次';

}

// 3. 高频修改收货地址

$addressChanges = $this->countAddressChanges($order['user_id'], 86400);

if ($addressChanges > 3) {

$risks[] = '24小时内修改收货地址超过3次';

}

if (!empty($risks)) {

$this->markRiskyAccount($order['user_id'], $risks);

return ['passed' => false, 'reasons' => $risks];

}

return ['passed' => true];

}

}

七、可视化风控配置与日志

系统支持后台可视化风控配置,管理员可自主调整限流阈值、风控等级,适配平台不同运营阶段的防护需求。同时留存完整风控日志,所有拦截记录、风险账号、异常IP全程溯源:

-- 风控日志表结构示例代码:

CREATE TABLE `risk_logs` (

`id` bigint(20) NOT NULL AUTO_INCREMENT,

`device_id` varchar(64) NOT NULL COMMENT '设备指纹',

`user_id` int(11) DEFAULT NULL COMMENT '用户ID(如有)',

`action` varchar(50) NOT NULL COMMENT '触发动作',

`risk_score` tinyint(4) DEFAULT NULL COMMENT '风险评分',

`block_reason` varchar(255) DEFAULT NULL COMMENT '拦截原因',

`request_uri` varchar(500) DEFAULT NULL COMMENT '请求URI',

`client_ip` varchar(45) DEFAULT NULL COMMENT '客户端IP',

`user_agent` varchar(500) DEFAULT NULL COMMENT 'User-Agent',

`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,

PRIMARY KEY (`id`),

KEY `idx_device_id` (`device_id`),

KEY `idx_user_id` (`user_id`),

KEY `idx_created_at` (`created_at`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;