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

  1. Retry after a delay on 429/5xx, respecting any Retry-After header
  2. Add per-request jitter/delays and cap concurrency to avoid rate limits
  3. Send browser-like headers (already sets UA/Accept; add Referer/Cookie if needed) to pass WAF checks
  4. 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

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)