fish2018/pansou · error

解析HTML失败

Error message

解析HTML失败: %w

What it means

searchImpl wraps errors from goquery.NewDocumentFromReader with this message. goquery parses the decompressed response body as HTML; this fails if the reader yields non-HTML or malformed content (JSON error page, binary data, truncated body).

Solutions

  1. Dump the first bytes of the response body (and Content-Type header) to see what was actually received
  2. Verify getResponseReader handled Content-Encoding correctly (gzip vs br vs identity)
  3. Check whether the site now returns JSON/captcha and update the plugin's parsing or headers
  4. Bypass any proxy that could truncate or modify the body
  5. Add a Content-Type check before parsing and skip non-HTML responses gracefully

Example fix

// before
doc, err := goquery.NewDocumentFromReader(reader)
if err != nil {
    return nil, fmt.Errorf("解析HTML失败: %w", err)
}
// after
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "text/html") {
    return nil, fmt.Errorf("unexpected content type %q", ct)
}
doc, err := goquery.NewDocumentFromReader(reader)
if err != nil {
    return nil, fmt.Errorf("解析HTML失败: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

doc, err := parseSearchHTML(reader)
if err != nil {
    if strings.Contains(err.Error(), "解析HTML失败") {
        // dump body preview and skip this mirror for this query
        log.Printf("leijing returned unparseable body: %v", err)
        return nil, errSkipMirror
    }
    return err
}

Prevention

When it happens

Trigger: goquery.NewDocumentFromReader(reader) returns an error because the response body (after optional gzip decoding via getResponseReader) is not parseable HTML — e.g. the site returned a JSON error, an empty/chunked-corrupt body, or mis-decoded gzip output.

Common situations: Content-Encoding handling mismatch so gzipped bytes are parsed as HTML after a failed/incomplete gunzip; WAF returns an error page in JSON; upstream serves a captcha or binary challenge; body truncated by a proxy.

Related errors


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

Appendix: source

Thrown at plugin/leijing/leijing.go:149

	if err != nil {
		return nil, fmt.Errorf("发送搜索请求失败: %w", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("搜索响应状态码异常: %d", resp.StatusCode)
	}
	
	// 处理响应体(可能是gzip压缩的)
	reader, err := p.getResponseReader(resp)
	if err != nil {
		return nil, err
	}
	
	// 解析HTML
	doc, err := goquery.NewDocumentFromReader(reader)
	if err != nil {
		return nil, fmt.Errorf("解析HTML失败: %w", err)
	}
	
	// 提取搜索结果
	results := p.extractSearchResults(doc, keyword)
	
	if p.debugMode {
		log.Printf("[Leijing] 找到 %d 个搜索结果", len(results))
	}
	
	// 对于没有直接提取到链接的结果,访问详情页获取链接
	results = p.enrichWithDetailLinks(client, results, keyword)
	
	// 过滤结果(去掉没有链接的)
	filteredResults := p.filterValidResults(results)
	
	if p.debugMode {
		log.Printf("[Leijing] 过滤后剩余 %d 个有效结果", len(filteredResults))
	}

View on GitHub (pinned to beaa561337)