fish2018/pansou · error
解析HTML失败
Error message
解析HTML失败: %w
What it means
The hdr4k plugin wraps any error from goquery.NewDocumentFromReader(resp.Body) — which reads the whole search-results HTML response — with this message. goquery only fails here if the underlying io.ReadAll of resp.Body fails, meaning the response body could not be read. It is a wrapper around a low-level network/IO failure that happened while draining the HTTP response.
Solutions
- Retry the search request; transient body-read failures usually succeed on a second attempt (the plugin already retries the request, but not the body read).
- Check network connectivity/proxy settings between the host and www.4khdr.cn.
- Inspect the wrapped %w error to identify the exact underlying IO failure.
- Verify the site is reachable in a browser and not blocking your IP/rate-limiting you.
Example fix
// before
resp, err := p.doRequestWithRetry(client, req, MaxRetries)
if err != nil { return nil, err }
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("解析HTML失败: %w", err)
}
// after
resp, err := p.doRequestWithRetry(client, req, MaxRetries)
if err != nil { return nil, err }
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return nil, fmt.Errorf("读取响应体失败(可重试): %w", readErr)
}
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(body)) Defensive patterns
Strategy: retry
Try / catch
results, err := plugin.Search(keyword, ext)
if err != nil {
var transient = strings.Contains(err.Error(), "解析HTML失败")
if transient {
time.Sleep(2 * time.Second)
results, err = plugin.Search(keyword, ext) // one manual retry
}
if err != nil {
log.Printf("hdr4k search failed: %v", err)
return fallbackResults
}
} Prevention
- Run searches from a host with stable connectivity to www.4khdr.cn
- Wrap body reads so transient IO failures are retried independently
- Always inspect the wrapped %w cause, not just the outer message
- Set reasonable client timeouts so connections aren't cut mid-body
When it happens
Trigger: Called in doSearch after a successful doRequestWithRetry; it triggers when reading resp.Body fails, e.g. the connection was reset or timed out mid-body, the server closed the connection early, or a proxy interrupted the transfer.
Common situations: Unstable connections to www.4khdr.cn, server-side connection drops on large search pages, VPN/proxy interference, or firewalls that cut long-lived response streams.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/49879c918b673190.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/hdr4k/hdr4k.go:161
return nil, fmt.Errorf("创建请求失败: %w", err)
}
// 设置请求头
req.Header.Set("User-Agent", getRandomUA())
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Referer", "https://www.4khdr.cn/")
// 发送请求(带重试)
resp, err := p.doRequestWithRetry(client, req, MaxRetries)
if err != nil {
return nil, fmt.Errorf("请求失败: %w", err)
}
defer resp.Body.Close()
// 解析HTML
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("解析HTML失败: %w", err)
}
// 提取搜索结果
var wg sync.WaitGroup
resultChan := make(chan model.SearchResult, 20)
errorChan := make(chan error, 20)
// 创建信号量控制并发数
semaphore := make(chan struct{}, MaxConcurrency)
// 预先收集所有需要处理的项
var items []*goquery.Selection
// 将关键词转为小写,用于不区分大小写的比较
lowerKeyword := strings.ToLower(keyword)
// 将关键词按空格分割,用于支持多关键词搜索
keywords := strings.Fields(lowerKeyword)View on GitHub (pinned to beaa561337)