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
- Log the status code to identify the class: 403/503 suggests bot-blocking, 404 suggests URL scheme change, 429/5xx suggests retry with backoff
- Update setRequestHeaders to mimic current browser headers (fresh User-Agent, cookie handling, Accept-Encoding)
- Handle site domain migration by updating the plugin's baseURL
- 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
- Parse the status code out of the message text (it is not wrapped with %w)
- Retry only 429/5xx with backoff; alert on 404 (URL scheme change) and 403 (bot block)
- Keep browser-like headers current
- Throttle request rate to avoid rate limiting
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)