fish2018/pansou · error
HTTP error (page , type )
Error message
HTTP error (page %d, type %s): %d
What it means
Per-page sentinel in sousou's concurrent fetch: the request for a given page/diskType succeeded at transport level but returned a status other than 200. Page number and disk type identify the exact failed shard; sent to errChan so one bad page does not kill the others.
Solutions
- Retry after a delay on 429/5xx, respecting any Retry-After header
- Add per-request jitter/delays and cap concurrency to avoid rate limits
- Send browser-like headers (already sets UA/Accept; add Referer/Cookie if needed) to pass WAF checks
- Log the response body on non-200 to see the WAF or API error detail
Example fix
// before
if resp.StatusCode != 200 {
errChan <- fmt.Errorf("HTTP error (page %d, type %s): %d", pageNum, diskType, resp.StatusCode)
return
}
// after
if resp.StatusCode == http.StatusTooManyRequests {
errChan <- fmt.Errorf("rate limited (page %d, type %s), retry after %s", pageNum, diskType, resp.Header.Get("Retry-After"))
return
}
if resp.StatusCode != 200 {
errChan <- fmt.Errorf("HTTP error (page %d, type %s): %d", pageNum, diskType, resp.StatusCode)
return
} Defensive patterns
Strategy: retry
Validate before calling
// after client.Do
if resp.StatusCode != 200 {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
return fmt.Errorf("status %d body=%.512s", resp.StatusCode, body)
} Type guard
func retryableStatus(code int) bool { return code == 429 || code == 502 || code == 503 || code == 504 } Try / catch
if err != nil {
var statusErr *StatusError
if errors.As(err, &statusErr) && retryableStatus(statusErr.Code) {
time.Sleep(backoff); goto retry
}
} Prevention
- Limit concurrent worker goroutines and add jitter delays
- Honor Retry-After headers on 429
- Send Referer/Cookie headers to pass WAF checks
- Log the body of non-200 responses to diagnose WAF vs API errors
When it happens
Trigger: The sousou API endpoint returns 403 (anti-bot/Cloudflare), 429 (rate limited), 404 (endpoint changed), or 5xx for the given page/type request.
Common situations: Rapid fan-out of parallel page requests triggers rate limiting; the site puts a WAF/CDN challenge in front of the API; the API path changed after a site update.
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/5371b3776a8fb254.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/sousou/sousou.go:426
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Referer", "https://sousou.pro/")
// 发送请求
resp, err := client.Do(req)
if err != nil {
debugLog("请求失败 (page %d, type %s): %v", pageNum, diskType, err)
errChan <- fmt.Errorf("request failed (page %d, type %s): %w", pageNum, diskType, err)
return
}
defer resp.Body.Close()
debugLog("收到响应 (page %d, type %s), 状态码: %d", pageNum, diskType, resp.StatusCode)
// 检查状态码
if resp.StatusCode != 200 {
debugLog("HTTP错误 (page %d, type %s): %d", pageNum, diskType, resp.StatusCode)
errChan <- fmt.Errorf("HTTP error (page %d, type %s): %d", pageNum, diskType, resp.StatusCode)
return
}
// 读取响应体
respBody, err := io.ReadAll(resp.Body)
if err != nil {
debugLog("读取响应失败 (page %d, type %s): %v", pageNum, diskType, err)
errChan <- fmt.Errorf("read response body failed (page %d, type %s): %w", pageNum, diskType, err)
return
}
debugLog("响应内容 (page %d, type %s, 前500字符): %s", pageNum, diskType, string(respBody[:min(500, len(respBody))]))
// 解析响应
var apiResp SousouResponse
if err := json.Unmarshal(respBody, &apiResp); err != nil {
debugLog("JSON解析失败 (page %d, type %s): %v", pageNum, diskType, err)
errChan <- fmt.Errorf("decode response failed (page %d, type %s): %w", pageNum, diskType, err)View on GitHub (pinned to beaa561337)