fish2018/pansou · error
[ ] 读取搜索结果失败
Error message
[%s] 读取搜索结果失败: %w
What it means
fetchSearch wraps an error from io.ReadAll(io.LimitReader(resp.Body, 4<<20)) — reading the response body failed mid-stream. The 4MB LimitReader caps memory use, so this indicates an actual I/O problem: the connection was reset or timed out while the body was being transferred, not an oversized page.
Solutions
- Check the wrapped %w error: 'context deadline exceeded' means requestTimeout elapsed during body read — increase requestTimeout.
- Add a retry around fetchSearch for transient connection-reset errors.
- If pages are legitimately near 4MB, raise the LimitReader cap (though most search pages are far smaller).
- Test with curl from the same host to see whether the full body downloads reliably.
Example fix
// before
body, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
if err != nil {
return nil, fmt.Errorf("[%s] 读取搜索结果失败: %w", p.Name(), err)
}
// after
body, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
if err != nil {
return nil, fmt.Errorf("[%s] 读取搜索结果失败: %w", p.Name(), err)
}
// 重新搜索时增加重试
var doc *goquery.Document
for i := 0; i < 3; i++ {
if doc, err = p.fetchSearch(client, keyword); err == nil {
break
}
time.Sleep(time.Second * time.Duration(i+1))
} Defensive patterns
Strategy: retry
Try / catch
results, err := p.Search(ctx, keyword)
if err != nil {
var netErr net.Error
if errors.As(err, &netErr) || errors.Is(err, io.ErrUnexpectedEOF) {
// 传输中断: 退避后重试
}
} Prevention
- Use a timeout longer than the site's worst-case response time.
- Retry transient I/O errors with backoff.
- Avoid scraping over unstable proxies/VPNs.
- Keep the LimitReader cap well above the page's actual size.
When it happens
Trigger: The TCP connection drops, the server closes early, or the request context's deadline expires while io.ReadAll is draining resp.Body on a large search results page.
Common situations: Unstable network or a flaky proxy interrupting large HTML transfers; the site slow-throttles responses and the context deadline (requestTimeout) cuts the read short; intermediaries (CDN/WAF) terminating long-running connections.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/f4b288c9a7462601.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/5266ys/5266ys.go:190
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+searchPath, strings.NewReader(form))
if err != nil {
return nil, fmt.Errorf("[%s] 创建搜索请求失败: %w", p.Name(), err)
}
setHeaders(req, baseURL+"/")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
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] 搜索请求返回 HTTP %d", p.Name(), resp.StatusCode)
}
body, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
if err != nil {
return nil, fmt.Errorf("[%s] 读取搜索结果失败: %w", p.Name(), err)
}
decoded, err := decodeGB18030(body)
if err != nil {
return nil, fmt.Errorf("[%s] 解码搜索结果失败: %w", p.Name(), err)
}
doc, err := goquery.NewDocumentFromReader(strings.NewReader(decoded))
if err != nil {
return nil, fmt.Errorf("[%s] 解析搜索结果失败: %w", p.Name(), err)
}
return doc, nil
}
func (p *Plugin) fetchDetail(client *http.Client, detailURL string) ([]magnetItem, string, string) {
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, detailURL, nil)
if err != nil {
return nil, "", ""View on GitHub (pinned to beaa561337)