{"record":{"id":"01053b8bd3a4dbbb","repo":"fish2018/pansou","slug":"s-w-01053b","errorCode":null,"errorMessage":"[%s] 解析搜索结果失败: %w","messagePattern":"\\[(.+?)\\] 解析搜索结果失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/zlxapp/zlxapp.go","lineNumber":124,"sourceCode":"\tsetRequestHeaders(req, p.baseURL)\n\n\tresp, err := doRequestWithRetry(client, req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize+1))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 读取搜索响应失败: %w\", p.Name(), err)\n\t}\n\tif len(body) > maxResponseSize {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索响应超过 %d 字节\", p.Name(), maxResponseSize)\n\t}\n\n\titems, err := parseListItems(body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 解析搜索结果失败: %w\", p.Name(), err)\n\t}\n\n\tresults := make([]model.SearchResult, 0, len(items))\n\tfor _, item := range items {\n\t\tif result, ok := convertItem(item); ok {\n\t\t\tresults = append(results, result)\n\t\t}\n\t}\n\treturn plugin.FilterResultsByKeyword(deduplicateResults(results), keyword), nil\n}\n\nfunc setRequestHeaders(req *http.Request, refererBaseURL string) {\n\treq.Header.Set(\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 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(\"Cache-Control\", \"no-cache\")\n\treq.Header.Set(\"Referer\", strings.TrimRight(refererBaseURL, \"/\")+\"/\")\n}","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/zlxapp/zlxapp.go#L106-L142","documentation":"After fetching and size-checking the body, searchImpl hands the raw HTML to parseListItems to extract result entries. This error wraps any failure from that parser — the HTML structure did not match what the parser expects, so no valid result list could be produced. It signals an upstream page-shape mismatch, not a network problem.","triggerScenarios":"parseListItems(body) returns an error because the fetched HTML lacks the expected list/result nodes: the site changed its DOM structure, returned a CAPTCHA/anti-bot interstitial, a login redirect page, or a 200-status error page instead of the search results markup.","commonSituations":"Target site redesign changes CSS selectors/element classes; anti-bot protection serving challenge pages with HTTP 200; region/CDN variants of the site with different markup; keyword triggering a 'no results' template the parser treats as malformed.","solutions":["Dump a sample of body on failure and compare the actual DOM against the selectors parseListItems expects; update the parser to the new structure.","Check whether the response is an anti-bot/CAPTCHA or login page (search for telltale markers in body) and handle that as a distinct case instead of a parse failure.","Pin or update the plugin version to one matching the current site layout; check upstream for an existing fix.","Return an empty result set instead of an error when the page parses but contains a recognized 'no results' template, so legit empty searches don't surface as failures."],"exampleFix":"// before\nitems, err := parseListItems(body)\nif err != nil {\n    return nil, fmt.Errorf(\"[%s] 解析搜索结果失败: %w\", p.Name(), err)\n}\n\n// after\nitems, err := parseListItems(body)\nif err != nil {\n    if bytes.Contains(body, []byte(\"captcha\")) || bytes.Contains(body, []byte(\"verify\")) {\n        return nil, fmt.Errorf(\"[%s] 目标站点返回了反爬验证页，无法解析搜索结果\", p.Name())\n    }\n    return nil, fmt.Errorf(\"[%s] 解析搜索结果失败（页面结构可能已变更）: %w\", p.Name(), err)\n}","handlingStrategy":"try-catch","validationCode":"// Pre-check that the body looks like a results page before parsing\nif !bytes.Contains(body, []byte(\"result\")) && !bytes.Contains(body, []byte(\"搜索结果\")) {\n    return fmt.Errorf(\"response does not look like a results page\")\n}","typeGuard":"func looksLikeResultsPage(body []byte) bool {\n    return bytes.Contains(body, []byte(\"<html\")) &&\n        !bytes.Contains(body, []byte(\"captcha\")) &&\n        !bytes.Contains(body, []byte(\"login\"))\n}","tryCatchPattern":"results, err := plugin.Search(ctx, keyword)\nif err != nil && strings.Contains(err.Error(), \"解析搜索结果失败\") {\n    // page shape changed or anti-bot page: alert/fallback, don't retry blindly\n    log.Printf(\"site layout may have changed: %v\", err)\n    return fallbackSearch(keyword)\n}","preventionTips":["Snapshot real HTML fixtures and run parseListItems against them in CI to catch site-redesign breakage","Detect CAPTCHA/login/anti-bot markers before treating parse failure as a code bug","Treat empty results as empty, not malformed, when the site has a recognizable no-results template","Log a short body excerpt on parse failure to speed up selector updates"],"tags":["go","html","parsing","scraping"],"backgroundTag":"unexpected-response-shape","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"}