fish2018/pansou · error
解析HTML失败
Error message
解析HTML失败: %w
What it means
This error wraps a failure from goquery.NewDocumentFromReader, which fully reads the response body and parses it as an HTML document via goquery's HTML parser (golang.org/x/net/html). The plugin throws it when the body cannot be read (I/O error mid-stream, e.g. decompression failure or truncated connection) or the HTML is too malformed for the parser. Without a parsed document, search results cannot be extracted.
Solutions
- Inspect the wrapped error: unexpected EOF usually means a truncated response — add retry around the whole search request.
- Log a snippet of the raw body before parsing to confirm it is actually HTML.
- Verify the gzip decompression path isn't failing mid-stream (this error can surface from the gzReader).
- Check that the response Content-Type is text/html before parsing.
- Consider setting a larger client timeout so slow responses aren't cut off mid-body.
Example fix
// before
doc, err := goquery.NewDocumentFromReader(reader)
if err != nil {
return nil, fmt.Errorf("解析HTML失败: %w", err)
}
// after
bodyBytes, err := io.ReadAll(reader)
if err != nil {
return nil, fmt.Errorf("读取响应体失败: %w", err)
}
if !utf8.Valid(bodyBytes) {
bodyBytes, err = simplifiedchinese.GBK.NewDecoder().Bytes(bodyBytes)
if err != nil {
return nil, fmt.Errorf("响应编码转换失败: %w", err)
}
}
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(bodyBytes))
if err != nil {
return nil, fmt.Errorf("解析HTML失败: %w", err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Go: sanity-check the body looks like HTML before parsing
body, err := io.ReadAll(reader)
if err != nil {
return fmt.Errorf("body read failed: %w", err)
}
head := strings.TrimSpace(strings.ToLower(string(body[:min(200, len(body))])))
if !strings.Contains(head, "<html") && !strings.Contains(head, "<!doctype") {
return fmt.Errorf("response is not HTML: %q", head)
} Type guard
// Go: nil-safety on the parsed document before extraction
doc, err := goquery.NewDocumentFromReader(reader)
if err != nil || doc == nil || doc.Selection.Length() == 0 {
return nil, fmt.Errorf("no parseable document: %w", err)
} Try / catch
doc, err := goquery.NewDocumentFromReader(reader)
if err != nil {
if errors.Is(err, io.ErrUnexpectedEOF) {
// truncated response: retry the whole search once
return p.searchImpl(client, keyword, ext)
}
return nil, fmt.Errorf("解析HTML失败: %w", err)
} Prevention
- Check Content-Type is text/html before parsing.
- Read the body into memory first so read errors are distinguishable from parse errors.
- Handle charset/encoding conversion (GBK etc.) for Chinese sites before parsing.
- Retry truncated responses (unexpected EOF) which are common with scraping.
- Keep goquery and golang.org/x/net/html updated for parser robustness fixes.
When it happens
Trigger: Calling Search on the xiaozhang plugin when the (possibly gzip-decompressed) response body fails to read — unexpected EOF from a truncated response, gzip stream corrupted mid-body, connection reset while streaming — or the content is not parseable HTML (binary garbage, invalid encoding).
Common situations: The site's CDN cuts the connection mid-response on slow links; the body is actually JSON or a challenge page mislabelled as HTML; character encoding issues corrupt the stream; or a proxy mangles the response body.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/405a03f078c59b19.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/xiaozhang/xiaozhang.go:177
if p.debugMode {
log.Printf("[Xiaozhang] Content-Encoding: %s", contentEncoding)
log.Printf("[Xiaozhang] Content-Type: %s", resp.Header.Get("Content-Type"))
}
// 如果是gzip压缩,手动解压
if contentEncoding == "gzip" {
gzReader, err := gzip.NewReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("创建gzip reader失败: %w", err)
}
defer gzReader.Close()
reader = gzReader
}
// 解析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("[Xiaozhang] 找到 %d 个搜索结果", len(results))
}
// 并发获取详情页链接
results = p.enrichWithDetailLinks(client, results, keyword)
// 过滤结果
filteredResults := plugin.FilterResultsByKeyword(results, keyword)
if p.debugMode {
log.Printf("[Xiaozhang] 过滤后剩余 %d 个结果", len(filteredResults))
}View on GitHub (pinned to beaa561337)