{"record":{"id":"02b2f002b5c76f6e","repo":"fish2018/pansou","slug":"s-w-02b2f0","errorCode":null,"errorMessage":"[%s] 读取搜索页面失败: %w","messagePattern":"\\[(.+?)\\] 读取搜索页面失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/djgou/djgou.go","lineNumber":152,"sourceCode":"\treq.Header.Set(\"Cache-Control\", \"max-age=0\")\n\treq.Header.Set(\"Referer\", SiteURL)\n\n\t// 5. 发送请求（带重试机制）\n\tresp, err := p.doRequestWithRetry(req, client)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n\t}\n\n\tif resp.StatusCode != 200 {\n\t\tresp.Body.Close()\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求返回状态码: %d\", p.Name(), resp.StatusCode)\n\t}\n\n\t// 6. 读取并解析搜索结果页面。部分节点先返回 BTWAF JS 跳转页。\n\tbody, err := io.ReadAll(resp.Body)\n\tresp.Body.Close()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 读取搜索页面失败: %w\", p.Name(), err)\n\t}\n\tdoc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body)))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 解析搜索页面失败: %w\", p.Name(), err)\n\t}\n\tif doc.Find(\"article.post-item-row\").Length() == 0 {\n\t\tif match := btwafURLRegex.FindStringSubmatch(string(body)); len(match) > 1 {\n\t\t\tchallengeURL := match[1]\n\t\t\tif strings.HasPrefix(challengeURL, \"/\") {\n\t\t\t\tchallengeURL = SiteURL + challengeURL\n\t\t\t}\n\t\t\tchallengeReq, reqErr := http.NewRequestWithContext(ctx, http.MethodGet, challengeURL, nil)\n\t\t\tif reqErr == nil {\n\t\t\t\tchallengeReq.Header = req.Header.Clone()\n\t\t\t\tchallengeResp, doErr := p.doRequestWithRetry(challengeReq, client)\n\t\t\t\tif doErr == nil {\n\t\t\t\t\tchallengeBody, readErr := io.ReadAll(challengeResp.Body)\n\t\t\t\t\tchallengeResp.Body.Close()","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/djgou/djgou.go#L134-L170","documentation":"This error means io.ReadAll on the search response body failed in the djgou plugin. It wraps the I/O error (e.g. unexpected EOF, connection reset mid-body). Rare, since the response headers already arrived, but it indicates the connection dropped while streaming the HTML.","triggerScenarios":"io.ReadAll(resp.Body) returns err != nil in searchImpl — server closed the connection before sending the full body, or a proxy reset the stream.","commonSituations":"Unstable upstream/proxy connections; server-side timeouts on large pages; HTTP/2 GOAWAY mid-transfer; keep-alive connection reused after server timeout.","solutions":["Retry the search — this is usually transient; the plugin's retry loop does not cover body reading","Lower request concurrency and disable aggressive keep-alive reuse if it recurs","Check proxy/load balancer idle timeouts versus page generation time","Capture the wrapped error to confirm reset vs EOF"],"exampleFix":"// before\nitems, err := p.searchImpl(k)\nreturn err\n// after\nif err != nil && strings.Contains(err.Error(), \"读取搜索页面失败\") {\n    time.Sleep(2 * time.Second)\n    items, err = p.searchImpl(k)\n}","handlingStrategy":"retry","validationCode":"// ensure network is up before calling\nif _, err := net.LookupHost(hostOf(siteURL)); err != nil {\n    return fmt.Errorf(\"dns failure: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"读取搜索页面失败\") {\n    time.Sleep(2*time.Second)\n    items, err = p.searchImpl(keyword) // one manual retry\n}","preventionTips":["Retry body-read failures; they are usually transient","Avoid reusing keep-alive connections across long idle periods","Keep proxy idle timeouts above page load time","Lower concurrency against the site"],"tags":["network","io","http"],"backgroundTag":"network-request-failed","analyzedSha":"beaa56133755a548ebc51b090b3816e2ae044aa6","analyzedAt":"2026-09-07T00:31:18.025Z","contentChangedAt":"2026-09-07T00:31:18.025Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}