fish2018/pansou · error

[ ] 网盘第 页JSON解析失败

Error message

[%s] %s网盘第%d页JSON解析失败: %w

What it means

The plugin received HTTP 200 but the response body could not be decoded into the expected APIResponse struct (json.Decode failed). This means sdso.top returned something other than the expected JSON envelope — an HTML page (challenge/block page), an error document, or a changed JSON schema.

Solutions

  1. Enable DebugLog or capture the raw body when this happens to see what was actually returned.
  2. If the body is HTML, the site is blocking the client — change IP, slow down, or update request headers.
  3. Compare the actual JSON against the APIResponse struct and update struct field names/types after site changes.
  4. Use json.NewDecoder with a size limit and check Content-Type is application/json before decoding.
  5. Retry transient truncation errors (unexpected EOF) via the existing retry mechanism.

Example fix

// before
var apiResp APIResponse
if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil { return nil, err }
// after
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") {
    return nil, fmt.Errorf("unexpected content-type %q, likely blocked", ct)
}
var apiResp APIResponse
if err := json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(&apiResp); err != nil { return nil, err }
Defensive patterns

Strategy: try-catch

Try / catch

results, err := plugin.Search(ctx, keyword)
if err != nil && strings.Contains(err.Error(), "JSON解析失败") {
    log.Printf("sdso returned non-JSON body (likely blocked or API changed): %v", err)
}

Prevention

When it happens

Trigger: json.NewDecoder(resp.Body).Decode(&apiResp) errors after a 200 response: the body is HTML (Cloudflare/anti-bot interstitial), empty, truncated by a dropped connection, or the APIResponse struct fields no longer match the site's JSON.

Common situations: Cloudflare or similar serving a 200 HTML challenge page to the scraper; the site updated its API response format; a proxy/CDN returning an HTML error page with status 200; connection reset mid-body causing unexpected EOF.

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/7b8f12655709858b. Report an issue: GitHub.

Appendix: source

Thrown at plugin/sdso/sdso.go:263

	req.Header.Set("Connection", "keep-alive")
	req.Header.Set("Referer", "https://sdso.top/")

	// 5. 发送HTTP请求(带重试机制)
	resp, err := p.doRequestWithRetry(req, client)
	if err != nil {
		return nil, fmt.Errorf("[%s] %s网盘第%d页请求失败: %w", p.Name(), fromType, pageNo, err)
	}
	defer resp.Body.Close()

	// 6. 检查状态码
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("[%s] %s网盘第%d页返回状态码: %d", p.Name(), fromType, pageNo, resp.StatusCode)
	}

	// 7. 解析响应
	var apiResp APIResponse
	if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
		return nil, fmt.Errorf("[%s] %s网盘第%d页JSON解析失败: %w", p.Name(), fromType, pageNo, err)
	}

	// 8. 检查API响应状态
	if apiResp.Code != 200 {
		return nil, fmt.Errorf("[%s] %s网盘第%d页API错误: %s", p.Name(), fromType, pageNo, apiResp.Msg)
	}

	if DebugLog {
		fmt.Printf("[%s] %s网盘第%d页获取到 %d 个原始结果\n", p.Name(), fromType, pageNo, len(apiResp.Data.List))
	}

	// 9. 转换为标准格式
	results := make([]model.SearchResult, 0, len(apiResp.Data.List))
	processedCount := 0
	skippedCount := 0
	
	for i, item := range apiResp.Data.List {
		// 解密网盘链接

View on GitHub (pinned to beaa561337)