fish2018/pansou · error
decode response failed
Error message
decode response failed (page %d, type %s): %w
What it means
json.Unmarshal failed on a sousou response body for a specific page/diskType. The server returned 200 but the body was not valid SousouResponse JSON — usually an anti-bot interstitial, truncated body, or schema change. Reported per-shard via errChan.
Solutions
- Log the first 500 bytes of respBody (the debugLog already does) to identify HTML vs truncated JSON
- Check whether a WAF challenge page is being returned and add required cookies/headers
- Update the SousouResponse struct if the API response schema changed
- Validate the body looks like JSON (e.g. starts with '{') and return a clearer message when it does not
Example fix
// before
var apiResp SousouResponse
if err := json.Unmarshal(respBody, &apiResp); err != nil {
errChan <- fmt.Errorf("decode response failed (page %d, type %s): %w", pageNum, diskType, err)
return
}
// after
if len(respBody) > 0 && respBody[0] != '{' && respBody[0] != '[' {
errChan <- fmt.Errorf("non-JSON response (page %d, type %s), likely WAF page: %.100s", pageNum, diskType, respBody)
return
}
var apiResp SousouResponse
if err := json.Unmarshal(respBody, &apiResp); err != nil {
errChan <- fmt.Errorf("decode response failed (page %d, type %s): %w", pageNum, diskType, err)
return
} Defensive patterns
Strategy: type-guard
Validate before calling
trimmed := bytes.TrimSpace(respBody)
if len(trimmed) == 0 || (trimmed[0] != '{' && trimmed[0] != '[') {
return fmt.Errorf("non-JSON body: %.100s", trimmed)
} Type guard
func looksLikeJSON(b []byte) bool {
b = bytes.TrimSpace(b)
return len(b) > 0 && (b[0] == '{' || b[0] == '[')
} Try / catch
var apiResp SousouResponse
if err := json.Unmarshal(respBody, &apiResp); err != nil {
return fmt.Errorf("unexpected sousou response: %w", err) // inspect logged body
} Prevention
- Keep the debugLog of the first 500 bytes enabled to diagnose HTML vs JSON
- Update SousouResponse struct when the API schema changes
- Detect WAF/challenge pages early and add required cookies
- Never assume status 200 implies a JSON body
When it happens
Trigger: The API returned HTML (error page, WAF challenge, login page), an empty body, or truncated JSON — anything json.Unmarshal cannot decode into SousouResponse.
Common situations: Cloudflare/anti-bot HTML page returned with status 200; API endpoint changed its response schema; body truncated by a proxy; site moved and a redirect's HTML is captured.
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/6930bf3da3cc413a.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/sousou/sousou.go:444
errChan <- fmt.Errorf("HTTP error (page %d, type %s): %d", pageNum, diskType, resp.StatusCode)
return
}
// 读取响应体
respBody, err := io.ReadAll(resp.Body)
if err != nil {
debugLog("读取响应失败 (page %d, type %s): %v", pageNum, diskType, err)
errChan <- fmt.Errorf("read response body failed (page %d, type %s): %w", pageNum, diskType, err)
return
}
debugLog("响应内容 (page %d, type %s, 前500字符): %s", pageNum, diskType, string(respBody[:min(500, len(respBody))]))
// 解析响应
var apiResp SousouResponse
if err := json.Unmarshal(respBody, &apiResp); err != nil {
debugLog("JSON解析失败 (page %d, type %s): %v", pageNum, diskType, err)
errChan <- fmt.Errorf("decode response failed (page %d, type %s): %w", pageNum, diskType, err)
return
}
// 检查响应状态
if apiResp.Code != 200 {
debugLog("API返回错误 (page %d, type %s): code=%d, msg=%s", pageNum, diskType, apiResp.Code, apiResp.Msg)
errChan <- fmt.Errorf("API returned error (page %d, type %s): %s", pageNum, diskType, apiResp.Msg)
return
}
debugLog("成功获取第 %d 页数据 (type %s),共 %d 条结果", pageNum, diskType, len(apiResp.Data.List))
// 将结果发送到通道
resultChan <- apiResp.Data.List
}(page)
}
// 启动一个goroutine等待所有页面请求完成并关闭通道View on GitHub (pinned to beaa561337)