fish2018/pansou · error

[ ] 搜索返回状态码

Error message

[%s] 搜索返回状态码: %d

What it means

searchImpl rejects any alupan search response whose HTTP status code is not 200. The site responded, but with an error/redirect status (e.g. 403 anti-bot block, 429 rate limit, 5xx server error, or 3xx redirect not followed), so the HTML parsing step would be meaningless.

Solutions

  1. Log resp.Status and the response body snippet to see what the site actually returned (403 pages often contain a WAF challenge).
  2. Compare headers sent by setCommonHeaders with a real browser request; update User-Agent/Referer/Accept to avoid bot detection.
  3. Honor 429: add backoff (retryBaseDelay already exists) and reduce request concurrency (maxConcurrency = 12 may be too aggressive).
  4. Handle 3xx explicitly by checking client.CheckRedirect if redirects are being stopped.
  5. Treat 5xx as transient — surface it to the retry loop instead of failing immediately.

Example fix

// before
if resp.StatusCode != http.StatusOK {
    return nil, fmt.Errorf("[%s] 搜索返回状态码: %d", p.Name(), resp.StatusCode)
}
// after
if resp.StatusCode == http.StatusTooManyRequests {
    time.Sleep(retryBaseDelay * 2)
    // retry or return a typed rate-limit error
}
if resp.StatusCode != http.StatusOK {
    body, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
    return nil, fmt.Errorf("[%s] 搜索返回状态码: %d, body: %s", p.Name(), resp.StatusCode, body)
}
Defensive patterns

Strategy: fallback

Validate before calling

// After receiving a response, inspect status before parsing
if resp.StatusCode != http.StatusOK {
    body, _ := io.ReadAll(io.LimitReader(resp.Body, 256))
    log.Printf("aliupan status=%d body=%q", resp.StatusCode, body)
}

Try / catch

// Go: treat non-200 as a typed condition and fall back
results, err := plugin.Search(keyword, ext)
if err != nil && strings.Contains(err.Error(), "搜索返回状态码") {
    // extract code, apply backoff for 429/5xx, then continue with other plugins
    time.Sleep(retryBaseDelay)
}

Prevention

When it happens

Trigger: doRequestWithRetry returns a response whose StatusCode != http.StatusOK — typically 403/429 (bot protection, rate limiting), 404 (site structure changed), 500/502/503 (server-side problems), or 301/302 when redirects are disabled.

Common situations: The site enabled Cloudflare or WAF bot detection and returns 403 to the plugin's headers; the host is rate-limited after heavy scraping (429); the site is temporarily down (5xx); a URL or redirect policy change in the http.Client stops redirects from being followed.

Related errors


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

Appendix: source

Thrown at plugin/alupan/alupan.go:138

	searchURL := fmt.Sprintf("https://www.aliupan.com/?s=%s", url.QueryEscape(keyword))
	ctx, cancel := context.WithTimeout(context.Background(), searchTimeout)
	defer cancel()

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

	setCommonHeaders(req, "https://www.aliupan.com/")

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

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("[%s] 搜索返回状态码: %d", p.Name(), resp.StatusCode)
	}

	doc, err := goquery.NewDocumentFromReader(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("[%s] 解析搜索页面失败: %w", p.Name(), err)
	}

	var (
		results []model.SearchResult
		wg      sync.WaitGroup
		mu      sync.Mutex
		sem     = make(chan struct{}, maxConcurrency)
	)

	doc.Find("article.excerpt").Each(func(_ int, item *goquery.Selection) {
		titleSel := item.Find("header h2 a")
		title := strings.TrimSpace(titleSel.Text())
		detailURL, ok := titleSel.Attr("href")

View on GitHub (pinned to beaa561337)