{"record":{"id":"df26f8f645110a86","repo":"fish2018/pansou","slug":"s-w-df26f8","errorCode":null,"errorMessage":"[%s] 解析搜索页面失败: %w","messagePattern":"\\[(.+?)\\] 解析搜索页面失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/duoduo/duoduo.go","lineNumber":190,"sourceCode":"\treq.Header.Set(\"Upgrade-Insecure-Requests\", \"1\")\n\treq.Header.Set(\"Cache-Control\", \"max-age=0\")\n\treq.Header.Set(\"Referer\", \"https://tv.yydsys.top/\")\n\t\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\tdefer resp.Body.Close()\n\t\n\tif resp.StatusCode != 200 {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求返回状态码: %d\", p.Name(), resp.StatusCode)\n\t}\n\t\n\t// 6. 解析搜索结果页面\n\tdoc, err := goquery.NewDocumentFromReader(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 解析搜索页面失败: %w\", p.Name(), err)\n\t}\n\t\n\t// 7. 提取搜索结果\n\tvar results []model.SearchResult\n\t\n\tdoc.Find(\".module-search-item\").Each(func(i int, s *goquery.Selection) {\n\t\tresult := p.parseSearchItem(s, keyword)\n\t\tif result.UniqueID != \"\" {\n\t\t\tresults = append(results, result)\n\t\t}\n\t})\n\t\n\t// 8. 异步获取详情页信息\n\tenhancedResults := p.enhanceWithDetails(client, results)\n\t\n\t// 9. 关键词过滤\n\treturn plugin.FilterResultsByKeyword(enhancedResults, keyword), nil\n}","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/duoduo/duoduo.go#L172-L208","documentation":"goquery.NewDocumentFromReader failed to parse the HTTP response body as HTML after a successful 200 response from the duoduo search page. goquery wraps golang.org/x/net/html, which returns an error on badly malformed markup or an empty/invalid body. The plugin wraps it with the plugin name for context.","triggerScenarios":"The response body is not valid HTML — e.g. an empty body, a JSON error payload returned with status 200, gzip/brotli-compressed bytes that were not decompressed because Content-Encoding handling was bypassed, or a truncated body from a connection drop mid-read.","commonSituations":"Site serves compressed responses but a custom transport strips automatic decompression; anti-bot systems return a 200 with a JS-challenge or empty page; server returns non-HTML content (JSON/XML) due to changed API; network interruption truncates the response.","solutions":["Log the first few hundred bytes of resp.Body (after io.ReadAll) to see what content actually arrived.","Ensure the http.Client (not a manual io.Copy) handles the response so automatic gzip decompression applies; do not set Accept-Encoding manually.","Check resp.Header Content-Type is text/html before parsing; fall back or error clearly otherwise.","Retry the request if the body is empty — transient truncation is common with flaky upstreams."],"exampleFix":"// before\ndoc, err := goquery.NewDocumentFromReader(resp.Body)\nif err != nil {\n    return nil, fmt.Errorf(\"[%s] 解析搜索页面失败: %w\", p.Name(), err)\n}\n// after\nbody, _ := io.ReadAll(resp.Body)\nif len(bytes.TrimSpace(body)) == 0 {\n    return nil, fmt.Errorf(\"[%s] 搜索页面响应为空\", p.Name())\n}\ndoc, err := goquery.NewDocumentFromReader(bytes.NewReader(body))\nif err != nil {\n    return nil, fmt.Errorf(\"[%s] 解析搜索页面失败: %w\", p.Name(), err)\n}","handlingStrategy":"validation","validationCode":"body, err := io.ReadAll(resp.Body)\nif err != nil { return err }\nif len(bytes.TrimSpace(body)) == 0 { return fmt.Errorf(\"empty body\") }\nif ct := resp.Header.Get(\"Content-Type\"); !strings.Contains(ct, \"text/html\") {\n    return fmt.Errorf(\"unexpected content-type: %s\", ct)\n}\ndoc, err := goquery.NewDocumentFromReader(bytes.NewReader(body))","typeGuard":"func looksLikeHTML(b []byte) bool {\n    s := bytes.TrimSpace(b)\n    return len(s) > 0 && (bytes.Contains(bytes.ToLower(s[:min(200, len(s))], []byte(\"<html\")) || bytes.Contains(bytes.ToLower(s[:min(200, len(s))]), []byte(\"<!doctype\"))\n}","tryCatchPattern":"doc, err := goquery.NewDocumentFromReader(resp.Body)\nif err != nil {\n    // log a body preview, then return a clear parse error\n    return nil, fmt.Errorf(\"parse search page: %w\", err)\n}","preventionTips":["Never set Accept-Encoding manually; let net/http decompress.","Check Content-Type before parsing HTML.","Snapshot the body on parse failures to debug upstream changes.","Validate selectors still exist after site redesigns."],"tags":["html","parsing","goquery","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"}