fish2018/pansou · error
status
Error message
status %d
What it means
doRequestWithRetry in the ikantv plugin wraps its final failure in a Chinese message '重试 %d 次后仍然失败' (still failed after N retries). Inside the retry loop, when the HTTP call returns a response whose status is not acceptable, the plugin synthesizes a 'status %d' error from statusCode(resp). It signals that all retry attempts of the upstream search request were exhausted without success.
Solutions
- Check the wrapped inner error ('status %d' or the transport error) to see the actual cause before changing code
- Verify the ikantv upstream URL is reachable from the deployment (curl the search endpoint)
- Increase maxRetries or add backoff between attempts for transient upstream failures
- Set a realistic User-Agent/headers so the site does not reject the request
- Handle the returned error gracefully in doSearch callers and surface a user-facing 'source unavailable' message
Example fix
// before
lastErr = fmt.Errorf("status %d", statusCode(resp))
// after
lastErr = fmt.Errorf("upstream returned HTTP %d after %d retries", statusCode(resp), maxRetries) Defensive patterns
Strategy: retry
Validate before calling
// Go: preflight check before relying on the source
resp, err := http.Head(sourceBaseURL)
if err != nil || resp.StatusCode != http.StatusOK {
log.Printf("ikantv upstream unhealthy: status=%v err=%v", statusOf(resp), err)
} Type guard
func isRetryExhausted(err error) bool {
return err != nil && strings.Contains(err.Error(), "重试")
} Try / catch
results, err := p.doSearch(keyword)
if err != nil {
log.Printf("ikantv unavailable: %v", err)
results = []model.SearchResult{} // degrade gracefully
} Prevention
- Add exponential backoff between retry attempts
- Monitor upstream availability and alert on repeated retry exhaustion
- Keep headers/User-Agent current to avoid bot blocks
- Set realistic timeouts so transient slowness does not consume all retries
When it happens
Trigger: All maxRetries attempts of doRequestWithRetry fail; either every attempt returns a transport error, or every attempt returns a non-OK HTTP status so the loop stores fmt.Errorf("status %d", statusCode(resp)) as lastErr.
Common situations: ikantv upstream site is down or returning 5xx; site blocks the scraper with 403/429; network/DNS failures in the deployment environment; timeout values too aggressive so transient slowness exhausts retries.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/f11b908fd4466c61.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/ikantv/ikantv.go:221
for i := 0; i < maxRetries; i++ {
if i > 0 {
backoff := time.Duration(1<<uint(i-1)) * 200 * time.Millisecond
time.Sleep(backoff)
}
reqClone := req.Clone(req.Context())
resp, err := client.Do(reqClone)
if err == nil && resp.StatusCode == 200 {
return resp, nil
}
if resp != nil {
resp.Body.Close()
}
lastErr = err
if lastErr == nil {
lastErr = fmt.Errorf("status %d", statusCode(resp))
}
}
return nil, fmt.Errorf("重试 %d 次后仍然失败: %w", maxRetries, lastErr)
}
func statusCode(resp *http.Response) int {
if resp == nil {
return 0
}
return resp.StatusCode
}
type apiResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data []apiItem `json:"data"`
}View on GitHub (pinned to beaa561337)