{"record":{"id":"0630f32d1918315d","repo":"fish2018/pansou","slug":"s-html-w-0630f3","errorCode":null,"errorMessage":"[%s] 解析搜索结果HTML失败: %w","messagePattern":"\\[(.+?)\\] 解析搜索结果HTML失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/hdmoli/hdmoli.go","lineNumber":146,"sourceCode":"\treq.Header.Set(\"Connection\", \"keep-alive\")\n\treq.Header.Set(\"Upgrade-Insecure-Requests\", \"1\")\n\treq.Header.Set(\"Cache-Control\", \"max-age=0\")\n\treq.Header.Set(\"Referer\", BaseURL+\"/\") // HDmoli需要设置referer\n\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\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求HTTP状态错误: %d\", p.Name(), resp.StatusCode)\n\t}\n\n\t// 解析HTML提取搜索结果\n\tdoc, err := goquery.NewDocumentFromReader(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 解析搜索结果HTML失败: %w\", p.Name(), err)\n\t}\n\n\treturn p.parseSearchResults(doc)\n}\n\n// doRequestWithRetry 带重试机制的HTTP请求\nfunc (p *HdmoliPlugin) doRequestWithRetry(req *http.Request, client *http.Client) (*http.Response, error) {\n\tmaxRetries := 3\n\tvar lastErr error\n\n\tfor i := 0; i < maxRetries; i++ {\n\t\tif i > 0 {\n\t\t\t// 指数退避重试\n\t\t\tbackoff := time.Duration(1<<uint(i-1)) * 200 * time.Millisecond\n\t\t\ttime.Sleep(backoff)\n\t\t}\n\n\t\t// 克隆请求避免并发问题","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/hdmoli/hdmoli.go#L128-L164","documentation":"executeSearch wraps the error from goquery.NewDocumentFromReader as 解析搜索结果HTML失败. NewDocumentFromReader only fails if the response body cannot be read from the stream (I/O error), since html.Parse is lenient with malformed markup.","triggerScenarios":"The response body stream errors while being read: connection reset mid-response, server closed the connection early, or the body was already consumed/closed before parsing.","commonSituations":"Server (or a middlebox) truncating the response; timeouts during body transfer; accidentally closing resp.Body earlier or reading it in another step (e.g. for logging) before goquery.","solutions":["Check that resp.Body is not read or closed anywhere before goquery parses it","Retry the request if the body transfer was interrupted (connection reset)","Limit-Read the body into memory first (io.ReadAll) so read errors surface clearly","Capture the server's actual content; a challenge/compressed body may need special handling"],"exampleFix":"// before\ndoc, err := goquery.NewDocumentFromReader(resp.Body)\nif err != nil {\n    return nil, fmt.Errorf(\"[%s] 解析搜索结果HTML失败: %w\", p.Name(), err)\n}\n// after\nbodyBytes, err := io.ReadAll(resp.Body)\nif err != nil {\n    return nil, fmt.Errorf(\"[%s] 读取响应体失败: %w\", p.Name(), err)\n}\ndoc, err := goquery.NewDocumentFromReader(bytes.NewReader(bodyBytes))\nif err != nil {\n    return nil, fmt.Errorf(\"[%s] 解析搜索结果HTML失败: %w\", p.Name(), err)\n}","handlingStrategy":"fallback","validationCode":"// Go: read the body into memory first so I/O errors surface clearly\nbodyBytes, err := io.ReadAll(io.LimitReader(resp.Body, maxBodySize))\nif err != nil {\n    return fmt.Errorf(\"failed to read response body: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"bodyBytes, rerr := io.ReadAll(resp.Body)\nif rerr != nil {\n    return nil, fmt.Errorf(\"body read failed (connection reset?): %w\", rerr)\n}\ndoc, err := goquery.NewDocumentFromReader(bytes.NewReader(bodyBytes))","preventionTips":["Never close or re-read resp.Body before parsing","LimitReader large bodies to bound memory use","Retry on mid-transfer connection resets"],"tags":["go","html","goquery","web-scraping"],"backgroundTag":"invalid-json-response","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"}