fish2018/pansou · error
[ ] 搜索请求失败
Error message
[%s] 搜索请求失败: %w
What it means
CldiPlugin.searchPage wraps errors from doRequestWithRetry with the plugin name. This covers transport-level failures of the search request after internal retries: DNS failure, connection refused/reset, TLS errors, or context deadline exceeded (30s timeout). The underlying cause is preserved via %w.
Solutions
- Unwrap with errors.Is(err, context.DeadlineExceeded) to distinguish timeout from connection failure.
- Test reachability of the search URL with curl and the same headers.
- Increase the 30s timeout if the site is slow, or add proxy rotation if blocked.
- Retry with backoff; check for rate limiting or IP bans.
Example fix
// before
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
// after
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, fmt.Errorf("[%s] 搜索请求超时(30s): %w", p.Name(), err)
}
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
} Defensive patterns
Strategy: retry
Validate before calling
// preflight reachability
conn, err := net.DialTimeout("tcp", host+":443", 5*time.Second)
if err != nil { log.Printf("搜索站点不可达: %v", err) } else { conn.Close() } Try / catch
results, err := plugin.Search(ctx, keyword)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
// timeout: retry with backoff or raise the 30s limit
} else if strings.Contains(err.Error(), "搜索请求失败") {
// transport failure: check network/proxy, consider fallback plugin
}
} Prevention
- Size the 30s context timeout to the site's real latency (measure it)
- Use retry-with-backoff inside doRequestWithRetry for transient errors
- Route through healthy proxies when the site blocks datacenter IPs
- Distinguish timeouts from connection errors when logging
When it happens
Trigger: p.doRequestWithRetry(req, client) returns err in searchPage — network unreachable, server down, or the 30-second context timeout elapsed before a response.
Common situations: Target site blocking the client's IP, firewall/proxy interference, slow site exceeding the 30s timeout, or transient network outages.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/e8d00c87cb0170b3.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/cldi/cldi.go:145
searchURL := fmt.Sprintf("%s/search-%s-0-2-%d.html", baseURL, url.QueryEscape(keyword), page)
// 创建带超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// 创建请求
req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil)
if err != nil {
return nil, fmt.Errorf("[%s] 创建请求失败: %w", p.Name(), err)
}
// 设置请求头
p.setRequestHeaders(req)
// 发送请求
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)
}
// 解析HTML
doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body)))
if err != nil {
return nil, fmt.Errorf("[%s] HTML解析失败: %w", p.Name(), err)View on GitHub (pinned to beaa561337)