fish2018/pansou · error

[ ] 搜索页面返回状态码

Error message

[%s] 搜索页面返回状态码: %d

What it means

searchImpl requires the search page response to have status 200. Any other status — 403/503 from Cloudflare or anti-bot protection, 404 for a moved path, 429 rate limit, 5xx outage — raises this error including the plugin name and status code. The HTTP exchange itself succeeded; the server rejected or failed the request.

Solutions

  1. Capture and log a body snippet on non-200 to tell anti-bot pages from genuine errors.
  2. Update baseURL to the current working domain for the site.
  3. Throttle search requests and add randomized delays to avoid 429/403 rate limiting.
  4. Maintain cookies/session from the initial page load so challenge pages are passed.
  5. Retry 5xx responses with backoff; treat 403 as a signal to rotate headers or use a resolver service.

Example fix

// before
if resp.StatusCode != http.StatusOK {
    return nil, fmt.Errorf("[%s] 搜索页面返回状态码: %d", p.Name(), resp.StatusCode)
}
// after: include a body hint for diagnosis
if resp.StatusCode != http.StatusOK {
    snippet, _ := io.ReadAll(io.LimitReader(resp.Body, 256))
    return nil, fmt.Errorf("[%s] 搜索页面返回状态码: %d, body=%q", p.Name(), resp.StatusCode, string(snippet))
}
Defensive patterns

Strategy: fallback

Validate before calling

resp, err := http.Get(p.baseURL + "/")
if err != nil {
    return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
    return fmt.Errorf("ting77 homepage returned %d — domain likely moved or blocked", resp.StatusCode)
}

Type guard

func isBlockedStatus(code int) bool {
    return code == 403 || code == 429 || code == 503
}

Try / catch

results, err := plugin.Search(keyword)
if err != nil && strings.Contains(err.Error(), "搜索页面返回状态码") {
    if strings.Contains(err.Error(), ": 403") || strings.Contains(err.Error(), ": 503") {
        results, err = searchViaResolver(keyword) // challenge page: alternate route
    } else {
        results, err = retryWithBackoff(2, func() ([]model.Item, error) {
            return plugin.Search(keyword)
        })
    }
}

Prevention

When it happens

Trigger: client.Do in searchImpl returns a response with StatusCode != http.StatusOK: bot-challenge pages on the ting77 domain, expired/moved search path, too-frequent searches, or server-side errors.

Common situations: Anti-bot (Cloudflare) interstitials when scraping without cookies; site moved to a new domain leaving the old one returning 404; hammering search triggers 429; transient 502/503 during site maintenance.

Related errors


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

Appendix: source

Thrown at plugin/ting77/ting77.go:86

	if keyword == "" {
		return nil, nil
	}

	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	defer cancel()
	searchURL := p.baseURL + "/search?q=" + url.QueryEscape(keyword)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL, nil)
	if err != nil {
		return nil, fmt.Errorf("[%s] 创建搜索请求失败: %w", p.Name(), err)
	}
	setRequestHeaders(req, p.baseURL+"/", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
	resp, err := client.Do(req)
	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)
	}
	doc, err := goquery.NewDocumentFromReader(io.LimitReader(resp.Body, maxResponseBytes))
	if err != nil {
		return nil, fmt.Errorf("[%s] 解析搜索页面失败: %w", p.Name(), err)
	}
	entries := parseSearchEntries(doc)
	if len(entries) == 0 {
		return nil, nil
	}

	results, resolveErr := p.resolveEntries(ctx, client, entries)
	if len(results) == 0 && resolveErr != nil {
		return nil, fmt.Errorf("[%s] 获取网盘链接失败: %w", p.Name(), resolveErr)
	}
	return plugin.FilterResultsByKeyword(results, keyword), nil
}

type searchEntry struct {

View on GitHub (pinned to beaa561337)