fish2018/pansou · error
[ ] 解析搜索页面失败
Error message
[%s] 解析搜索页面失败: %w
What it means
searchImpl wraps errors from goquery.NewDocumentFromReader, which parses the search response body as an HTML document. The response reached status 200 but goquery (built on golang.org/x/net/html) could not parse the body — the content is not valid HTML, is compressed/unreadable, or the body is truncated/empty due to an interrupted transfer.
Solutions
- Capture the first bytes of the response body (Content-Type plus a snippet) when this error occurs to see what was actually returned.
- Check the Content-Type header before parsing — skip or log when it is not text/html.
- Inspect the raw body with curl to determine whether the site changed its markup or serves challenge pages on 200.
- Check for mid-stream read errors (connection reset) in the wrapped error and rely on doRequestWithRetry for such transient failures.
- Ensure the Transport isn't disabling automatic decompression (DisableCompression) while the server sends gzip.
Example fix
// before
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] 解析搜索页面失败: %w", p.Name(), err)
}
// after
if ct := resp.Header.Get("Content-Type"); !strings.Contains(ct, "text/html") {
return nil, fmt.Errorf("[%s] 非HTML响应: %s", p.Name(), ct)
}
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] 解析搜索页面失败: %w", p.Name(), err)
} Defensive patterns
Strategy: validation
Validate before calling
// Validate the response is parseable HTML before goquery
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "text/html") {
return nil, fmt.Errorf("unexpected content-type: %s", ct)
}
head, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
if !strings.Contains(string(head), "<") {
return nil, fmt.Errorf("response body is not HTML")
} Try / catch
// Go: wrap parse failure with body context for diagnosis
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("parse search page (status=%d, ct=%s): %w",
resp.StatusCode, resp.Header.Get("Content-Type"), err)
} Prevention
- Check Content-Type before parsing every scraped response.
- Log a body snippet on parse failure to spot CAPTCHA/challenge pages served with 200.
- Confirm gzip handling: leave Transport compression enabled when the server sends gzip.
- Watch for connection resets during body reads; treat them as retryable transport errors.
When it happens
Trigger: NewDocumentFromReader returns an error when the response body contains malformed HTML that the html parser rejects, or when reading the body fails mid-stream (connection reset during body read, gzip/deflate corruption, chunked encoding truncated).
Common situations: The site returns a 200 page that is actually a JSON error, a CAPTCHA/challenge page with broken markup, or binary content; a proxy or antivirus mangles the body; the server closes the connection mid-response; custom Transport settings interfere with automatic gzip handling.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/0deb7a760b6c70aa.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/alupan/alupan.go:143
if err != nil {
return nil, fmt.Errorf("[%s] 创建请求失败: %w", p.Name(), err)
}
setCommonHeaders(req, "https://www.aliupan.com/")
resp, err := p.doRequestWithRetry(req, client, searchMaxRetries)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("[%s] 搜索返回状态码: %d", p.Name(), resp.StatusCode)
}
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] 解析搜索页面失败: %w", p.Name(), err)
}
var (
results []model.SearchResult
wg sync.WaitGroup
mu sync.Mutex
sem = make(chan struct{}, maxConcurrency)
)
doc.Find("article.excerpt").Each(func(_ int, item *goquery.Selection) {
titleSel := item.Find("header h2 a")
title := strings.TrimSpace(titleSel.Text())
detailURL, ok := titleSel.Attr("href")
if !ok || title == "" || detailURL == "" {
return
}
articleID := extractArticleID(detailURL)View on GitHub (pinned to beaa561337)