fish2018/pansou · error
解析 Seroval 响应失败
Error message
解析 Seroval 响应失败: %w
What it means
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.
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.
Defensive patterns
Strategy: try-catch
Validate before calling
if !stdjson.Valid(body) {
return fmt.Errorf("response is not JSON (first 200 bytes: %.200q)", body)
} Type guard
func isJSONResponse(body []byte) bool { return stdjson.Valid(body) } Try / catch
results, err := parseSearchResponse(body)
if err != nil {
var perr *stdjson.UnmarshalTypeError
if errors.As(err, &perr) {
log.Printf("seroval schema drift at %v", perr)
}
log.Printf("raw body: %.500q", body)
return nil, err
} Prevention
- 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).
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/1f272fe5a37ee533.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/xiaokupan/xiaokupan.go:266
return "", fmt.Errorf("读取入口脚本失败: %w", err)
}
routeIndex := strings.Index(string(assetBody), "/s/$query")
if routeIndex < 0 {
return "", fmt.Errorf("入口脚本未找到搜索路由")
}
windowStart := max(0, routeIndex-2048)
hashes := hashPattern.FindAll(assetBody[windowStart:routeIndex], -1)
if len(hashes) == 0 {
return "", fmt.Errorf("入口脚本未找到搜索接口标识")
}
return string(hashes[len(hashes)-1]), nil
}
func parseSearchResponse(body []byte) ([]model.SearchResult, error) {
var root serovalNode
if err := stdjson.Unmarshal(body, &root); err != nil {
return nil, fmt.Errorf("解析 Seroval 响应失败: %w", err)
}
decoder := newSerovalDecoder(&root)
resultNode := decoder.objectValue(&root, "result")
searchResultsNode := decoder.objectValue(resultNode, "searchResults")
mergedNode := decoder.objectValue(searchResultsNode, "merged_by_type")
mergedNode = decoder.resolve(mergedNode)
if mergedNode == nil || mergedNode.Props == nil {
return nil, fmt.Errorf("响应缺少 merged_by_type")
}
results := make([]model.SearchResult, 0)
seenURLs := make(map[string]struct{})
for index, linkType := range mergedNode.Props.Keys {
if index >= len(mergedNode.Props.Values) {
break
}
arrayNode := decoder.resolve(mergedNode.Props.Values[index])
if arrayNode == nil || arrayNode.Type != 9 {View on GitHub (pinned to beaa561337)