fish2018/pansou · error

[ ] 内容接口返回状态码

Error message

[%s] 内容接口返回状态码: %d

What it means

fetchPosts returns this error when the Ghost Content API responds with a status code other than 200 after all retries. The plugin treats only HTTP 200 as success and reports the actual code in the message. This typically means the upstream jsnoteclub.com service rejected the request (auth on the data key, rate limiting, blocks) or is having server-side problems.

Solutions

  1. Log/capture the status code from the message and handle the specific code: 401/403 → re-fetch a fresh data-key via fetchDataKey; 429 → slow down; 5xx → retry later.
  2. Re-run the search after the 1-hour posts cache expires or restart the process to force re-scraping the homepage key.
  3. Update headers (User-Agent/Referer) in setAPIHeaders if the site's bot protection is rejecting requests.
  4. Verify the API endpoint URL is still current (Ghost Content API path may have changed); update the hard-coded base URL if the site migrated.
  5. Reduce limit=10000 or other aggressive query parameters if the server rejects oversized requests.

Example fix

// before
if resp.StatusCode != http.StatusOK {
    return nil, fmt.Errorf("[%s] 内容接口返回状态码: %d", p.Name(), resp.StatusCode)
}
// after
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
    return nil, fmt.Errorf("[%s] key invalid/blocked (status %d), refresh data-key", p.Name(), resp.StatusCode)
}
if resp.StatusCode != http.StatusOK {
    return nil, fmt.Errorf("[%s] 内容接口返回状态码: %d", p.Name(), resp.StatusCode)
}
Defensive patterns

Strategy: retry

Validate before calling

// probe the API endpoint and check the status before full usage
req, _ := http.NewRequest("GET", "https://jsnoteclub.com/ghost/api/content/posts/?limit=1", nil)
resp, err := client.Do(req)
if err == nil && resp.StatusCode != http.StatusOK {
    log.Printf("posts API unhealthy: %d", resp.StatusCode)
}

Try / catch

posts, err := p.fetchPosts(client, dataKey)
if err != nil {
    var statusErr *fmt.Errorf
    if strings.Contains(err.Error(), "内容接口返回状态码") {
        // parse the code and branch: 403/429 → backoff & refresh key; 5xx → retry later
    }
    return nil, err
}

Prevention

When it happens

Trigger: resp.StatusCode != http.StatusOK (e.g. 401/403 for a bad or expired key, 404 if the API path changed, 429 rate limit, 5xx server errors) on the GET to https://jsnoteclub.com/ghost/api/content/posts/ with the data-key query parameter.

Common situations: The site enabled Cloudflare/bot protection returning 403 challenge pages; the scraped data-key is stale or invalid; the Ghost API version or path changed; the server returns 502/503 during downtime; the request is rate-limited due to the limit=10000 query.

Related errors


AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07). Data as JSON: /api/errors/82207dff72e10bb1. Report an issue: GitHub.

Appendix: source

Thrown at plugin/jsnoteclub/jsnoteclub.go:289

	reqURL := fmt.Sprintf("https://jsnoteclub.com/ghost/api/content/posts/?%s", params.Encode())

	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
	if err != nil {
		return nil, fmt.Errorf("[%s] 创建内容请求失败: %w", p.Name(), err)
	}
	setAPIHeaders(req, "https://jsnoteclub.com/")

	resp, err := p.doRequestWithRetry(req, client, maxRequestRetries)
	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 ghostPostsResponse
	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 *JsNoteClubPlugin) fetchDetailLinks(client *http.Client, detailURL string) []model.Link {
	if cached, ok := detailCache.Load(detailURL); ok {
		if entry, valid := cached.(detailCacheEntry); valid && time.Now().Before(entry.expiresAt) {
			return entry.links
		}
		detailCache.Delete(detailURL)
	}

View on GitHub (pinned to beaa561337)