fish2018/pansou · error

状态码

Error message

状态码 %d

What it means

doRequestWithRetry treats any non-200 status as a retriable failure. When a response arrives with a status other than 200, it constructs this plain error (no wrapped cause) as lastErr, closes the body, and retries; if all attempts fail, this error is returned to fetchPage and surfaces as error 817.

Solutions

  1. Log the status code and response headers/body snippet to identify 403 vs 429 vs 5xx and act accordingly.
  2. For 429, add exponential backoff and respect any Retry-After header before retrying.
  3. For 403, refresh cookies/User-Agent/headers in setRequestHeaders to look like a real browser.
  4. Reduce request concurrency and add pacing to avoid triggering blocks.
  5. Retry later for 5xx — the failure is server-side.

Example fix

// before
lastErr = fmt.Errorf("状态码 %d", resp.StatusCode)
resp.Body.Close()
// after
lastErr = fmt.Errorf("状态码 %d (%s)", resp.StatusCode, http.StatusText(resp.StatusCode))
if resp.StatusCode == http.StatusTooManyRequests {
	select {
	case <-time.After(backoff):
	case <-req.Context().Done():
		return nil, req.Context().Err()
	}
}
resp.Body.Close()
Defensive patterns

Strategy: retry

Validate before calling

req.Header.Set("User-Agent", realisticUA)
req.Header.Set("Accept", "text/html,application/xhtml+xml")
// send a HEAD/probe first and bail out early on 403/429
probe, _ := client.Head(siteURL)
if probe != nil && (probe.StatusCode == 403 || probe.StatusCode == 429) {
	return fmt.Errorf("blocked upstream, status %d", probe.StatusCode)
}

Type guard

func isTransientStatus(code int) bool { return code == 429 || code >= 500 }

Try / catch

resp, err := doRequestWithRetry(client, req)
if err != nil {
	if strings.Contains(err.Error(), "429") || strings.Contains(err.Error(), "403") {
		time.Sleep(respectBackoff)
	}
	return pageResult{}, err
}

Prevention

When it happens

Trigger: The Xiaoyu search endpoint responded with a status other than 200 (403 anti-bot block, 429 rate limit, 404 for unsupported page numbers, 5xx server errors) on every retry attempt.

Common situations: IP blocked by the site's WAF after aggressive scraping; request rate exceeded limits; missing/stale cookies or headers causing 403; requesting a page index the site does not serve; transient 5xx during site maintenance.

Related errors


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

Appendix: source

Thrown at plugin/xiaoyu/xiaoyu.go:194

}

func doRequestWithRetry(client *http.Client, req *http.Request) (*http.Response, error) {
	var lastErr error
	for attempt := 0; attempt <= maxRetries; attempt++ {
		if attempt > 0 {
			time.Sleep(time.Duration(attempt) * 200 * time.Millisecond)
		}

		resp, err := client.Do(req.Clone(req.Context()))
		if err != nil {
			lastErr = err
			continue
		}
		if resp.StatusCode == http.StatusOK {
			return resp, nil
		}

		lastErr = fmt.Errorf("状态码 %d", resp.StatusCode)
		resp.Body.Close()
	}
	return nil, lastErr
}

func parsePage(doc *goquery.Document) pageResult {
	result := pageResult{TotalPages: parseTotalPages(doc)}
	doc.Find(".search-list .item").Each(func(_ int, item *goquery.Selection) {
		parsed, ok := parseItem(item)
		if ok {
			result.Results = append(result.Results, parsed)
		}
	})
	return result
}

func parseTotalPages(doc *goquery.Document) int {
	pageText := cleanText(doc.Find(".count strong").Eq(1).Text())

View on GitHub (pinned to beaa561337)