fish2018/pansou · error
HTTP 403 Forbidden
Error message
HTTP 403 Forbidden
What it means
fetchDetail in the Gying plugin calls the site's /res/downurl detail endpoint through a cloudscraper session and throws this error when the HTTP response status is exactly 403. The plugin interprets 403 as an expired or invalid session cookie (or an unresolved anti-bot challenge), so the caller is expected to re-login and retry.
Solutions
- Re-run the plugin login flow to refresh the session cookie, then retry the search.
- Enable DebugLog and inspect the response to see whether it is a challenge page or an auth rejection.
- If it persists, check whether the server blocks your IP; route through a different egress or add delay between requests.
- Update the plugin/site config (base URL, headers) if the site changed its anti-bot scheme.
Example fix
// before
results, err := plugin.Search(ctx, query) // fails with HTTP 403 Forbidden
// after
if err != nil && strings.Contains(err.Error(), "403") {
if rerr := plugin.Relogin(ctx); rerr != nil { return rerr }
results, err = plugin.Search(ctx, query)
} Defensive patterns
Strategy: retry
Validate before calling
// re-login if session is stale before searching
if time.Since(lastLogin) > sessionTTL {
if err := plugin.Relogin(ctx); err != nil { return err }
} Type guard
func isForbidden(err error) bool { return err != nil && strings.Contains(err.Error(), "403") } Try / catch
results, err := plugin.Search(ctx, kw)
if isForbidden(err) {
if rerr := plugin.Relogin(ctx); rerr != nil { return rerr }
results, err = plugin.Search(ctx, kw)
} Prevention
- Refresh the site session cookie on a schedule shorter than its TTL.
- Keep request rates low to avoid IP-level blocking.
- Route detail fetches through the same logged-in cloudscraper session.
- Alert on repeated 403s to detect account/IP bans early.
When it happens
Trigger: Calling fetchDetail (via fetchAllDetails during Search) when the site returns HTTP 403: the stored cookie/session has expired, the Cloudflare-style challenge was not solved, or the server is blocking the client IP/User-Agent.
Common situations: Long-running deployments where the site session rotted overnight; running from a datacenter IP that the site rate-limits or blocks; cloudscraper failing to pass the anti-bot challenge; the site changing its login/challenge flow.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/c44ed628d4e43c8c.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/gying/gying.go:2718
// 使用cloudscraper发送请求(自动管理Cookie和绕过反爬虫)
body, statusCode, _, err := p.requestWithChallengeRetry(scraper, http.MethodGet, detailURL, "", "")
if err != nil {
if DebugLog {
fmt.Printf("[Gying] 请求失败: %v\n", err)
}
return nil, err
}
if DebugLog {
fmt.Printf("[Gying] 响应状态码: %d\n", statusCode)
}
// 检查403错误
if statusCode == http.StatusForbidden {
if DebugLog {
fmt.Printf("[Gying] ❌ 详情接口返回403 - Cookie可能已过期\n")
}
return nil, fmt.Errorf("HTTP 403 Forbidden")
}
if statusCode != http.StatusOK {
if DebugLog {
fmt.Printf("[Gying] ❌ HTTP错误: %d\n", statusCode)
}
return nil, fmt.Errorf("HTTP %d", statusCode)
}
if DebugLog {
fmt.Printf("[Gying] 响应长度: %d 字节\n", len(body))
}
if isLoginShell(body) {
return nil, fmt.Errorf("HTTP 403 Forbidden")
}
var detail DetailData
if err := json.Unmarshal(body, &detail); err != nil {View on GitHub (pinned to beaa561337)