fish2018/pansou · warning

[ ] 详情页验证失败

Error message

[%s] 详情页验证失败: %w

What it means

This error wraps a failure that occurred while the plugin tried to solve the site's slider (CAPTCHA) verification challenge after the detail page fetch returned a verification page. It is produced inside getDetailInfo when solveVerification returns an error for a candidate URL, and the wrapped cause (e.g. missing verification script, script fetch failure, incomplete script parameters) is carried via %w. It means the plugin could not automatically pass the anti-bot challenge for that candidate URL, so it moves on to the next candidate and ultimately reports this as lastErr if all candidates fail.

Solutions

  1. Retry later or from a different IP — the challenge is often triggered by rate limiting or flagged IPs; reduce request frequency.
  2. Check whether the verification page markup changed and update verificationScriptRegex and the other verification*Regex patterns in plugin/qiwei/qiwei.go.
  3. Ensure the same *http.Client with a shared cookie jar is used across fetchBody and solveVerification, since the challenge is session-bound.
  4. Inspect the wrapped cause (errors.Unwrap / %v of the error) to see whether it was 未找到滑动验证脚本, 获取验证脚本失败, or 验证脚本参数不完整 and fix that specific step.
  5. Log the verifyHTML body to confirm isVerifyPage is not producing false positives on a normal page.

Example fix

// before
lastErr = fmt.Errorf("[%s] 详情页验证失败: %w", p.Name(), verifyErr)
continue
// after
// inspect the wrapped cause to react specifically
if errors.Is(verifyErr, errVerificationScriptNotFound) {
    logger.Warnf("%s: verification script missing, page markup may have changed", p.Name())
}
lastErr = fmt.Errorf("[%s] 详情页验证失败: %w", p.Name(), verifyErr)
continue
Defensive patterns

Strategy: retry

Validate before calling

// probe before parsing
detailTimeout := 15 * time.Second
body, err := fetchBody(client, url, url, detailTimeout)
if err == nil && isVerifyPage(body) {
    if err := plugin.SolveVerification(client, url, body); err != nil {
        logger.Warnf("verification unsolvable for %s: %v", url, err)
    }
}

Type guard

func isWrappedVerificationErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "详情页验证失败")
}

Try / catch

info, err := plugin.GetDetailInfo(client, detailURL, title, pic, false)
if err != nil {
    var wrapped error
    if errors.As(err, &wrapped) && strings.Contains(err.Error(), "详情页验证失败") {
        // fall back to cached/fallback title+cover, or queue for retry with backoff
        return fallbackResult, nil
    }
    return nil, err
}

Prevention

When it happens

Trigger: Calling buildResult/getDetailInfo when the target detail page responds with a verification page (detected by isVerifyPage) and solveVerification fails — e.g. the verification script tag does not match verificationScriptRegex, downloading the JS script fails, or the script contents lack the expected type/key/value parameters.

Common situations: The site changed its verification page markup or script URL so the regexes no longer match; the site hardened its CAPTCHA (session-bound challenge with rotating tokens); requests are being rate-limited or flagged as bot traffic; a proxy/CDN serves an interstitial challenge page to the scraper's datacenter IP.

Related errors


AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07). Data as JSON: /api/errors/b4ea45e1907bc7ba. Report an issue: GitHub.

Appendix: source

Thrown at plugin/qiwei/qiwei.go:318

	} else if cached, ok := p.detailCache.Load(detailURL); ok {
		if entry, ok := cached.(detailCacheEntry); ok {
			if time.Since(entry.CachedAt) < cacheTTL {
				return entry.Info, nil
			}
			p.detailCache.Delete(detailURL)
		}
	}

	var lastErr error
	for _, candidateURL := range p.detailURLCandidates(detailURL) {
		body, err := p.fetchBody(client, candidateURL, candidateURL, detailTimeout)
		if err != nil {
			lastErr = err
			continue
		}
		if isVerifyPage(body) {
			if verifyErr := p.solveVerification(client, candidateURL, body); verifyErr != nil {
				lastErr = fmt.Errorf("[%s] 详情页验证失败: %w", p.Name(), verifyErr)
				continue
			}
			body, err = p.fetchBody(client, candidateURL, candidateURL, detailTimeout)
			if err != nil {
				lastErr = err
				continue
			}
			if isVerifyPage(body) {
				lastErr = fmt.Errorf("[%s] 详情页验证未通过: %s", p.Name(), candidateURL)
				continue
			}
		}

		info, err := p.parseDetail(candidateURL, body, fallbackTitle, fallbackPic)
		if err != nil {
			lastErr = err
			continue
		}

View on GitHub (pinned to beaa561337)