fish2018/pansou · error

[ ] 搜索请求返回 HTTP

Error message

[%s] 搜索请求返回 HTTP %d

What it means

Returned by searchImpl when the HTTP response status code is anything other than 200 OK. The plugin treats only a 200 as a valid search response, so 403/429 anti-bot blocks, 5xx outages, and 3xx redirects that the client did not follow all land here. Note the status code is embedded with %d and NOT wrapped with %w, so it cannot be unwrapped programmatically — parse the message text to recover the code.

Solutions

  1. Log the status code to identify the class: 403/503 suggests bot-blocking, 404 suggests URL scheme change, 429/5xx suggests retry with backoff
  2. Update setRequestHeaders to mimic current browser headers (fresh User-Agent, cookie handling, Accept-Encoding)
  3. Handle site domain migration by updating the plugin's baseURL
  4. Add retry with exponential backoff for 429/5xx responses
Defensive patterns

Strategy: retry

Try / catch

var apiErr *APIStatusError // if your wrapper exposes one
if errors.As(err, &apiErr) {
    switch {
    case apiErr.Code == 429: time.Sleep(backoff); retry()
    case apiErr.Code >= 500: retry()
    case apiErr.Code == 404: alertURLSchemeChanged()
    }
}

Prevention

When it happens

Trigger: The haitunsou server responds with HTTP status != 200: 403 (Cloudflare/WAF block of the User-Agent), 429 rate limiting, 5xx server errors, 404 if the site changes its URL scheme from /s/<keyword>.html.

Common situations: Site deploys stricter anti-bot protection (JS challenge/Cloudflare) that the static Chrome User-Agent headers no longer satisfy; the site migrates domains or changes its search path; burst querying triggers rate limits.

Related errors


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

Appendix: source

Thrown at plugin/haitunsou/haitunsou.go:101

		client = http.DefaultClient
	}

	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	defer cancel()
	searchURL := fmt.Sprintf("%s/s/%s.html", strings.TrimRight(p.baseURL, "/"), url.PathEscape(keyword))
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL, nil)
	if err != nil {
		return nil, fmt.Errorf("[%s] 创建搜索请求失败: %w", p.Name(), err)
	}
	setRequestHeaders(req, p.baseURL)

	resp, err := client.Do(req)
	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] 搜索请求返回 HTTP %d", p.Name(), resp.StatusCode)
	}
	body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize+1))
	if err != nil {
		return nil, fmt.Errorf("[%s] 读取搜索响应失败: %w", p.Name(), err)
	}
	if len(body) > maxResponseSize {
		return nil, fmt.Errorf("[%s] 搜索响应超过 %d 字节", p.Name(), maxResponseSize)
	}

	items, err := parseEmbeddedList(body)
	if err != nil {
		return nil, fmt.Errorf("[%s] 解析搜索结果失败: %w", p.Name(), err)
	}
	results := make([]model.SearchResult, 0, len(items))
	seen := make(map[string]struct{}, len(items))
	for _, item := range items {
		result, ok := convertItem(item)
		if !ok {

View on GitHub (pinned to beaa561337)