{"record":{"id":"e69b6806ebca20e3","repo":"fish2018/pansou","slug":"merged-by-type","errorCode":null,"errorMessage":"响应缺少 merged_by_type","messagePattern":"响应缺少 merged_by_type","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/xiaokupan/xiaokupan.go","lineNumber":274,"sourceCode":"\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 {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, itemNode := range arrayNode.Array {\n\t\t\tresourceURL := strings.TrimSpace(decoder.stringValue(decoder.objectValue(itemNode, \"url\")))\n\t\t\tif !validResourceURL(resourceURL, linkType) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := seenURLs[resourceURL]; exists {","sourceCodeStart":256,"sourceCodeEnd":292,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/xiaokupan/xiaokupan.go#L256-L292","documentation":"The Seroval response parsed successfully, but navigating node.result -> searchResults -> merged_by_type (with reference resolution) yielded a nil node or a node without Props. The plugin requires merged_by_type to enumerate search results grouped by link type, so it returns this error when the expected structure is missing.","triggerScenarios":"decoder.objectValue/resolve produce a nil merged_by_type node or one with nil Props: the API call succeeded (valid JSON) but returned no search data — zero results shape, an API-level error payload, or a schema change.","commonSituations":"The search keyword returned no results and the API omits merged_by_type; the server function ID is wrong so a different payload shape comes back; the site upgraded and renamed/renested result fields.","solutions":["Verify the server function ID is current by re-running discovery; an invalid ID commonly yields an unexpected payload shape.","Confirm with a browser DevTools capture what a real (non-empty) response looks like and compare node paths (result.searchResults.merged_by_type).","Handle the empty-results case explicitly before erroring if zero hits legitimately omit merged_by_type.","Update the decoder navigation keys if the field was renamed upstream."],"exampleFix":"// before\nif mergedNode == nil || mergedNode.Props == nil {\n\treturn nil, fmt.Errorf(\"响应缺少 merged_by_type\")\n}\n// after\nif mergedNode == nil || mergedNode.Props == nil {\n\treturn []model.SearchResult{}, nil // treat as zero results\n}","handlingStrategy":"type-guard","validationCode":"var probe map[string]stdjson.RawMessage\nif stdjson.Unmarshal(body, &probe) == nil {\n\tif _, ok := probe[\"result\"]; !ok {\n\t\treturn fmt.Errorf(\"response lacks result envelope\")\n\t}\n}","typeGuard":"func hasMergedByType(root *serovalNode, d *serovalDecoder) bool {\n\tn := d.resolve(d.objectValue(d.objectValue(d.objectValue(root, \"result\"), \"searchResults\"), \"merged_by_type\"))\n\treturn n != nil && n.Props != nil\n}","tryCatchPattern":"results, err := parseSearchResponse(body)\nif err != nil {\n\tif strings.Contains(err.Error(), \"响应缺少 merged_by_type\") {\n\t\treturn []model.SearchResult{}, nil // treat as empty result set\n\t}\n\treturn nil, err\n}","preventionTips":["Treat a missing merged_by_type with zero-result keywords as an empty set, not a hard error.","Capture a real browser response periodically to detect schema drift early.","Re-run function-ID discovery when payload shapes look wrong."],"tags":["scraping","response-shape","api","empty-results"],"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"}