fish2018/pansou · warning
[ ] 解析搜索页面失败
Error message
[%s] 解析搜索页面失败: %w
What it means
searchImpl wraps an error from goquery.NewDocumentFromReader(resp.Body) — parsing the ahhhhfs search results HTML failed. goquery rarely errors since Go's HTML parser is extremely lenient, so this generally means the body stream itself failed during parse (connection died mid-read) or the reader returned non-HTML input the parser could not set up with.
Solutions
- Read the wrapped %w error: an io.EOF/unexpected-EOF means the connection died mid-parse — retry the search.
- Check resp header Content-Encoding and ensure the transport decompresses gzip before goquery sees it.
- Log a sample of the body (tee resp.Body to a buffer) to confirm it is HTML and not a challenge page.
- Add a retry around searchImpl for transient mid-stream failures.
- Upgrade github.com/PuerkitoBio/goquery to the latest version if parsing legitimately fails.
Example fix
// before
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] 解析搜索页面失败: %w", p.Name(), err)
}
// after
bodyBytes, rerr := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
if rerr != nil {
return nil, fmt.Errorf("[%s] 读取搜索页面失败: %w", p.Name(), rerr)
}
doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(bodyBytes)))
if err != nil {
return nil, fmt.Errorf("[%s] 解析搜索页面失败: %w", p.Name(), err)
} Defensive patterns
Strategy: retry
Validate before calling
// 解析前确认响应是 HTML
if ct := resp.Header.Get("Content-Type"); ct != "" && !strings.Contains(ct, "text/html") {
return fmt.Errorf("非HTML响应: %s", ct)
} Try / catch
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
// 中途读取失败: 退避后重试整次搜索
time.Sleep(time.Second)
doc, err = retrySearch()
} Prevention
- Buffer the body (io.ReadAll with a cap) before parsing so I/O errors are reported distinctly.
- Ensure gzip responses are decompressed before parsing.
- Retry transient mid-stream failures with backoff.
- Log response content samples when parse errors occur to spot WAF pages.
When it happens
Trigger: goquery.NewDocumentFromReader returns err: reading resp.Body fails during document construction (connection reset mid-stream, context deadline hit) or the response content is not parseable HTML (compressed/binary WAF challenge body).
Common situations: The connection drops while streaming a large results page; an anti-bot challenge served as non-HTML content; a gzip body that wasn't decompressed is handed straight to the parser.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/52651af91318f353.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/ahhhhfs/ahhhhfs.go:192
req.Header.Set("Upgrade-Insecure-Requests", "1")
req.Header.Set("Cache-Control", "max-age=0")
req.Header.Set("Referer", "https://www.ahhhhfs.com/")
// 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)
}
// 6. 解析搜索结果页面
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] 解析搜索页面失败: %w", p.Name(), err)
}
// 7. 提取搜索结果
var results []model.SearchResult
var wg sync.WaitGroup
var mu sync.Mutex
semaphore := make(chan struct{}, MaxConcurrency)
doc.Find("article.post-item.item-list").Each(func(i int, s *goquery.Selection) {
// 解析基本信息
titleElem := s.Find(".entry-title a")
title := strings.TrimSpace(titleElem.Text())
if title == "" {
title = strings.TrimSpace(titleElem.AttrOr("title", ""))
}
detailURL, exists := titleElem.Attr("href")
if !exists || detailURL == "" || title == "" {View on GitHub (pinned to beaa561337)