fish2018/pansou · error

[ ] 解析搜索页面失败

Error message

[%s] 解析搜索页面失败: %w

What it means

searchAtBase wraps an error from goquery.NewDocumentFromReader: the labi mirror returned a 200 status but its body could not be parsed as HTML. The response was likely empty, truncated, compressed, or not HTML (e.g. a challenge or JSON error body).

Solutions

  1. Inspect the wrapped parse error and dump the first bytes of the body to see what was actually returned
  2. Do not set Accept-Encoding manually so net/http transparently decompresses before goquery reads it
  3. Treat this mirror as unhealthy and fall back to the next candidate base URL
  4. Retry once on short/truncated bodies before giving up on the mirror

Example fix

// before
req.Header.Set("Accept-Encoding", "gzip")
// after
// remove the header; let net/http handle decompression so goquery receives plain HTML
Defensive patterns

Strategy: validation

Validate before calling

resp, err := client.Get(searchURL)
if err == nil {
    defer resp.Body.Close()
    if ct := resp.Header.Get("Content-Type"); !strings.Contains(ct, "text/html") {
        // parse will fail; treat mirror as unhealthy
    }
}

Type guard

func looksLikeHTML(b []byte) bool {
    s := strings.TrimSpace(strings.ToLower(string(b[:min(len(b), 512)])))
    return strings.HasPrefix(s, "<!doctype html") || strings.HasPrefix(s, "<html")
}

Try / catch

doc, err := searchAndParse(base, kw)
if err != nil && strings.Contains(err.Error(), "解析搜索页面失败") {
    log.Printf("mirror %s returned non-HTML body: %v", base, err)
    return tryNextMirror(kw)
}

Prevention

When it happens

Trigger: goquery.NewDocumentFromReader(resp.Body) errors after a 200 response: empty/short body from a broken mirror, manually-set Accept-Encoding causing raw gzip to reach the parser, or a non-HTML body.

Common situations: A half-dead mirror returning empty 200 pages, proxy/CDN mangling or truncating responses, custom transport without auto-decompression, anti-bot challenge served with a 200 status.

Related errors


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

Appendix: source

Thrown at plugin/labi/labi.go:210

	req.Header.Set("Upgrade-Insecure-Requests", "1")
	req.Header.Set("Cache-Control", "max-age=0")
	req.Header.Set("Referer", strings.TrimRight(baseURL, "/")+"/")

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

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

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

	// 4. 提取搜索结果
	var results []model.SearchResult

	doc.Find(".module-search-item").Each(func(i int, s *goquery.Selection) {
		result := p.parseSearchItem(s, keyword)
		if result.UniqueID != "" {
			results = append(results, result)
		}
	})

	return results, nil
}

// parseSearchItem 解析单个搜索结果项
func (p *LabiAsyncPlugin) parseSearchItem(s *goquery.Selection, keyword string) model.SearchResult {
	result := model.SearchResult{}

View on GitHub (pinned to beaa561337)