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
- Retry later or from a different IP — the challenge is often triggered by rate limiting or flagged IPs; reduce request frequency.
- Check whether the verification page markup changed and update verificationScriptRegex and the other verification*Regex patterns in plugin/qiwei/qiwei.go.
- Ensure the same *http.Client with a shared cookie jar is used across fetchBody and solveVerification, since the challenge is session-bound.
- Inspect the wrapped cause (errors.Unwrap / %v of the error) to see whether it was 未找到滑动验证脚本, 获取验证脚本失败, or 验证脚本参数不完整 and fix that specific step.
- 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
- Throttle requests and add jitter to avoid triggering the site's CAPTCHA in the first place.
- Use a session-persistent cookie jar on one http.Client for the whole detail flow.
- Alert on rate of verification-page responses — a spike means your IP is flagged.
- Keep the verification*Regex patterns covered by tests against saved page fixtures.
- Consider residential/proxy rotation when datacenter IPs are persistently challenged.
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)