fish2018/pansou · error
[ ] 解析搜索流失败
Error message
[%s] 解析搜索流失败: %w
What it means
This error is returned by MiosouPlugin.searchImpl when parseSearchStream fails to parse the SSE (text/event-stream) body returned with a 200 status. It wraps the underlying parse error; causes include malformed SSE frames, an empty stream, mid-stream connection reset, or an HTML/error page served with 200.
Solutions
- Capture the raw stream bytes on failure to see whether it is SSE, HTML, or empty
- Update parseSearchStream to match the current SSE event format if the upstream schema changed
- Check for proxy/CDN buffering that truncates streaming responses (disable buffering or use direct connection)
- Retry — a mid-stream reset is often transient
Example fix
// before
groups, err := parseSearchStream(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] 解析搜索流失败: %w", p.Name(), err)
}
// after
data, _ := io.ReadAll(io.TeeReader(resp.Body, &buf))
groups, err := parseSearchStream(bytes.NewReader(data))
if err != nil {
if looksLikeHTML(data) {
return nil, fmt.Errorf("[%s] 收到 HTML 而非 SSE 流", p.Name())
}
return nil, fmt.Errorf("[%s] 解析搜索流失败: %w", p.Name(), err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Go: sanity-check the stream before parsing
br := bufio.NewReader(resp.Body)
head, _ := br.Peek(5)
if !bytes.HasPrefix(head, []byte("event")) && !bytes.HasPrefix(head, []byte("data:")) {
// not an SSE stream: abort parse early
} Type guard
func isSSEStream(head []byte) bool {
return bytes.HasPrefix(head, []byte("data:")) || bytes.HasPrefix(head, []byte("event:"))
} Try / catch
groups, err := parseSearchStream(resp.Body)
if err != nil {
log.Printf("SSE parse failed: %v", err)
// retry once or surface a clear upstream-format error
return nil, fmt.Errorf("search stream parse failed: %w", err)
} Prevention
- Re-validate parseSearchStream against live SSE captures after site updates
- Disable proxy/CDN buffering for streaming endpoints
- Cap stream size and log tails on parse failure
- Distinguish empty streams from malformed frames
When it happens
Trigger: searchImpl gets StatusCode 200, calls parseSearchStream(resp.Body), and the SSE stream is truncated, empty, contains unexpected event formats, or the connection drops mid-stream so the parser errors out.
Common situations: The API changed its SSE event schema after a site update; an intermediary (proxy/CDN) buffers or cuts long-lived streams; a 200-but-HTML anti-bot page slips past isAnubisGateResponse; network instability aborts the stream halfway.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/c7d7d481a78ee587.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/miosou/miosou.go:124
if isAnubisGateResponse(resp) {
resp.Body.Close()
cancel()
p.invalidateGate()
if err := p.ensureGate(); err != nil {
return nil, err
}
continue
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
cancel()
return nil, fmt.Errorf("[%s] 搜索接口返回状态码: %d", p.Name(), resp.StatusCode)
}
groups, err := parseSearchStream(resp.Body)
resp.Body.Close()
if err != nil {
cancel()
return nil, fmt.Errorf("[%s] 解析搜索流失败: %w", p.Name(), err)
}
results := p.convertGroups(ctx, groups, keyword)
cancel()
return results, nil
}
return nil, fmt.Errorf("[%s] 人机验证会话失效", p.Name())
}
func (p *MiosouPlugin) ensureGate() error {
p.gateMu.Lock()
defer p.gateMu.Unlock()
if p.gateReady {
return nil
}
var lastErr error
for attempt := 0; attempt < 3; attempt++ {
ctx, cancel := context.WithTimeout(context.Background(), gateTimeout)View on GitHub (pinned to beaa561337)