feat: 集成 openclaw-zero-token 反检测策略(Profile装饰、干净退出欺骗、AIMD反封延迟)
This commit is contained in:
@@ -27,6 +27,40 @@ CLI 自动完成:打开标签页 → 导航 → 粘贴参考图 → 输入提
|
|||||||
|
|
||||||
每次生图/下载成功或失败会自动写入日志:`scripts/output/logs/YYYY-MM-DD.log`,可 `cat` 或 `tail` 查看历史记录。
|
每次生图/下载成功或失败会自动写入日志:`scripts/output/logs/YYYY-MM-DD.log`,可 `cat` 或 `tail` 查看历史记录。
|
||||||
|
|
||||||
|
## 反检测策略
|
||||||
|
|
||||||
|
以下策略基于 openclaw-zero-token 项目研究,降低 Gemini 自动化检测风险。
|
||||||
|
|
||||||
|
### Profile 装饰
|
||||||
|
|
||||||
|
浏览器启动后,修改 Chrome Profile 文件使其看起来像真实用户创建的 Profile(而非空白自动化 Profile):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PROFILE="<user-data-dir>/Local State"
|
||||||
|
# 修改 profile.info_cache.Default.name 为有意义的名字
|
||||||
|
# 修改 profile.info_cache.Default.profile_color_seed 为合法的 SkColor 值
|
||||||
|
```
|
||||||
|
|
||||||
|
OpenClaw browser 工具的 Profile 位于 `~/.openclaw/browser/openclaw/user-data/`。
|
||||||
|
|
||||||
|
### 干净退出欺骗
|
||||||
|
|
||||||
|
每次生图前,将 `Preferences` 文件的 `exit_type` 设为 `"Normal"`、`exited_cleanly` 设为 `true`。防止 Chrome 显示崩溃恢复提示,避免"非正常退出"这种自动化特征。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node scripts/cli.js _clean_exit <profile-path>
|
||||||
|
# 或直接修改: <profile-path>/Default/Preferences → exit_type="Normal", exited_cleanly=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### AIMD 反封延迟
|
||||||
|
|
||||||
|
多轮对话生图时,使用 AIMD 算法控制请求间隔(非固定延迟):
|
||||||
|
- 成功:间隔减半(不低于 baseDelay 的一半)
|
||||||
|
- 被限流:间隔翻倍(不超过 maxDelay)
|
||||||
|
- 每次加 0-5 秒随机抖动
|
||||||
|
|
||||||
|
通过环境变量可调:`GEMINI_BASE_DELAY=15`(秒)、`GEMINI_MAX_DELAY=120`。
|
||||||
|
|
||||||
## 所有生图方式
|
## 所有生图方式
|
||||||
|
|
||||||
### 文生图
|
### 文生图
|
||||||
@@ -60,7 +94,7 @@ echo "提示词" | node scripts/cli.js generate --prompt stdin --mode single
|
|||||||
# 首轮(不加 --mode,保持标签页)
|
# 首轮(不加 --mode,保持标签页)
|
||||||
node scripts/cli.js generate --prompt "画一幅日落"
|
node scripts/cli.js generate --prompt "画一幅日落"
|
||||||
|
|
||||||
# 续次(不加 --mode)
|
# 续次(不加 --mode,间隔自动调整)
|
||||||
node scripts/cli.js generate --session <id> --prompt "加入一艘小船"
|
node scripts/cli.js generate --session <id> --prompt "加入一艘小船"
|
||||||
|
|
||||||
# 末轮(--mode single 自动关闭)
|
# 末轮(--mode single 自动关闭)
|
||||||
|
|||||||
@@ -348,6 +348,14 @@ close 参数:
|
|||||||
emit('progress', { step: 'connect', message: `Continuing session: ${sessionId} (mode: ${mode})` });
|
emit('progress', { step: 'connect', message: `Continuing session: ${sessionId} (mode: ${mode})` });
|
||||||
// Don't navigate — stay on the current chat page for multi-round
|
// Don't navigate — stay on the current chat page for multi-round
|
||||||
continuedSession = true;
|
continuedSession = true;
|
||||||
|
|
||||||
|
// AIMD anti-ban delay: break fixed-interval pattern between successive requests
|
||||||
|
const baseDelay = parseInt(process.env.GEMINI_BASE_DELAY, 10) || 15;
|
||||||
|
const maxDelay = parseInt(process.env.GEMINI_MAX_DELAY, 10) || 120;
|
||||||
|
const jitter = Math.random() * 5;
|
||||||
|
const delaySec = Math.min(maxDelay, baseDelay * 0.7 + jitter);
|
||||||
|
emit('progress', { step: 'anti-ban', message: `AIMD delay ${delaySec.toFixed(1)}s (breaking fixed-interval pattern)` });
|
||||||
|
await sleep(delaySec * 1000);
|
||||||
} else if (args.chatUrl) {
|
} else if (args.chatUrl) {
|
||||||
emit('progress', { step: 'navigate', message: `Navigating to chat URL: ${args.chatUrl}` });
|
emit('progress', { step: 'navigate', message: `Navigating to chat URL: ${args.chatUrl}` });
|
||||||
await page.goto(args.chatUrl, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
await page.goto(args.chatUrl, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Anti-ban adaptive delay controller.
|
||||||
|
* AIMD (Additive Increase Multiplicative Decrease) algorithm.
|
||||||
|
* - Success: delay *= 0.7 (minimum: baseDelay * 0.5)
|
||||||
|
* - Rate limit / timeout: delay *= 2 (maximum: maxDelay)
|
||||||
|
* - Each wait adds 0-jitterMax seconds of random jitter
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface AntiBanConfig {
|
||||||
|
baseDelay: number; // seconds
|
||||||
|
maxDelay: number; // seconds
|
||||||
|
jitterMax: number; // seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG: AntiBanConfig = {
|
||||||
|
baseDelay: Number(process.env.GEMINI_BASE_DELAY) || 15,
|
||||||
|
maxDelay: Number(process.env.GEMINI_MAX_DELAY) || 120,
|
||||||
|
jitterMax: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class AntiBanController {
|
||||||
|
private currentDelay: number;
|
||||||
|
private config: AntiBanConfig;
|
||||||
|
retryCount = 0;
|
||||||
|
rateLimitEvents = 0;
|
||||||
|
|
||||||
|
constructor(config?: Partial<AntiBanConfig>) {
|
||||||
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||||
|
this.currentDelay = this.config.baseDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Call after a successful generation */
|
||||||
|
onSuccess(): void {
|
||||||
|
this.currentDelay = Math.max(this.config.baseDelay * 0.5, this.currentDelay * 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Call after rate limit / timeout / error */
|
||||||
|
onRateLimit(): void {
|
||||||
|
this.rateLimitEvents++;
|
||||||
|
this.currentDelay = Math.min(this.config.maxDelay, this.currentDelay * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wait between requests with jitter */
|
||||||
|
async wait(): Promise<void> {
|
||||||
|
const jitter = Math.random() * this.config.jitterMax;
|
||||||
|
const totalMs = (this.currentDelay + jitter) * 1000;
|
||||||
|
const log = (await import('./browser.js')).log;
|
||||||
|
log(`[anti-ban] waiting ${(totalMs / 1000).toFixed(1)}s (base=${this.currentDelay.toFixed(1)}s, jitter=${jitter.toFixed(1)}s)`);
|
||||||
|
await new Promise((r) => setTimeout(r, totalMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
get delay(): number {
|
||||||
|
return this.currentDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stats for logging */
|
||||||
|
get stats() {
|
||||||
|
return {
|
||||||
|
currentDelay: this.currentDelay,
|
||||||
|
rateLimitEvents: this.rateLimitEvents,
|
||||||
|
retryCount: this.retryCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user