{"record":{"id":"061cbc14b616e3b1","repo":"fish2018/pansou","slug":"s-w-061cbc","errorCode":null,"errorMessage":"[%s] 解析搜索结果失败: %w","messagePattern":"\\[(.+?)\\] 解析搜索结果失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/haitunsou/haitunsou.go","lineNumber":113,"sourceCode":"\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求返回 HTTP %d\", p.Name(), resp.StatusCode)\n\t}\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 := parseEmbeddedList(body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 解析搜索结果失败: %w\", p.Name(), err)\n\t}\n\tresults := make([]model.SearchResult, 0, len(items))\n\tseen := make(map[string]struct{}, len(items))\n\tfor _, item := range items {\n\t\tresult, ok := convertItem(item)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey := result.Links[0].URL + \"\\x00\" + result.Links[0].Password\n\t\tif _, exists := seen[key]; exists {\n\t\t\tcontinue\n\t\t}\n\t\tseen[key] = struct{}{}\n\t\tresults = append(results, result)\n\t}\n\treturn plugin.FilterResultsByKeyword(results, keyword), nil\n}\n","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/haitunsou/haitunsou.go#L95-L131","documentation":"Returned by searchImpl when parseEmbeddedList fails to extract or decode the search results embedded in the page HTML. parseEmbeddedList locates a JS snippet ('const list = JSON.parse(' or 'let listItems = JSON.parse('), scans the single-quoted string, decodes JS escapes, and unmarshals JSON; any failure in that pipeline is surfaced here wrapped with %w. It almost always means the site's page structure/JavaScript changed and the scraper's markers no longer match.","triggerScenarios":"The page HTML no longer contains either list marker, the embedded string is malformed/truncated, or the embedded JSON no longer matches the []searchItem schema (e.g. items became an object).","commonSituations":"Upstream site redesign changes variable names or moves results into an API endpoint; CDN serves a challenge page instead of search results; partial/truncated HTML delivery.","solutions":["errors.Unwrap the cause to distinguish marker-missing vs scan/decode vs JSON unmarshal failures","Fetch the page with curl and diff against the expected markers (const list = JSON.parse(' / let listItems = JSON.parse(') — update listMarkers to match the new markup","If the site moved to an API, rewrite parseEmbeddedList to call the JSON API directly","Check whether an anti-bot page was served (look for Cloudflare markers in the body) and improve headers"],"exampleFix":"// before\nvar listMarkers = [][]byte{\n    []byte(\"const list = JSON.parse('\"),\n    []byte(\"let listItems = JSON.parse('\"),\n}\n// after\nvar listMarkers = [][]byte{\n    []byte(\"const list = JSON.parse('\"),\n    []byte(\"let listItems = JSON.parse('\"),\n    []byte(\"const searchResults = JSON.parse('\"), // new marker observed on site\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"results, err := plugin.Search(kw, nil)\nif err != nil {\n    // unwrap to see which parsing stage failed\n    log.Warnf(\"haitunsou parse failed: %v\", err)\n    // degrade gracefully: continue with other plugins' results\n}","preventionTips":["Keep curl-verified fixtures of the page and run parseEmbeddedList against them in CI","Treat this as an upstream-change alarm, not a transient error","Fall back to other search plugins so users aren't blocked","Monitor unwrap chain: JSON unmarshal errors mean schema drift, marker errors mean layout drift"],"tags":["scraping","html-parsing","upstream-change"],"backgroundTag":"unexpected-api-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"}