{"record":{"id":"62cbb9bb55365087","repo":"fish2018/pansou","slug":"s-html-w-62cbb9","errorCode":null,"errorMessage":"[%s] HTML解析失败: %w","messagePattern":"\\[(.+?)\\] HTML解析失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/cldi/cldi.go","lineNumber":163,"sourceCode":"\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// 检查状态码\n\tif resp.StatusCode != 200 {\n\t\treturn nil, fmt.Errorf(\"[%s] 请求返回状态码: %d\", p.Name(), resp.StatusCode)\n\t}\n\n\t// 读取响应\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 读取响应失败: %w\", p.Name(), err)\n\t}\n\n\t// 解析HTML\n\tdoc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body)))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] HTML解析失败: %w\", p.Name(), err)\n\t}\n\n\t// 提取搜索结果\n\treturn p.extractSearchResults(doc), nil\n}\n\n// setRequestHeaders 设置请求头\nfunc (p *CldiPlugin) setRequestHeaders(req *http.Request) {\n\treq.Header.Set(\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36\")\n\treq.Header.Set(\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\")\n\treq.Header.Set(\"Accept-Language\", \"zh-CN,zh;q=0.9,en;q=0.8\")\n\treq.Header.Set(\"Connection\", \"keep-alive\")\n\treq.Header.Set(\"Cache-Control\", \"no-cache\")\n\treq.Header.Set(\"Pragma\", \"no-cache\")\n\treq.Header.Set(\"Referer\", baseURL+\"/\")\n}\n\n// doRequestWithRetry 带重试机制的HTTP请求","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/cldi/cldi.go#L145-L181","documentation":"CldiPlugin.searchPage wraps goquery.NewDocumentFromReader failures with the plugin name. This is uncommon because Go's HTML parser is lenient, but it fires when the reader itself fails (nil/failed reader) or the input cannot be parsed at all — for example binary/compressed content delivered without decompression.","triggerScenarios":"goquery.NewDocumentFromReader(strings.NewReader(string(body))) returns err in searchPage — most plausibly when body is gzip-compressed bytes (content-encoding not handled) or the read errored.","commonSituations":"Server returning gzip/brotli content while the client didn't send Accept-Encoding or didn't decompress, a binary CAPTCHA/challenge page, or corrupted transfer.","solutions":["Check the Content-Type and Content-Encoding headers of the response before parsing.","If content is compressed, ensure the transport decompresses (DisableCompression=false) or decompress manually with gzip.NewReader.","Log the first bytes of body to see whether it is actually HTML.","Verify the endpoint still returns an HTML search page and not an API/binary response."],"exampleFix":"// before\ndoc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body)))\nif err != nil {\n    return nil, fmt.Errorf(\"[%s] HTML解析失败: %w\", p.Name(), err)\n}\n// after\nct := resp.Header.Get(\"Content-Type\")\nif !strings.Contains(ct, \"text/html\") {\n    return nil, fmt.Errorf(\"[%s] 非HTML响应(Content-Type=%s), 前200字节: %q\", p.Name(), ct, body[:min(len(body),200)])\n}\ndoc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body)))\nif err != nil {\n    return nil, fmt.Errorf(\"[%s] HTML解析失败: %w\", p.Name(), err)\n}","handlingStrategy":"validation","validationCode":"ct := resp.Header.Get(\"Content-Type\")\nenc := resp.Header.Get(\"Content-Encoding\")\nif enc != \"\" && enc != \"identity\" {\n    return fmt.Errorf(\"未处理的压缩编码: %s\", enc)\n}\nif !strings.Contains(ct, \"text/html\") {\n    return fmt.Errorf(\"非HTML响应: %s\", ct)\n}","typeGuard":"func looksLikeHTML(body []byte) bool {\n    s := strings.TrimSpace(strings.ToLower(string(body[:min(len(body),512)])));\n    return strings.HasPrefix(s, \"<!doctype html\") || strings.HasPrefix(s, \"<html\")\n}","tryCatchPattern":"results, err := plugin.Search(ctx, keyword)\nif err != nil {\n    if strings.Contains(err.Error(), \"HTML解析失败\") {\n        log.Printf(\"CLDI返回了不可解析内容: %v\", err)\n        // skip plugin or switch to alternate source\n    }\n}","preventionTips":["Verify Content-Type/Content-Encoding before parsing the body","Let net/http decompress gzip automatically (don't set Accept-Encoding manually unless handling it)","Spot-check response bytes in tests with recorded fixtures","Update extraction selectors when the site's HTML changes"],"tags":["html","parsing","goquery","plugin"],"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"}