Commit db013080 authored by 郭阔's avatar 郭阔

feat: 站点熔断——同站撞反爬墙后,后续采集快速跳过

- harvest.sh 运行前检查 .wall-<port>:熔断期内同站源直接跳过(不碰浏览器/
  不耗 API),last.log 记'⏸ 站点熔断';12h 过期自动解除
- 触发条件:本次 FAILED 且 0 交付 + 反爬特征(≥3 篇 EMPTY_BODY/CAPTCHA/
  LOGIN_REQUIRED,或 0 尝试整场拒绝)
- keepalive.sh 每小时确认站点正常(wall=false)即删除熔断标记,自动恢复采集
- 调度中心熔断状态显示灰色徽标(区别于红失败)
- 实测:昨夜 reuters-world 墙记录判定触发;模拟熔断标记后 nikkei-world
  1 秒跳过、零副作用
parent f8b12362
......@@ -96,6 +96,33 @@ _nh_quit_instance() {
}
trap _nh_quit_instance EXIT
# ── 站点熔断(反爬墙) ─────────────────────────────────────────
# 同站近期确认撞反爬墙(0 交付+EMPTY_BODY/CAPTCHA 连片)时,后续同站采集直接跳过,
# 避免持续硬闯推高 DataDome 风险评分;保温访问确认站点恢复(wall=false)或超过 12h 自动解除。
_nh_wall_file="$_nh_lock_dir/.wall-${_nh_port:-nosite}"
if [ -f "$_nh_wall_file" ]; then
_wts="$(cat "$_nh_wall_file" 2>/dev/null || echo 0)"
_wage=$(( $(date +%s) - _wts ))
if [ "$_wage" -lt 43200 ]; then
echo "$(date '+%F %T') [breaker] 站点(端口 ${_nh_port})处于反爬墙熔断(已持续 $((_wage / 60)) 分钟),跳过 $SOURCE 本次采集"
{
echo "===== $(date '+%Y-%m-%d %H:%M:%S') 抓取 [$SOURCE] ====="
echo "状态: ⏸ 站点熔断(反爬墙未恢复)"
echo "Provider: (未启动)"
echo ""
echo "----- result -----"
echo "⏸ 站点熔断,本次未采集"
echo ""
echo "说明:"
echo "- 同站近期采集确认撞反爬墙(DataDome),本次按熔断策略跳过,未产生消耗。"
echo "- 保温访问确认站点恢复后自动解除;或最长 12 小时后过期。"
echo "- 需要立即恢复:面板 站点登录 → 该站人工登录,完成一次滑块验证。"
} > "$LOG_DIR/${SOURCE}.last.log"
exit 0
fi
rm -f "$_nh_wall_file" 2>/dev/null || true # 超过 12h,过期解除
fi
echo "排队中" > "$LOG_DIR/${SOURCE}.phase"
_queue_t0=$SECONDS
until mkdir "$_nh_queue_dir" 2>/dev/null; do
......@@ -376,6 +403,18 @@ if [ "$DENIALS" -gt 0 ] && [ "$STATUS_TAG" = "SUCCESS" ]; then
STATUS_TEXT="成功(但有 ${DENIALS} 次权限拒绝)"
fi
# --- 站点熔断触发:失败 + 0 交付 + 反爬特征(≥3 篇 EMPTY_BODY/CAPTCHA/LOGIN_REQUIRED,
# 或 0 尝试的整场拒绝)→ 写入站点熔断标记,后续同站采集快速跳过(见脚本头熔断闸) ---
if [ "$STATUS_TAG" = "FAILED" ] && command -v jq >/dev/null 2>&1 && [ -s "$JSON_OUT" ]; then
_b_succ="$(jq -r '.result.succeeded_count // 0' "$JSON_OUT" 2>/dev/null)"
_b_eff="$(jq -r '[.result.articles[]? | select(.failure_type == "EMPTY_BODY" or .failure_type == "CAPTCHA" or .failure_type == "LOGIN_REQUIRED")] | length' "$JSON_OUT" 2>/dev/null)"
_b_att="$(jq -r '.result.attempted_count // 0' "$JSON_OUT" 2>/dev/null)"
if [ "${_b_succ:-1}" = "0" ] && { [ "${_b_eff:-0}" -ge 3 ] || [ "${_b_att:-1}" = "0" ]; }; then
date +%s > "$_nh_wall_file" 2>/dev/null || true
echo "[breaker] 检测到反爬墙特征(0 交付,EMPTY_BODY/CAPTCHA ${_b_eff:-0} 篇,尝试 ${_b_att:-0} 篇),已触发站点熔断:后续同站采集将跳过,保温访问确认恢复后自动解除"
fi
fi
# --- 格式化耗时 ---
MINS=$((WALL_DURATION / 60))
SECS=$((WALL_DURATION % 60))
......
......@@ -36,6 +36,14 @@ export NH_KEEPALIVE_STATUS="${LOG_DIR:-$WORK_DIR/logs}/${SITE}.keepalive.json"
node "$SKILL_DIR/scripts/keepalive.js"
_rc=$?
# 站点正常即解除反爬熔断(harvest.sh 撞墙时写入 .wall-<port>,跳过后续同站采集)
if [ "$_rc" = 0 ]; then
if [ -f "$NH_LOCK_DIR/.wall-${PORT}" ]; then
rm -f "$NH_LOCK_DIR/.wall-${PORT}" 2>/dev/null \
&& echo "keepalive: 站点已恢复正常,解除反爬熔断(后续采集恢复)"
fi
fi
# 撞墙告警(exit 3):邮件提醒人工过一次滑块;6 小时节流,避免每小时重复轰炸
if [ "$_rc" = 3 ] && [ -x "$HERE/notify-mail.sh" ]; then
_alert_ts="$LOG_DIR/${SITE}.wall-alert.ts"
......
......@@ -107,7 +107,8 @@ function toggleEdit(name) {
{% if r.last %}
{{ r.last.mtime }}<br>
{% set st = r.last.status or '' %}
{% if '部分交付' in st or '⚠' in st %}<span class="badge warn">{{ st }}</span>
{% if '熔断' in st %}<span class="badge unknown">{{ st }}</span>
{% elif '部分交付' in st or '⚠' in st %}<span class="badge warn">{{ st }}</span>
{% elif '成功' in st %}<span class="badge ok">{{ st }}</span>
{% elif st %}<span class="badge fail">{{ st }}</span>{% endif %}
{% else %}<span style="color:#666">从未</span>{% endif %}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment