fish2018/pansou · error

[ ] 请求返回状态码

Error message

[%s] 请求返回状态码: %d

What it means

ClmaoPlugin.searchPage received an HTTP response whose status code is not 200 and turns it into an error that includes the actual code. The retry helper doRequestWithRetry only retries transport errors, so a non-200 (403 Forbidden, 429 Too Many Requests, 503, 404) reaches this check on the first attempt and fails immediately — these statuses are not retried.

Solutions

  1. Log the status code and a response-body snippet to identify whether it's 403 (blocked), 429 (rate limited), or 404/5xx.
  2. For 403: refresh/update cookies and User-Agent in setRequestHeaders to match a real browser.
  3. For 429: back off and slow down request frequency; respect Retry-After header.
  4. For 404: check whether the site changed its search endpoint and update searchURL.
  5. Optionally extend doRequestWithRetry to retry on 429/5xx with backoff.

Example fix

// before
if resp.StatusCode != 200 {
    return nil, fmt.Errorf("[%s] 请求返回状态码: %d", p.Name(), resp.StatusCode)
}
// after
if resp.StatusCode == http.StatusTooManyRequests {
    if ra := resp.Header.Get("Retry-After"); ra != "" {
        if secs, e := strconv.Atoi(ra); e == nil { time.Sleep(time.Duration(secs) * time.Second) }
    }
    return nil, fmt.Errorf("[%s] rate limited (429)", p.Name())
}
if resp.StatusCode != 200 {
    return nil, fmt.Errorf("[%s] 请求返回状态码: %d", p.Name(), resp.StatusCode)
}
Defensive patterns

Strategy: fallback

Validate before calling

// pre-flight status probe
func statusOK(client *http.Client, url string) (bool, int) {
    resp, err := client.Get(url)
    if err != nil { return false, 0 }
    defer resp.Body.Close()
    return resp.StatusCode == http.StatusOK, resp.StatusCode
}

Try / catch

results, err := p.searchPage(client, keyword, page)
if err != nil {
    if strings.Contains(err.Error(), "429") {
        time.Sleep(rateLimitBackoff) // retry later
    } else if strings.Contains(err.Error(), "403") {
        refreshSessionCookies()
    }
    return nil, err
}

Prevention

When it happens

Trigger: p.doRequestWithRetry returns a valid *http.Response whose resp.StatusCode != 200 — e.g. 403 from missing/expired cookies, 429 after too-frequent searches, or 503 during maintenance — and searchPage returns "[clmao] 请求返回状态码: %d".

Common situations: Anti-bot protection flagging the default User-Agent; running many searches in a short window and hitting rate limits; the site changed its URL structure so the old search path now 404s; the site is temporarily under maintenance (503).

Related errors


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

Appendix: source

Thrown at plugin/clmao/clmao.go:194

	// 创建请求
	req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil)
	if err != nil {
		return nil, fmt.Errorf("[%s] 创建请求失败: %w", p.Name(), err)
	}

	// 设置请求头
	p.setRequestHeaders(req)

	// 发送HTTP请求
	resp, err := p.doRequestWithRetry(req, client)
	if err != nil {
		return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
	}
	defer resp.Body.Close()

	// 检查状态码
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("[%s] 请求返回状态码: %d", p.Name(), resp.StatusCode)
	}

	// 读取响应体内容
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("[%s] 读取响应失败: %w", p.Name(), err)
	}

	decodedHTML := decodeModernPayload(string(body))
	if decodedHTML != string(body) {
		if modernResults := p.parseModernSearchResults(client, decodedHTML); len(modernResults) > 0 {
			return modernResults, nil
		}
	}

	// 兼容旧模板
	doc, err := goquery.NewDocumentFromReader(strings.NewReader(decodedHTML))
	if err != nil {

View on GitHub (pinned to beaa561337)