fish2018/pansou · error
[ ] 第 页读取响应失败
Error message
[%s] 第%d页读取响应失败: %w
What it means
This error is thrown by Dy4kPlugin.searchPage when io.ReadAll fails while reading the HTTP response body after a search request. It means the connection returned status 200 but the body could not be fully read, typically because the remote server or a proxy closed/reset the connection mid-transfer. The plugin wraps the underlying io error with the plugin name and page number for diagnostics.
Solutions
- Retry the request — doRequestWithRetry already retries, but re-running the whole search after a pause usually succeeds
- Verify network connectivity/proxy settings to the 4KDY host (curl the same search URL)
- Check whether the site now blocks the client User-Agent and rotate UA via getRandomUA
- Reduce page size/scope of search pages requested to shrink the response body
Example fix
// before
htmlBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, 0, fmt.Errorf("[%s] 第%d页读取响应失败: %w", p.Name(), page, err)
}
// after
htmlBytes, err := io.ReadAll(io.LimitReader(resp.Body, maxBodySize))
if err != nil {
return nil, 0, fmt.Errorf("[%s] 第%d页读取响应失败: %w", p.Name(), page, err)
} Defensive patterns
Strategy: retry
Validate before calling
resp, err := client.Get(url)
if err != nil { return err }
if resp.StatusCode != http.StatusOK { return fmt.Errorf("bad status: %d", resp.StatusCode) }
if resp.Body == nil { return errors.New("empty response body") } Try / catch
htmlBytes, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read response failed: %w", err) // caller retries with backoff
} Prevention
- Set reasonable timeouts on the http.Client so half-open connections don't hang
- Retry transient body-read errors with backoff
- Use io.LimitReader to cap body size on untrusted servers
- Monitor network stability / avoid flaky proxies when scraping
When it happens
Trigger: Calling Search (via searchImpl -> searchPage) when the 4KDY server accepts the request but drops the TCP connection or times out mid-body during io.ReadAll(resp.Body) for a given search page.
Common situations: Unstable network or VPN connections to the target site; server-side anti-scraping that aborts large responses; proxies/CDNs terminating long responses; site under load returning truncated bodies.
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/c06d4d96546aa827.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/dy4k/dy4k.go:421
}
}
return nil, 0, fmt.Errorf("[%s] 第%d页搜索请求失败: %w", p.Name(), page, err)
}
defer resp.Body.Close()
debugPrintf("✅ [Dy4k DEBUG] HTTP请求成功 (耗时: %v)\n", requestDuration)
// 6. 检查状态码
debugPrintf("🔧 [Dy4k DEBUG] HTTP响应状态码: %d\n", resp.StatusCode)
if resp.StatusCode != 200 {
debugPrintf("❌ [Dy4k DEBUG] 状态码异常: %d\n", resp.StatusCode)
return nil, 0, fmt.Errorf("[%s] 第%d页请求返回状态码: %d", p.Name(), page, resp.StatusCode)
}
// 7. 读取并打印HTML响应
htmlBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, 0, fmt.Errorf("[%s] 第%d页读取响应失败: %w", p.Name(), page, err)
}
htmlContent := string(htmlBytes)
debugPrintf("🔧 [Dy4k DEBUG] 第%d页 HTML长度: %d bytes\n", page, len(htmlContent))
// 保存HTML到文件(仅在调试模式下)
if DebugMode {
htmlDir := "./html"
os.MkdirAll(htmlDir, 0755)
filename := fmt.Sprintf("dy4k_page_%d_%s.html", page, strings.ReplaceAll(encodedKeyword, "%", "_"))
filepath := filepath.Join(htmlDir, filename)
err = os.WriteFile(filepath, htmlBytes, 0644)
if err != nil {
debugPrintf("❌ [Dy4k DEBUG] 保存HTML文件失败: %v\n", err)
} else {
debugPrintf("✅ [Dy4k DEBUG] HTML已保存到: %s\n", filepath)View on GitHub (pinned to beaa561337)