fish2018/pansou · error

[ ] 读取搜索页面失败

Error message

[%s] 读取搜索页面失败: %w

What it means

This error means io.ReadAll on the search response body failed in the djgou plugin. It wraps the I/O error (e.g. unexpected EOF, connection reset mid-body). Rare, since the response headers already arrived, but it indicates the connection dropped while streaming the HTML.

Solutions

  1. Retry the search — this is usually transient; the plugin's retry loop does not cover body reading
  2. Lower request concurrency and disable aggressive keep-alive reuse if it recurs
  3. Check proxy/load balancer idle timeouts versus page generation time
  4. Capture the wrapped error to confirm reset vs EOF

Example fix

// before
items, err := p.searchImpl(k)
return err
// after
if err != nil && strings.Contains(err.Error(), "读取搜索页面失败") {
    time.Sleep(2 * time.Second)
    items, err = p.searchImpl(k)
}
Defensive patterns

Strategy: retry

Validate before calling

// ensure network is up before calling
if _, err := net.LookupHost(hostOf(siteURL)); err != nil {
    return fmt.Errorf("dns failure: %w", err)
}

Try / catch

if err != nil && strings.Contains(err.Error(), "读取搜索页面失败") {
    time.Sleep(2*time.Second)
    items, err = p.searchImpl(keyword) // one manual retry
}

Prevention

When it happens

Trigger: io.ReadAll(resp.Body) returns err != nil in searchImpl — server closed the connection before sending the full body, or a proxy reset the stream.

Common situations: Unstable upstream/proxy connections; server-side timeouts on large pages; HTTP/2 GOAWAY mid-transfer; keep-alive connection reused after server timeout.

Related errors


AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07). Data as JSON: /api/errors/02b2f002b5c76f6e. Report an issue: GitHub.

Appendix: source

Thrown at plugin/djgou/djgou.go:152

	req.Header.Set("Cache-Control", "max-age=0")
	req.Header.Set("Referer", SiteURL)

	// 5. 发送请求(带重试机制)
	resp, err := p.doRequestWithRetry(req, client)
	if err != nil {
		return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
	}

	if resp.StatusCode != 200 {
		resp.Body.Close()
		return nil, fmt.Errorf("[%s] 搜索请求返回状态码: %d", p.Name(), resp.StatusCode)
	}

	// 6. 读取并解析搜索结果页面。部分节点先返回 BTWAF JS 跳转页。
	body, err := io.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return nil, fmt.Errorf("[%s] 读取搜索页面失败: %w", p.Name(), err)
	}
	doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body)))
	if err != nil {
		return nil, fmt.Errorf("[%s] 解析搜索页面失败: %w", p.Name(), err)
	}
	if doc.Find("article.post-item-row").Length() == 0 {
		if match := btwafURLRegex.FindStringSubmatch(string(body)); len(match) > 1 {
			challengeURL := match[1]
			if strings.HasPrefix(challengeURL, "/") {
				challengeURL = SiteURL + challengeURL
			}
			challengeReq, reqErr := http.NewRequestWithContext(ctx, http.MethodGet, challengeURL, nil)
			if reqErr == nil {
				challengeReq.Header = req.Header.Clone()
				challengeResp, doErr := p.doRequestWithRetry(challengeReq, client)
				if doErr == nil {
					challengeBody, readErr := io.ReadAll(challengeResp.Body)
					challengeResp.Body.Close()

View on GitHub (pinned to beaa561337)