fish2018/pansou · error

[ ] 解析搜索结果失败

Error message

[%s] 解析搜索结果失败: %w

What it means

fetchSearch parses the response with goquery.NewDocumentFromReader over an io.LimitReader(resp.Body, 6<<20) and wraps failures with this message. Parsing fails when the body (capped at 6 MiB) is not valid HTML — JSON error, captcha page in another format, or corrupt/truncated stream.

Solutions

  1. Check Content-Type and log a body snippet to confirm what was actually returned
  2. Raise or remove the 6 MiB LimitReader cap if truncation is the cause
  3. Retry once on parse failure — transient network truncation often resolves
  4. Validate encoding (GBK vs UTF-8 common on Discuz forums) and convert before parsing
  5. If the site changed response format, update parsing or disable the mirror

Example fix

// before
doc, err := goquery.NewDocumentFromReader(io.LimitReader(resp.Body, 6<<20))
if err != nil {
    return nil, fmt.Errorf("[%s] 解析搜索结果失败: %w", p.Name(), err)
}
// after
raw, err := io.ReadAll(io.LimitReader(resp.Body, 12<<20))
if err != nil {
    return nil, fmt.Errorf("[%s] 读取响应失败: %w", p.Name(), err)
}
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(raw))
if err != nil {
    return nil, fmt.Errorf("[%s] 解析搜索结果失败 (len=%d): %w", p.Name(), len(raw), err)
}
Defensive patterns

Strategy: validation

Validate before calling

ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "text/html") {
    return fmt.Errorf("leso returned non-HTML: %q", ct)
}

Try / catch

doc, err := plugin.fetchSearch(client, baseURL, keyword)
if err != nil {
    if strings.Contains(err.Error(), "解析搜索结果失败") {
        // one clean retry often fixes transient truncation
        if doc, retryErr := plugin.fetchSearch(client, baseURL, keyword); retryErr == nil {
            return doc, nil
        }
    }
    return err
}

Prevention

When it happens

Trigger: goquery.NewDocumentFromReader returns an error after reading at most 6 MiB of the response body: malformed HTML, non-HTML content type, or body cut off mid-stream (by the limit reader or proxy).

Common situations: Huge response truncated at 6 MiB producing unbalanced markup goquery can't finalize; anti-bot JSON challenge; misrouted response from a transparent proxy; encoding issues producing invalid bytes.

Related errors


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

Appendix: source

Thrown at plugin/leso/leso.go:157

	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	defer cancel()
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+"/search.php?searchsubmit=yes", strings.NewReader(form.Encode()))
	if err != nil {
		return nil, fmt.Errorf("[%s] 创建搜索请求失败: %w", p.Name(), err)
	}
	setHeaders(req, baseURL+"/")
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("[%s] 搜索请求返回 HTTP %d", p.Name(), resp.StatusCode)
	}
	doc, err := goquery.NewDocumentFromReader(io.LimitReader(resp.Body, 6<<20))
	if err != nil {
		return nil, fmt.Errorf("[%s] 解析搜索结果失败: %w", p.Name(), err)
	}
	return doc, nil
}

func (p *Plugin) fetchDetail(client *http.Client, item searchItem) (model.SearchResult, bool) {
	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	defer cancel()
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, item.detailURL, nil)
	if err != nil {
		return model.SearchResult{}, false
	}
	setHeaders(req, baseURL+"/")
	resp, err := client.Do(req)
	if err != nil {
		return model.SearchResult{}, false
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {

View on GitHub (pinned to beaa561337)