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
- Dump the first bytes of the response body (and Content-Type header) to see what was actually received
- Verify getResponseReader handled Content-Encoding correctly (gzip vs br vs identity)
- Check whether the site now returns JSON/captcha and update the plugin's parsing or headers
- Bypass any proxy that could truncate or modify the body
- 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
- Check Content-Type before parsing HTML
- Ensure gzip/brotli decoding matches the Content-Encoding header
- Verify the site hasn't switched to JSON/captcha responses
- Keep goquery and encoding handling up to date
- Fall back to other plugins when one mirror returns garbage
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)