fish2018/pansou · error
JSON解析失败
Error message
JSON解析失败: %w
What it means
fetchSearchResults unmarshals the response body into []CygPost; if the body is not valid JSON or does not match the expected array shape, this error wraps the json.Unmarshal failure. Typically the server returned HTML (error page/login page) instead of the REST JSON array.
Solutions
- Log the first ~500 bytes of the body on failure to see whether it's HTML, a JSON object, or truncated JSON.
- Handle WordPress REST error objects: try unmarshalling into a struct with code/message fields and surface that message.
- Use a browser-like User-Agent and cookies so the server returns real JSON instead of a challenge page.
- Validate the Content-Type header is application/json before unmarshalling.
Example fix
// before
var posts []CygPost
if err := json.Unmarshal(body, &posts); err != nil {
return nil, fmt.Errorf("JSON解析失败: %w", err)
}
// after
var posts []CygPost
if err := json.Unmarshal(body, &posts); err != nil {
return nil, fmt.Errorf("JSON解析失败: %w (body head: %.200s)", err, string(body))
} Defensive patterns
Strategy: type-guard
Validate before calling
resp, _ := http.Get(searchURL)
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") {
// upstream returned HTML/other: don't attempt JSON parse
} Try / catch
results, err := plugin.SearchWithResult(ctx, opts)
if err != nil && strings.Contains(err.Error(), "JSON解析失败") {
// response was not the expected JSON array: log body, try alternate source
} Prevention
- Check Content-Type before expecting JSON
- Send realistic headers so WAFs don't serve HTML challenge pages with 200
- Handle WordPress REST error objects (code/message) separately from result arrays
- Keep CygPost struct fields tolerant (json:"..." with omitempty/pointers) to survive schema drift
When it happens
Trigger: searchImpl gets a 200 whose body is not a JSON array of CygPost — e.g. an HTML block/interstitial page, a WordPress JSON error object ({"code":...}) instead of an array, or truncated/malformed JSON.
Common situations: WAF or anti-bot serves a 200 HTML challenge page; REST API returns an error object with 200 in some plugin setups; content-type/encoding mismatch; upstream changed response schema.
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/a719d330c8f75525.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/cyg/cyg.go:166
if err != nil {
return nil, fmt.Errorf("HTTP请求失败: %w", err)
}
defer resp.Body.Close()
// 检查状态码
if resp.StatusCode != 200 {
return nil, fmt.Errorf("HTTP错误状态码: %d", resp.StatusCode)
}
// 解析响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应失败: %w", err)
}
var posts []CygPost
if err := json.Unmarshal(body, &posts); err != nil {
return nil, fmt.Errorf("JSON解析失败: %w", err)
}
return posts, nil
}
// fetchDownloadLinksAsync 并发获取下载链接
func (p *CygPlugin) fetchDownloadLinksAsync(client *http.Client, posts []CygPost, keyword string) []model.SearchResult {
var wg sync.WaitGroup
resultChan := make(chan model.SearchResult, len(posts))
// 限制并发数量
semaphore := make(chan struct{}, 10) // 最多10个并发
for _, post := range posts {
wg.Add(1)
go func(p *CygPlugin, post CygPost) {
defer wg.Done()
View on GitHub (pinned to beaa561337)