fish2018/pansou · error

服务器返回非200状态码

Error message

服务器返回非200状态码: %d

What it means

fetchFirstPage rejects any pansearch.me response whose status code is not 200 (after handling 404 separately). This wraps any unexpected HTTP status from the upstream Next.js data API (3xx redirect terminal responses, 403 bot-blocking, 429 rate limiting, 5xx outages) into a descriptive error carrying the numeric code.

Solutions

  1. Log resp.StatusCode and a body snippet to identify the exact upstream cause (403 vs 429 vs 5xx)
  2. Add exponential backoff retry for 429/5xx statuses
  3. Send full browser-like headers (User-Agent, Referer, Accept, Cookie) as fetchPage does, to avoid bot-blocking 403
  4. Throttle request rate to the upstream site; if persistent, treat the site as temporarily unavailable and degrade gracefully

Example fix

// before
if resp.StatusCode != 200 {
    return nil, 0, fmt.Errorf("服务器返回非200状态码: %d", resp.StatusCode)
}
// after
if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500 {
    return nil, 0, retryableStatusError{code: resp.StatusCode}
}
if resp.StatusCode != 200 {
    return nil, 0, fmt.Errorf("服务器返回非200状态码: %d, body: %.200s", resp.StatusCode, safeBodySnippet(resp))
}
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight reachability probe before batch searching:
// resp, err := client.Get("https://www.pansearch.me/")
// if err == nil && (resp.StatusCode == 403 || resp.StatusCode == 429) { backoff before proceeding }

Type guard

func isRetryableStatus(code int) bool {
    return code == http.StatusTooManyRequests || code >= 500
}

Try / catch

items, total, err := p.fetchFirstPage(ctx, query)
if err != nil {
    var statusErr *statusError
    if errors.As(err, &statusErr) && isRetryableStatus(statusErr.Code) {
        time.Sleep(backoff(attempt))
        items, total, err = p.fetchFirstPage(ctx, query)
    }
}
if err != nil { return err }

Prevention

When it happens

Trigger: fetchFirstPage (called by doSearch) receives resp.StatusCode not in {200, 404} from the pansearch.me /_next/data request, e.g. 403 from Cloudflare, 429 throttling, or 500/502/503 from the upstream server.

Common situations: Pansearch.me blocks non-browser traffic with 403; scraping too frequently triggers 429; the upstream site is down or deploying (5xx); a CDN edge returns an unusual status.

Related errors


AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07). Data as JSON: /api/errors/2e1fd03edcd38c85. Report an issue: GitHub.

Appendix: source

Thrown at plugin/pansearch/pansearch.go:615

	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
	req.Header.Set("Connection", "keep-alive")
	req.Header.Set("Cache-Control", "no-cache")
	req.Header.Set("Pragma", "no-cache")

	// 发送请求
	resp, err := client.Do(req)
	if err != nil {
		return nil, 0, fmt.Errorf("请求失败: %w", err)
	}
	defer resp.Body.Close()

	// 检查状态码
	if resp.StatusCode == 404 {
		return nil, 0, fmt.Errorf("404 Not Found,buildId可能已过期")
	}

	if resp.StatusCode != 200 {
		return nil, 0, fmt.Errorf("服务器返回非200状态码: %d", resp.StatusCode)
	}

	// 读取响应体
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, 0, fmt.Errorf("读取响应失败: %w", err)
	}

	// 解析响应
	var apiResp PanSearchResponse
	if err := json.Unmarshal(respBody, &apiResp); err != nil {
		return nil, 0, fmt.Errorf("解析响应失败: %w", err)
	}

	// 获取total和结果
	total := apiResp.PageProps.Data.Total
	items := apiResp.PageProps.Data.Data

View on GitHub (pinned to beaa561337)