{"record":{"id":"5309951a8fd7a1f1","repo":"fish2018/pansou","slug":"s-w-530995","errorCode":null,"errorMessage":"[%s] 解析详情页失败: %w","messagePattern":"\\[(.+?)\\] 解析详情页失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/lou1/lou1.go","lineNumber":279,"sourceCode":"\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, detailURL, nil)\n\tif err != nil {\n\t\treturn detailResult{}, fmt.Errorf(\"[%s] 创建详情页请求失败: %w\", p.Name(), err)\n\t}\n\tsetHTMLHeaders(req, baseURL)\n\n\tresp, err := p.doRequestWithRetry(req, client, maxRequestRetries)\n\tif err != nil {\n\t\treturn detailResult{}, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn detailResult{}, fmt.Errorf(\"[%s] 详情页返回状态码: %d\", p.Name(), resp.StatusCode)\n\t}\n\n\tdoc, err := goquery.NewDocumentFromReader(resp.Body)\n\tif err != nil {\n\t\treturn detailResult{}, fmt.Errorf(\"[%s] 解析详情页失败: %w\", p.Name(), err)\n\t}\n\n\tcontent := doc.Find(\"div.message[isfirst='1']\")\n\tif content.Length() == 0 {\n\t\tcontent = doc.Find(\".message\")\n\t}\n\tif content.Length() == 0 {\n\t\tcontent = doc.Selection\n\t}\n\n\tcontent.Find(\"script, style\").Remove()\n\n\tlinks := extractLinksFromSelection(content)\n\tlinks = filterQuarkLinks(links)\n\tdescription := strings.TrimSpace(doc.Find(\"meta[name='description']\").AttrOr(\"content\", \"\"))\n\tif description == \"\" {\n\t\tdescription = truncateString(strings.TrimSpace(content.Text()), 200)\n\t}","sourceCodeStart":261,"sourceCodeEnd":297,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/lou1/lou1.go#L261-L297","documentation":"goquery.NewDocumentFromReader parses the detail response body as HTML; this error wraps any parser failure. goquery's underlying golang.org/x/net/html parser is lenient, so this rarely triggers on valid text — it fires when the body is not HTML at all (binary, gzip-encoded-but-undeclared, or a truncated stream) or the body was already consumed.","triggerScenarios":"Response body is not readable HTML: compressed content with wrong Content-Encoding, a binary error page, or an empty/corrupt stream that makes net/html parsing return an error.","commonSituations":"Server returns a challenge page or garbage bytes instead of the forum HTML; a proxy mangles content-encoding; double decompression when a custom Transport sets Accept-Encoding and also has DisableCompression=false.","solutions":["Log the wrapped %w error and the first bytes of resp.Body to see what was actually returned","Verify no custom Transport is interfering with gzip handling (let Go handle Content-Encoding automatically)","Check Content-Type of the response; skip non-HTML responses before parsing","Increase retry robustness: a transient truncated body usually succeeds on a later retry"],"exampleFix":"// before\ndoc, err := goquery.NewDocumentFromReader(resp.Body)\nif err != nil {\n    return detailResult{}, fmt.Errorf(\"[%s] 解析详情页失败: %w\", p.Name(), err)\n}\n// after\nif ct := resp.Header.Get(\"Content-Type\"); ct != \"\" && !strings.Contains(ct, \"html\") {\n    return detailResult{}, fmt.Errorf(\"[%s] 非HTML响应: %s\", p.Name(), ct)\n}\ndoc, err := goquery.NewDocumentFromReader(resp.Body)\nif err != nil {\n    return detailResult{}, fmt.Errorf(\"[%s] 解析详情页失败: %w\", p.Name(), err)\n}","handlingStrategy":"try-catch","validationCode":"ct := resp.Header.Get(\"Content-Type\")\nif ct != \"\" && !strings.Contains(ct, \"text/html\") {\n    return skip // not an HTML page, parsing will fail\n}","typeGuard":null,"tryCatchPattern":"if err != nil {\n    log.Printf(\"html parse failed: %v\", err)\n    // fall back to returning an empty result for this detail page\n    return detailResult{}, nil\n}","preventionTips":["Do not wrap the Transport's automatic gzip handling with manual decompression","Validate Content-Type before parsing","Retry once on parse failure — transient truncation is common","Keep goquery and x/net/html updated for parser robustness fixes"],"tags":["html","parsing","goquery"],"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"}