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

  1. Unwrap the error to identify the root cause (timeout, refused, TLS) with errors.As.
  2. Confirm reachability with curl using the same URL and headers; fix proxy/DNS accordingly.
  3. Raise the context timeout (30s) or the retry count if the site is merely slow.
  4. 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

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)