{"record":{"id":"1f272fe5a37ee533","repo":"fish2018/pansou","slug":"seroval-w","errorCode":null,"errorMessage":"解析 Seroval 响应失败: %w","messagePattern":"解析 Seroval 响应失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/xiaokupan/xiaokupan.go","lineNumber":266,"sourceCode":"\t\treturn \"\", fmt.Errorf(\"读取入口脚本失败: %w\", err)\n\t}\n\n\trouteIndex := strings.Index(string(assetBody), \"/s/$query\")\n\tif routeIndex < 0 {\n\t\treturn \"\", fmt.Errorf(\"入口脚本未找到搜索路由\")\n\t}\n\twindowStart := max(0, routeIndex-2048)\n\thashes := hashPattern.FindAll(assetBody[windowStart:routeIndex], -1)\n\tif len(hashes) == 0 {\n\t\treturn \"\", fmt.Errorf(\"入口脚本未找到搜索接口标识\")\n\t}\n\treturn string(hashes[len(hashes)-1]), nil\n}\n\nfunc parseSearchResponse(body []byte) ([]model.SearchResult, error) {\n\tvar root serovalNode\n\tif err := stdjson.Unmarshal(body, &root); err != nil {\n\t\treturn nil, fmt.Errorf(\"解析 Seroval 响应失败: %w\", err)\n\t}\n\tdecoder := newSerovalDecoder(&root)\n\tresultNode := decoder.objectValue(&root, \"result\")\n\tsearchResultsNode := decoder.objectValue(resultNode, \"searchResults\")\n\tmergedNode := decoder.objectValue(searchResultsNode, \"merged_by_type\")\n\tmergedNode = decoder.resolve(mergedNode)\n\tif mergedNode == nil || mergedNode.Props == nil {\n\t\treturn nil, fmt.Errorf(\"响应缺少 merged_by_type\")\n\t}\n\n\tresults := make([]model.SearchResult, 0)\n\tseenURLs := make(map[string]struct{})\n\tfor index, linkType := range mergedNode.Props.Keys {\n\t\tif index >= len(mergedNode.Props.Values) {\n\t\t\tbreak\n\t\t}\n\t\tarrayNode := decoder.resolve(mergedNode.Props.Values[index])\n\t\tif arrayNode == nil || arrayNode.Type != 9 {","sourceCodeStart":248,"sourceCodeEnd":284,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/xiaokupan/xiaokupan.go#L248-L284","documentation":"parseSearchResponse unmarshals the search API response as a Seroval (server-function serialization) node using encoding/json. If stdjson.Unmarshal fails, the raw body was not valid JSON in the expected Seroval envelope, and the underlying parse error is wrapped with this message.","triggerScenarios":"The search endpoint returned HTML (login/captcha/error page), an empty body, truncated output, or a JSON shape incompatible with the serovalNode struct, so stdjson.Unmarshal(body, &root) errors.","commonSituations":"The server function ID is stale/invalid so the API returns an error page; rate limiting or WAF returns HTML; the site switched serialization format after a framework upgrade; response truncated by a size limit.","solutions":["Log the raw response body and status code on failure to see what was actually returned (HTML error page vs JSON).","Re-run discovery (refreshServerFunctionID) to obtain a fresh server function ID — a stale ID often yields error responses.","Check for bot-blocking (captcha/HTML challenge) and add appropriate headers, cookies, or slower request pacing.","Update serovalNode struct fields if the Seroval envelope format changed upstream."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if !stdjson.Valid(body) {\n\treturn fmt.Errorf(\"response is not JSON (first 200 bytes: %.200q)\", body)\n}","typeGuard":"func isJSONResponse(body []byte) bool { return stdjson.Valid(body) }","tryCatchPattern":"results, err := parseSearchResponse(body)\nif err != nil {\n\tvar perr *stdjson.UnmarshalTypeError\n\tif errors.As(err, &perr) {\n\t\tlog.Printf(\"seroval schema drift at %v\", perr)\n\t}\n\tlog.Printf(\"raw body: %.500q\", body)\n\treturn nil, err\n}","preventionTips":["Check the HTTP status and Content-Type are application/json before unmarshalling.","Log a body snippet on parse failure to distinguish HTML error pages from schema drift.","Refresh the server function ID when parse errors start appearing (stale IDs yield error payloads)."],"tags":["json","parsing","http-response","scraping"],"backgroundTag":"json-unmarshal-failed","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"}