fish2018/pansou · error
[ ] 解析搜索结果失败
Error message
[%s] 解析搜索结果失败: %w
What it means
This error is returned by MikuclubPlugin.fetchCategoryPosts when the HTTP body of a 200 OK response from the mikuclub.uk post_list API cannot be decoded into postListResponse via json.NewDecoder(...).Decode. It wraps the underlying json error, so type mismatches, truncated bodies, or HTML error pages served with 200 all surface here.
Solutions
- Dump/inspect the raw response body to see whether it is HTML (protection) or malformed JSON
- Check the current API response shape at the post_list endpoint and update the postListResponse struct tags
- If the body is a bot-challenge page, add gate handling/headers like the miosou plugin does
- Retry later if the body was truncated due to a transient upstream failure
Example fix
// before
var payload postListResponse
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
return nil, fmt.Errorf("[%s] 解析搜索结果失败: %w", p.Name(), err)
}
// after
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if looksLikeHTML(body) {
return nil, fmt.Errorf("[%s] 收到防爬页面而非 JSON", p.Name())
}
var payload postListResponse
if err := json.Unmarshal(body, &payload); err != nil {
return nil, fmt.Errorf("[%s] 解析搜索结果失败: %w (body: %.200s)", p.Name(), err, body)
} Defensive patterns
Strategy: validation
Validate before calling
// Go: sniff body before decoding
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if len(bytes.TrimSpace(body)) == 0 || bytes.Contains(bytes.ToLower(body[:min(200,len(body))]), []byte("<html")) {
// protection page or empty: do not attempt JSON decode
} Type guard
func looksLikeJSON(b []byte) bool {
t := bytes.TrimSpace(b)
return len(t) > 0 && (t[0] == '{' || t[0] == '[')
} Try / catch
if err := json.Unmarshal(body, &payload); err != nil {
var jsonErr *json.SyntaxError
if errors.As(err, &jsonErr) {
log.Printf("bad JSON at offset %d: %v; body head: %.200s", jsonErr.Offset, jsonErr, body)
}
return nil, err
} Prevention
- Always cap body reads with io.LimitReader
- Log a body snippet on decode failure for diagnosis
- Re-verify struct tags after upstream site updates
- Watch for 200-with-HTML anti-bot pages
When it happens
Trigger: fetchCategoryPosts receives StatusCode 200 but the body is not valid JSON: an anti-bot HTML interstitial, a Cloudflare/challenge page, an empty body, or a JSON schema change in postListResponse that no longer matches the struct.
Common situations: The site starts serving a JavaScript challenge page with status 200; the API changes its response shape (field renames/types); the response is gzipped or truncated mid-stream; proxy interference corrupts the body.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/200f017f04d5f39b.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/mikuclub/mikuclub.go:258
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
if err != nil {
return nil, fmt.Errorf("[%s] 创建搜索请求失败: %w", p.Name(), err)
}
setCommonHeaders(req, "https://www.mikuclub.uk/")
resp, err := p.doRequestWithRetry(req, client, searchMaxRetries)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("[%s] 搜索返回状态码: %d", p.Name(), resp.StatusCode)
}
var payload postListResponse
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
return nil, fmt.Errorf("[%s] 解析搜索结果失败: %w", p.Name(), err)
}
return payload.Posts, nil
}
func (p *MikuclubPlugin) fetchDetailLinks(client *http.Client, postID int64, detailURL string) []model.Link {
if cached, ok := detailCache.Load(postID); ok {
if entry, valid := cached.(cacheEntry); valid {
if time.Now().Before(entry.expiresAt) && len(entry.links) > 0 {
return entry.links
}
detailCache.Delete(postID)
}
}
ctx, cancel := context.WithTimeout(context.Background(), detailTimeout)
defer cancel()
View on GitHub (pinned to beaa561337)