fish2018/pansou · error

[ ] 解析搜索结果失败

Error message

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

What it means

The duanjuw plugin wraps goquery.NewDocumentFromReader failures as '[plugin] failed to parse search results'. The body could not be parsed as an HTML document, usually because it is empty or not HTML despite a 200 status.

Solutions

  1. Read and log the first bytes of resp.Body on failure to see the actual content
  2. Avoid manually setting Accept-Encoding unless the Transport decompresses
  3. Retry — empty 200 bodies are often transient
  4. If the site changed its page structure/content type, update the parser

Example fix

// before
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil { return nil, err }
// after
body, rerr := io.ReadAll(resp.Body)
if rerr != nil || len(bytes.TrimSpace(body)) == 0 {
    return nil, fmt.Errorf("empty search body")
}
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(body))
Defensive patterns

Strategy: validation

Validate before calling

body, _ := io.ReadAll(resp.Body)
if len(bytes.TrimSpace(body)) == 0 {
    return fmt.Errorf("empty body; skip goquery parse")
}
if !utf8.Valid(body) {
    return fmt.Errorf("non-utf8 body")
}

Try / catch

doc, err := goquery.NewDocumentFromReader(bytes.NewReader(body))
if err != nil {
    log.Printf("parse failed, body head: %q", body[:min(200,len(body))])
    return []model.SearchResult{}, nil
}

Prevention

When it happens

Trigger: goquery.NewDocumentFromReader(resp.Body) returns err in searchImpl — empty body, truncated stream, or non-HTML content (binary, badly encoded).

Common situations: Server returns 200 with an empty or challenge body; compression mismatch when Accept-Encoding is set manually; connection cut mid-body causing malformed HTML.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at plugin/duanjuw/duanjuw.go:126

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL, nil)
	if err != nil {
		return nil, fmt.Errorf("[%s] 创建搜索请求失败: %w", p.Name(), err)
	}
	setDuanjuwHeaders(req, duanjuwBaseURL+"/")

	resp, err := doDuanjuwRequestWithRetry(req, client)
	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] 搜索返回状态码: %d", p.Name(), resp.StatusCode)
	}

	doc, err := goquery.NewDocumentFromReader(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("[%s] 解析搜索结果失败: %w", p.Name(), err)
	}

	items := p.parseSearchResults(doc)
	if len(items) == 0 {
		return []model.SearchResult{}, nil
	}
	// The current site renders search results as numbered chat entries with
	// direct pan links. Older pages still use result cards and need detail fetches.
	for _, item := range items {
		if len(item.Links) > 0 {
			return plugin.FilterResultsByKeyword(items, keyword), nil
		}
	}

	results := p.enrichResults(client, items)
	return plugin.FilterResultsByKeyword(results, keyword), nil
}

View on GitHub (pinned to beaa561337)