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

  1. Check the wrapped inner error ('status %d' or the transport error) to see the actual cause before changing code
  2. Verify the ikantv upstream URL is reachable from the deployment (curl the search endpoint)
  3. Increase maxRetries or add backoff between attempts for transient upstream failures
  4. Set a realistic User-Agent/headers so the site does not reject the request
  5. 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

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)