fish2018/pansou · error
[ ] HTML解析失败
Error message
[%s] HTML解析失败: %w
What it means
goquery.NewDocumentFromReader failed parsing the search response body. goquery returns errors from reading the body stream, not from HTML syntax, so this indicates the response body could not be read (truncated connection, decompression failure, or consumed stream).
Solutions
- Retry the request; truncation is often transient.
- Remove manual Accept-Encoding headers or enable automatic decompression on the http.Transport.
- Buffer the body with io.ReadAll first so it can be logged on failure and parsed from memory.
- Check for middleware/debug code reading resp.Body earlier and replace with io.TeeReader.
Example fix
// before
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] HTML解析失败: %w", p.Name(), err)
}
// after
body, rerr := io.ReadAll(resp.Body)
if rerr != nil {
return nil, fmt.Errorf("[%s] 读取响应体失败: %w", p.Name(), rerr)
}
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(body)) Defensive patterns
Strategy: retry
Validate before calling
body, err := io.ReadAll(resp.Body)
if err != nil || len(bytes.TrimSpace(body)) == 0 {
return // empty/truncated body, retry
} Type guard
func isBodyReadError(err error) bool {
return err != nil && (errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF))
} Try / catch
results, err := plugin.Search(keyword)
if err != nil && strings.Contains(err.Error(), "HTML解析失败") {
select {
case <-time.After(5 * time.Second):
return plugin.Search(keyword) // one retry for transient body errors
}
} Prevention
- Enable automatic decompression on the transport instead of manual Accept-Encoding.
- Buffer bodies with io.ReadAll before parsing for easier diagnostics.
- Never read resp.Body before goquery (use io.TeeReader for logging).
- Retry read failures with a short backoff — they're usually transient.
When it happens
Trigger: Reading resp.Body fails after a 200 response: connection reset mid-body, gzip/br content not decoded (Accept-Encoding set manually), or body partially consumed before parsing.
Common situations: A proxy or anti-bot layer returned an encoded/compressed body the client didn't decode; flaky network truncating the page; debug logging code accidentally reading resp.Body before goquery.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/dc92bb3b72b59b1a.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/qingying/qingying.go:143
}
p.setHeaders(req, baseURL)
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
defer resp.Body.Close()
debugPrintf("📡 HTTP状态码: %d\n", resp.StatusCode)
if resp.StatusCode != 200 {
return nil, fmt.Errorf("[%s] 请求返回状态码: %d", p.Name(), resp.StatusCode)
}
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] HTML解析失败: %w", p.Name(), err)
}
var items []searchItem
doc.Find("div.module-search-item").Each(func(i int, s *goquery.Selection) {
link := s.Find(".video-info .video-info-header h3 a")
href, exists := link.Attr("href")
if !exists {
debugPrintf("⚠️ 第%d个结果没有href属性\n", i+1)
return
}
title := strings.TrimSpace(link.Text())
if title == "" {
title, _ = link.Attr("title")
title = strings.TrimSpace(title)
}
if title == "" {View on GitHub (pinned to beaa561337)