fish2018/pansou · error

[ ] 搜索请求失败

Error message

[%s] 搜索请求失败: %w

What it means

fetchSearchResults wraps the error returned by doRequestWithRetry after the request creation succeeded. doRequestWithRetry already retried the request maxRetries times, so this error means the kkv search page could not be fetched at all — network failure, timeout, TLS error, or the server refused/dropped the connection on every attempt.

Solutions

  1. Verify the kkv base URL resolves and is reachable from the deployment machine (curl the search URL there)
  2. Check the wrapped error: if it is a context deadline, consider raising the 30s timeout; if connection refused, the domain is wrong or dead
  3. Use a proxy or mirror domain if the source is network-blocked at the deployment location
  4. Confirm the app has outbound internet/DNS access (containers often lack it)

Example fix

// before
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
// after
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) // slow/blocked upstream
Defensive patterns

Strategy: retry

Validate before calling

conn, err := net.DialTimeout("tcp", host+":443", 5*time.Second)
if err != nil {
    // upstream unreachable before even calling Search
}

Type guard

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(ctx, kw)
if err != nil {
    if isTimeoutErr(err) {
        ctx2, cancel := context.WithTimeout(context.Background(), 90*time.Second)
        defer cancel()
        return plugin.Search(ctx2, kw)
    }
    log.Printf("search transport failed: %v", err)
    return nil
}

Prevention

When it happens

Trigger: p.doRequestWithRetry(req, client) returns err after exhausting retries: DNS failure for the kkv domain, 30s context timeout, TLS handshake failure, or connection refused/reset each time.

Common situations: The kkv site is blocked or unreachable from the server's network (geo-block, GFW), DNS resolution fails for a dead mirror, the server has no outbound internet, or the 30-second timeout expires on a slow site.

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/842fa23fedc82a2d. Report an issue: GitHub.

Appendix: source

Thrown at plugin/kkv/kkv.go:130

	
	return filtered
}

func (p *KKVPlugin) 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("article.post").Each(func(i int, s *goquery.Selection) {
		link := s.Find(".entry-header h2.entry-title a")
		href, exists := link.Attr("href")

View on GitHub (pinned to beaa561337)