fish2018/pansou · warning
[ ] 详情页验证未通过
Error message
[%s] 详情页验证未通过: %s
What it means
This error means the plugin solved the slider verification (or attempted to), re-fetched the detail page, but the response was STILL a verification page (isVerifyPage(body) true after solving). It indicates the automated challenge bypass did not convince the server, so the candidate URL is abandoned and this is recorded as lastErr for getDetailInfo.
Solutions
- Verify the same *http.Client (with its cookie jar) is reused for solveVerification and the re-fetch — a fresh client loses the session cookie that marks the challenge as solved.
- Capture and replay the verification request in a browser (DevTools) to confirm the expected value encoding (md5StringToHex) and endpoint path still match; update verification*Regex / endpoint logic if the site changed.
- Slow down request rate and add jitter/backoff — persistent challenges usually mean the IP/session is flagged.
- Increase candidate coverage by checking p.detailURLCandidates so another candidate URL may succeed.
- Manually inspect the re-fetched body: if it is a different challenge type, the deterministic slider solver no longer applies and needs reimplementation.
Example fix
// before
client := &http.Client{Timeout: detailTimeout} // new client each call, loses cookies
// after
client := &http.Client{Timeout: detailTimeout, Jar: sharedCookieJar} // reuse jar across solveVerification + re-fetch Defensive patterns
Strategy: fallback
Validate before calling
// verify the session actually retained the solve cookie before refetching
if len(client.Jar.Cookies(parsedURL)) == 0 {
return errors.New("cookie jar empty; verification session cannot persist")
} Type guard
func isVerifyNotPassedErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "详情页验证未通过")
} Try / catch
info, err := plugin.GetDetailInfo(client, detailURL, title, pic, false)
if isVerifyNotPassedErr(err) {
// drop to fallback data (base title/cover) and mark URL for a later retry
result.MarkNeedsRetry(detailURL)
return fallbackResult, nil
} Prevention
- Always reuse the same client (and its cookie jar) between solving and re-fetching.
- Replicate the verification request from a real browser to confirm value-encoding and endpoint still match.
- Back off exponentially after failed solves instead of hammering the endpoint.
- Rotate IPs/sessions when challenges persist across solved sessions.
- Monitor whether the challenge page returns a different variant; adapt the solver accordingly.
When it happens
Trigger: In getDetailInfo, after solveVerification succeeds, the follow-up p.fetchBody of candidateURL returns HTML that still matches isVerifyPage — the server re-issues the challenge for that session/candidate URL.
Common situations: The site invalidates the verification cookie server-side or issues a fresh challenge per request; the md5-encoded value sent to the verification endpoint is rejected (site changed hashing or parameter names); heavy request volume keeps the session flagged; cookies are not persisted because a new client/cookie jar was used between solve and re-fetch.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/7497315884a5246f.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/qiwei/qiwei.go:327
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
}
p.detailCache.Store(detailURL, detailCacheEntry{Info: info, CachedAt: time.Now()})
return info, nil
}
if lastErr == nil {
lastErr = fmt.Errorf("[%s] 获取详情失败: %s", p.Name(), detailURL)
}
return detailInfo{}, lastErrView on GitHub (pinned to beaa561337)