fish2018/pansou · error
[ ] 搜索请求失败
Error message
[%s] 搜索请求失败: %w
What it means
fetchSearchResults wraps an error from doRequestWithRetry for the search request: every retry attempt failed at the transport level (DNS, connect, TLS, or timeout). The plugin name and wrapped cause are included.
Solutions
- Unwrap the error to identify the root cause (timeout, refused, TLS) with errors.As.
- Confirm reachability with curl using the same URL and headers; fix proxy/DNS accordingly.
- Raise the context timeout (30s) or the retry count if the site is merely slow.
- Check whether the site moved domains and update baseURL in the plugin config.
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 {
return nil, fmt.Errorf("[%s] 搜索请求失败(请检查网络或站点可达性): %w", p.Name(), err)
} Defensive patterns
Strategy: retry
Validate before calling
u, _ := url.Parse(baseURL)
conn, err := net.DialTimeout("tcp", net.JoinHostPort(u.Hostname(), "443"), 5*time.Second)
if err != nil {
log.Println("qingying site unreachable, skip")
return
}
conn.Close() Type guard
func isTimeoutErr(err error) bool {
var nerr net.Error
return errors.As(err, &nerr) && nerr.Timeout()
} Try / catch
results, err := plugin.Search(keyword)
if err != nil {
if strings.Contains(err.Error(), "搜索请求失败") && isTimeoutErr(err) {
time.Sleep(time.Minute) // backoff, then retry
return plugin.Search(keyword)
}
return nil, err
} Prevention
- Set generous timeouts for slow aggregator sites.
- Verify DNS/proxy can reach the site before scheduled searches.
- Add exponential backoff between retry rounds.
- Unwrap *url.Error to log the real network cause.
When it happens
Trigger: p.doRequestWithRetry(req, client) returns err after all retries for the search URL — site down, connection refused, TLS handshake failure, or the 30-second context deadline exceeded on each attempt.
Common situations: The aggregation site is offline or changed domain; local DNS cannot resolve the host; firewall/proxy blocks the connection; heavy search load makes each attempt exceed the 30s timeout.
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/299ff72b2c64ba6c.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/qingying/qingying.go:131
return filtered
}
func (p *QingYingPlugin) fetchSearchResults(searchURL string, client *http.Client) ([]searchItem, error) {
debugPrintf("🌐 请求搜索页面: %s\n", searchURL)
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.setHeaders(req, baseURL)
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
defer resp.Body.Close()
debugPrintf("📡 HTTP状态码: %d\n", resp.StatusCode)
if resp.StatusCode != 200 {
return nil, fmt.Errorf("[%s] 请求返回状态码: %d", p.Name(), resp.StatusCode)
}
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] HTML解析失败: %w", p.Name(), err)
}
var items []searchItem
doc.Find("div.module-search-item").Each(func(i int, s *goquery.Selection) {
link := s.Find(".video-info .video-info-header h3 a")
href, exists := link.Attr("href")View on GitHub (pinned to beaa561337)