fish2018/pansou · error
状态码
Error message
状态码 %d
What it means
doRequestWithRetry treats any non-200 status as a retriable failure. When a response arrives with a status other than 200, it constructs this plain error (no wrapped cause) as lastErr, closes the body, and retries; if all attempts fail, this error is returned to fetchPage and surfaces as error 817.
Solutions
- Log the status code and response headers/body snippet to identify 403 vs 429 vs 5xx and act accordingly.
- For 429, add exponential backoff and respect any Retry-After header before retrying.
- For 403, refresh cookies/User-Agent/headers in setRequestHeaders to look like a real browser.
- Reduce request concurrency and add pacing to avoid triggering blocks.
- Retry later for 5xx — the failure is server-side.
Example fix
// before
lastErr = fmt.Errorf("状态码 %d", resp.StatusCode)
resp.Body.Close()
// after
lastErr = fmt.Errorf("状态码 %d (%s)", resp.StatusCode, http.StatusText(resp.StatusCode))
if resp.StatusCode == http.StatusTooManyRequests {
select {
case <-time.After(backoff):
case <-req.Context().Done():
return nil, req.Context().Err()
}
}
resp.Body.Close() Defensive patterns
Strategy: retry
Validate before calling
req.Header.Set("User-Agent", realisticUA)
req.Header.Set("Accept", "text/html,application/xhtml+xml")
// send a HEAD/probe first and bail out early on 403/429
probe, _ := client.Head(siteURL)
if probe != nil && (probe.StatusCode == 403 || probe.StatusCode == 429) {
return fmt.Errorf("blocked upstream, status %d", probe.StatusCode)
} Type guard
func isTransientStatus(code int) bool { return code == 429 || code >= 500 } Try / catch
resp, err := doRequestWithRetry(client, req)
if err != nil {
if strings.Contains(err.Error(), "429") || strings.Contains(err.Error(), "403") {
time.Sleep(respectBackoff)
}
return pageResult{}, err
} Prevention
- Back off exponentially on 429 and honor Retry-After headers.
- Refresh cookies/User-Agent when 403s start appearing.
- Throttle concurrency and add per-request pacing to avoid WAF blocks.
- Retry only transient statuses (429/5xx); fail fast on 403/404.
When it happens
Trigger: The Xiaoyu search endpoint responded with a status other than 200 (403 anti-bot block, 429 rate limit, 404 for unsupported page numbers, 5xx server errors) on every retry attempt.
Common situations: IP blocked by the site's WAF after aggressive scraping; request rate exceeded limits; missing/stale cookies or headers causing 403; requesting a page index the site does not serve; transient 5xx during site maintenance.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/b5e69a87cc1a8efa.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/xiaoyu/xiaoyu.go:194
}
func doRequestWithRetry(client *http.Client, req *http.Request) (*http.Response, error) {
var lastErr error
for attempt := 0; attempt <= maxRetries; attempt++ {
if attempt > 0 {
time.Sleep(time.Duration(attempt) * 200 * time.Millisecond)
}
resp, err := client.Do(req.Clone(req.Context()))
if err != nil {
lastErr = err
continue
}
if resp.StatusCode == http.StatusOK {
return resp, nil
}
lastErr = fmt.Errorf("状态码 %d", resp.StatusCode)
resp.Body.Close()
}
return nil, lastErr
}
func parsePage(doc *goquery.Document) pageResult {
result := pageResult{TotalPages: parseTotalPages(doc)}
doc.Find(".search-list .item").Each(func(_ int, item *goquery.Selection) {
parsed, ok := parseItem(item)
if ok {
result.Results = append(result.Results, parsed)
}
})
return result
}
func parseTotalPages(doc *goquery.Document) int {
pageText := cleanText(doc.Find(".count strong").Eq(1).Text())View on GitHub (pinned to beaa561337)