fish2018/pansou · error
[ ] 搜索请求失败
Error message
[%s] 搜索请求失败: %w
What it means
jutoushe searchImpl wraps the error returned by doRequestWithRetry — meaning the HTTP request to the site failed after all internal retries (connection errors, TLS errors, timeouts). The wrapped lastErr explains the network-level cause.
Solutions
- Inspect the wrapped error for the root cause (dial tcp / timeout / x509)
- Test connectivity to the site from the host (curl baseURL)
- Increase the 30s context timeout if on slow networks
- Check whether the site is blocking the User-Agent/IP and adjust headers or use a proxy
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 {
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
return nil, fmt.Errorf("[%s] search timed out: %w", p.Name(), err)
}
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
} Defensive patterns
Strategy: retry
Validate before calling
conn, err := net.DialTimeout("tcp", "www.jutoushe.cc:443", 5*time.Second)
if err != nil { return fmt.Errorf("site unreachable before search: %w", err) }
conn.Close() Try / catch
results, err := plugin.Search(keyword)
if err != nil {
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
return retryLater(err) // back off and retry later
}
return err
} Prevention
- Monitor target site reachability before scheduling scrapes
- Use generous but bounded timeouts (30s is reasonable)
- Retry with exponential backoff and jitter
- Fall back to alternative sources on persistent failure
When it happens
Trigger: p.doRequestWithRetry(req, client) exhausts its retries: server unreachable, DNS failure, TLS handshake error, or context timeout (30s) expires mid-flight.
Common situations: Target site blocked or down; no internet/DNS in the deployment environment; site rate-limiting the scraper's IP; 30s context too short on slow networks.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/9bc82d38f4e88162.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/jutoushe/jutoushe.go:70
defer cancel()
// 3. 创建请求对象
req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil)
if err != nil {
return nil, fmt.Errorf("[%s] 创建请求失败: %w", p.Name(), err)
}
// 4. 设置请求头,避免反爬虫检测
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Referer", baseURL+"/")
// 5. 发送HTTP请求(带重试机制)
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
defer resp.Body.Close()
// 6. 检查状态码
if resp.StatusCode != 200 {
return nil, fmt.Errorf("[%s] 请求返回状态码: %d", p.Name(), resp.StatusCode)
}
// 7. 解析搜索结果页面
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] HTML解析失败: %w", p.Name(), err)
}
// 8. 提取搜索结果
var results []model.SearchResult
doc.Find("ul.erx-list li.item").Each(func(i int, s *goquery.Selection) {
// 提取标题和链接View on GitHub (pinned to beaa561337)