fish2018/pansou · error
发送搜索请求失败
Error message
发送搜索请求失败: %w
What it means
This error wraps any failure from the HTTP client when the xiaozhang plugin's searchImpl sends its GET request to the site's search URL via doRequest. It is a transport-level failure: DNS resolution, TCP connect, TLS handshake, timeout, or redirect-policy errors are all wrapped here with %w so the root cause is preserved. The plugin throws it because without an HTTP response there is nothing to parse for search results.
Solutions
- Inspect the wrapped cause with errors.Unwrap/%v of the returned error to see whether it is DNS, timeout, or TLS.
- Verify network connectivity to the search host (curl the searchURL from the same host).
- Increase the http.Client Timeout if failures are deadline-exceeded on a slow site.
- Check the BaseURL/SearchPath constants against the current live site — the domain may have moved.
- If the site requires a proxy, configure HTTP_PROXY/HTTPS_PROXY or a custom http.Transport Proxy.
- Add retry with backoff around doRequest for transient network errors.
Example fix
// before
resp, err := p.doRequest(client, searchURL, BaseURL, true)
if err != nil {
return nil, fmt.Errorf("发送搜索请求失败: %w", err)
}
// after
resp, err := p.doRequestWithRetry(client, searchURL, BaseURL, true, 3) // retry wrapper
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, fmt.Errorf("发送搜索请求超时: %w", err)
}
return nil, fmt.Errorf("发送搜索请求失败: %w", err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Go: pre-check reachability before invoking the plugin search
u, err := url.Parse(searchURL)
if err != nil || u.Scheme == "" || u.Host == "" {
return fmt.Errorf("invalid search URL: %w", err)
}
conn, err := net.DialTimeout("tcp", net.JoinHostPort(u.Hostname(), portOf(u, "443")), 3*time.Second)
if err != nil {
return fmt.Errorf("site unreachable: %w", err)
}
conn.Close() Type guard
// Go: errors-based narrowing of the wrapped cause
func isTimeoutErr(err error) bool {
var ne net.Error
return errors.As(err, &ne) && ne.Timeout() || errors.Is(err, context.DeadlineExceeded)
} Try / catch
results, err := plugin.Search(keyword, ext)
if err != nil {
if isTimeoutErr(err) {
// transient network issue: retry once with a longer timeout
results, err = plugin.Search(keyword, ext)
}
if err != nil {
log.Printf("xiaozhang search failed: %v", err)
return nil, results // degrade gracefully, other plugins may still answer
}
} Prevention
- Set a generous but bounded http.Client Timeout and monitor for timeout spikes.
- Health-check the target site domain periodically; domains of scraped sites change often.
- Configure proxy env vars when the site is not directly reachable from your network.
- Wrap requests in a retry helper with backoff for transient net errors.
- Keep errors wrapped with %w so errors.Is/As work on the root cause.
When it happens
Trigger: Calling the xiaozhang plugin Search/SearchWithResult when doRequest's tempClient.Do(req) returns a non-nil error — e.g. the site is unreachable, the configured BaseURL no longer resolves, the client timeout expires, TLS fails, or a redirect loop hits CheckRedirect limits.
Common situations: The target site is down or blocked (firewall/GFW), no network connectivity in the container, DNS record changed or domain expired, client Timeout too short for a slow upstream, or a proxy is required but not configured in the environment.
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/e99403a0e23bb29a.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/xiaozhang/xiaozhang.go:146
log.Printf("[Xiaozhang] 响应状态: %d", resp.StatusCode)
}
return resp, nil
}
// searchImpl 实际的搜索实现
func (p *XiaozhangPlugin) searchImpl(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
searchURL := fmt.Sprintf("%s%s?keyword=%s", BaseURL, SearchPath, url.QueryEscape(keyword))
if p.debugMode {
log.Printf("[Xiaozhang] 开始搜索: %s", keyword)
log.Printf("[Xiaozhang] 搜索URL: %s", searchURL)
}
// 发送搜索请求
resp, err := p.doRequest(client, searchURL, BaseURL, true)
if err != nil {
return nil, fmt.Errorf("发送搜索请求失败: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("搜索响应状态码异常: %d", resp.StatusCode)
}
// 处理响应体(可能是gzip压缩的)
var reader io.Reader = resp.Body
// 检查Content-Encoding
contentEncoding := resp.Header.Get("Content-Encoding")
if p.debugMode {
log.Printf("[Xiaozhang] Content-Encoding: %s", contentEncoding)
log.Printf("[Xiaozhang] Content-Type: %s", resp.Header.Get("Content-Type"))
}
// 如果是gzip压缩,手动解压View on GitHub (pinned to beaa561337)