fish2018/pansou · error

HTTP

Error message

HTTP %d

What it means

Inside doLingjiGET's retry loop, when a non-nil error occurred and lastErr ended up nil, the code fabricates a fallback error "HTTP <status>" from resp.StatusCode. This is a defensive branch for the case where the response indicated failure but no underlying error object was captured — it surfaces the numeric HTTP status as the failure reason.

Solutions

  1. Check the status number in the message: 403→add browser headers, 429→slow down/backoff, 404→update API path, 5xx→retry later
  2. Increase lingjiMaxRetries so transient 5xx/429 are retried with the existing exponential backoff
  3. Send browser-like User-Agent/Referer headers to avoid 403 from WAF
  4. Verify lingjiAPIBase URL is current if seeing 404
  5. Handle the response status explicitly (return on expected statuses) instead of relying on the nil-err fallback

Example fix

// before
lastErr = err
if lastErr == nil {
    lastErr = fmt.Errorf("HTTP %d", resp.StatusCode)
}
// after
if err != nil {
    lastErr = err
} else {
    lastErr = fmt.Errorf("HTTP %d", resp.StatusCode)
}
if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500 {
    cancel()
    continue // retryable
}
return nil, lastErr // non-retryable, fail fast
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check expected status handling before the retry loop
statusOK := func(code int) bool { return code >= 200 && code < 300 }
if resp != nil && !statusOK(resp.StatusCode) {
    return classifyAndMaybeRetry(resp.StatusCode) // 429/5xx retry, 4xx fail fast
}

Try / catch

body, err := doLingjiGET(client, apiURL, timeout)
if err != nil {
    var httpErr interface{ HTTPStatus() int }
    if errors.As(err, &httpErr) && httpErr.HTTPStatus() == http.StatusTooManyRequests {
        time.Sleep(longerBackoff)
        body, err = doLingjiGET(client, apiURL, timeout)
    }
    if err != nil {
        return nil, err
    }
}

Prevention

When it happens

Trigger: An attempt received an HTTP response whose status was treated as failure (non-2xx/expected), but err from the request was nil, so the loop synthesizes fmt.Errorf("HTTP %d", resp.StatusCode) as lastErr — e.g. 403, 404, 429, 502 from the API.

Common situations: Upstream returns 429 during bursts of search/detail calls; API path changed yielding 404; WAF returns 403 to non-browser clients; gateway 502/504 during API downtime. The error then surfaces wrapped by 搜索请求失败/详情请求失败 after all 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/1117af151c9e6a62. Report an issue: GitHub.

Appendix: source

Thrown at plugin/lingjisp/lingjisp.go:276

				if n > 0 {
					data = append(data, buffer[:n]...)
				}
				if readErr != nil {
					if strings.Contains(readErr.Error(), "EOF") {
						cancel()
						return data, nil
					}
					lastErr = readErr
					break
				}
			}
		} else {
			if resp != nil {
				resp.Body.Close()
			}
			lastErr = err
			if lastErr == nil {
				lastErr = fmt.Errorf("HTTP %d", resp.StatusCode)
			}
		}
		cancel()

		if attempt < lingjiMaxRetries-1 {
			time.Sleep(200 * time.Millisecond * time.Duration(1<<attempt))
		}
	}

	return nil, fmt.Errorf("重试 %d 次后失败: %w", lingjiMaxRetries, lastErr)
}

func dedupeLingjiItems(items []lingjiVideoItem) []lingjiVideoItem {
	seen := make(map[int]struct{})
	results := make([]lingjiVideoItem, 0, len(items))

	for _, item := range items {
		id := chooseLingjiID(item)

View on GitHub (pinned to beaa561337)