fish2018/pansou · error
[ ] 搜索请求返回 HTTP
Error message
[%s] 搜索请求返回 HTTP %d
What it means
After a successful round trip, fetchSearch checks resp.StatusCode != http.StatusOK and returns this formatted message instead of an error value — the site answered, but not with 200. Since the search is a form POST to a scrape endpoint, non-200 usually means anti-bot blocking, a redirect to a captcha/verification page, or the endpoint path having changed on the site.
Solutions
- Log resp.StatusCode and, on 403/503, fetch the response body to look for Cloudflare/captcha markers.
- Verify the search endpoint still exists by replaying the same POST with curl -v and the same headers setHeaders applies.
- Add/refresh browser-like headers (User-Agent, Referer, Cookie) in setHeaders to pass anti-bot checks.
- Respect rate limits: add delay/backoff between searches to avoid 429.
- If the site moved paths, update the searchPath constant to the new endpoint.
Example fix
// before
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("[%s] 搜索请求返回 HTTP %d", p.Name(), resp.StatusCode)
}
// after
if resp.StatusCode != http.StatusOK {
snippet, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
return nil, fmt.Errorf("[%s] 搜索请求返回 HTTP %d, body: %s", p.Name(), resp.StatusCode, snippet)
} Defensive patterns
Strategy: retry
Try / catch
results, err := p.Search(ctx, keyword)
if err != nil && strings.Contains(err.Error(), "HTTP 4") {
// 403/429: 等待退避后重试, 不要立即重复
time.Sleep(backoff)
results, err = p.Search(ctx, keyword)
} Prevention
- Throttle request rate to avoid 429/403 rate limiting and WAF blocks.
- Keep browser-like headers (User-Agent/Referer/Cookies) current to pass anti-bot checks.
- Alert on repeated non-200 codes — it usually means the site changed or is blocking you.
- Check the site manually in a browser when status codes change.
When it happens
Trigger: The site returns 301/302 (redirect client doesn't follow to 200), 403 (WAF/anti-bot blocks the request headers or IP), 404 (searchPath changed on the site), 429 (rate limited), or 5xx (server error) for the POST to baseURL+searchPath.
Common situations: Too-frequent scraping triggers 429/403 rate limiting; the site added Cloudflare/WAF protection that rejects the default User-Agent; the site restructured its search URL so the old path 404s; the site is temporarily down (502/503).
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/c3e8ffd7db6b7f40.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/5266ys/5266ys.go:186
if err != nil {
return nil, fmt.Errorf("[%s] 编码搜索关键词失败: %w", p.Name(), err)
}
form := "show=title%2Csmalltext&tempid=1&tbname=article&keyboard=" + url.QueryEscape(string(encoded)) + "&submit="
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+searchPath, strings.NewReader(form))
if err != nil {
return nil, fmt.Errorf("[%s] 创建搜索请求失败: %w", p.Name(), err)
}
setHeaders(req, baseURL+"/")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
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, 4<<20))
if err != nil {
return nil, fmt.Errorf("[%s] 读取搜索结果失败: %w", p.Name(), err)
}
decoded, err := decodeGB18030(body)
if err != nil {
return nil, fmt.Errorf("[%s] 解码搜索结果失败: %w", p.Name(), err)
}
doc, err := goquery.NewDocumentFromReader(strings.NewReader(decoded))
if err != nil {
return nil, fmt.Errorf("[%s] 解析搜索结果失败: %w", p.Name(), err)
}
return doc, nil
}
func (p *Plugin) fetchDetail(client *http.Client, detailURL string) ([]magnetItem, string, string) {
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)View on GitHub (pinned to beaa561337)