fish2018/pansou · error

[ ] 网盘第 页JSON解析失败

Error message

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

What it means

This error is thrown by fetchSearchPage in the haisou (海搜) plugin when the JSON body returned by the upstream search API cannot be unmarshalled into SearchAPIResponse. The error wraps the underlying json.Unmarshal error with the plugin name, the pan (cloud-drive) type, and the page number, so you know exactly which page of which drive failed. It indicates the upstream API returned malformed, truncated, or structurally unexpected JSON (e.g. an HTML error page instead of JSON).

Solutions

  1. Log the raw response body (fmt.Println(string(body))) before unmarshalling to see what the API actually returned
  2. Check whether the upstream API is up and serving JSON (curl the endpoint manually)
  3. Verify the SearchAPIResponse struct still matches the current API schema and update field types
  4. Add retry/backoff on this page request in case the failure is transient
  5. Use json.Decoder with better error context or tolerate unknown fields via json.RawMessage

Example fix

// before
if err := json.Unmarshal(body, &apiResp); err != nil {
	return nil, fmt.Errorf("[%s] %s网盘第%d页JSON解析失败: %w", p.Name(), panType, pageNo, err)
}
// after
if len(body) > 0 && body[0] != '{' {
	return nil, fmt.Errorf("[%s] %s网盘第%d页返回非JSON内容(前100字节: %s)", p.Name(), panType, pageNo, body[:min(100, len(body))])
}
if err := json.Unmarshal(body, &apiResp); err != nil {
	return nil, fmt.Errorf("[%s] %s网盘第%d页JSON解析失败: %w (body=%s)", p.Name(), panType, pageNo, err, body[:min(200, len(body))])
}
Defensive patterns

Strategy: try-catch

Validate before calling

if len(body) == 0 || bytes.TrimSpace(body)[0] != '{' {
	return fmt.Errorf("upstream returned non-JSON content")
}
if !json.Valid(body) {
	return fmt.Errorf("body is not valid JSON")
}

Type guard

func looksLikeJSON(b []byte) bool {
	t := bytes.TrimSpace(b)
	return len(t) > 0 && (t[0] == '{' || t[0] == '[') && json.Valid(t)
}

Try / catch

var apiResp SearchAPIResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
	log.Printf("search page parse failed, body head: %.200s", body)
	return fmt.Errorf("search JSON parse failed: %w", err)
}

Prevention

When it happens

Trigger: json.Unmarshal(body, &apiResp) fails in fetchSearchPage — the HTTP body fetched from the haisou search API is not valid JSON matching SearchAPIResponse, or violates JSON unmarshalling rules (wrong types, truncated response).

Common situations: Upstream haisou.cc API is degraded and returns an HTML block/captcha/anti-bot page; response body truncated by proxy or timeout; API contract change introducing new field types (e.g. string where number expected); rate limiting returning an error page with HTTP 200.

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

Appendix: source

Thrown at plugin/haisou/haisou.go:359

		return nil, fmt.Errorf("[%s] %s网盘第%d页请求失败: %w", p.Name(), panType, pageNo, err)
	}
	defer resp.Body.Close()

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

	// 读取响应体
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("[%s] %s网盘第%d页读取响应失败: %w", p.Name(), panType, pageNo, err)
	}

	// 解析响应
	var apiResp SearchAPIResponse
	if err := json.Unmarshal(body, &apiResp); err != nil {
		return nil, fmt.Errorf("[%s] %s网盘第%d页JSON解析失败: %w", p.Name(), panType, pageNo, err)
	}

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

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

	return apiResp.Data.List, nil
}

// fetchShareLink 通过hsid获取具体的分享链接
func (p *HaisouPlugin) fetchShareLink(client *http.Client, hsid string, platform string) (string, string, error) {
	// 构建获取链接的URL
	fetchURL := fmt.Sprintf("https://haisou.cc/api/pan/share/%s/fetch", hsid)

View on GitHub (pinned to beaa561337)