fish2018/pansou · critical

[ ] API 失败且网页搜索请求失败: (API: )

Error message

[%s] API 失败且网页搜索请求失败: %v (API: %v)

What it means

searchWeb performs the fallback HTML search only after the API already failed (apiErr); when client.Do also fails, the returned error combines both: the web-request error via %v and the original API error for context. It means both the JSON API and the HTML scraping path were unreachable.

Solutions

  1. Check connectivity/DNS to both the API and web hosts
  2. Update API/web base URL constants after any site domain migration
  3. Configure an HTTP proxy for blocked upstreams
  4. Read both %v causes: the web error and the original apiErr

Example fix

// before
resp, err := client.Do(req)
if err != nil {
    return nil, fmt.Errorf("[%s] API 失败且网页搜索请求失败: %v (API: %v)", p.Name(), err, apiErr)
}
// after
resp, err := client.Do(req)
if err != nil {
    return nil, fmt.Errorf("[%s] API 失败且网页搜索请求失败: %w (API: %v)", p.Name(), err, apiErr)
}
Defensive patterns

Strategy: fallback

Validate before calling

// reachability pre-check across both endpoints
for _, host := range []string{apiBaseHost, webBaseHost} {
    if _, err := net.LookupHost(host); err != nil {
        log.Printf("host unreachable, skipping plugin: %s", host)
    }
}

Try / catch

results, err := plugin.Search(keyword)
if err != nil && strings.Contains(err.Error(), "API 失败且网页搜索请求失败") {
    log.Printf("both feikuai paths failed: %v", err)
    results = queryAlternativePlugins(keyword) // multi-source aggregation
}

Prevention

When it happens

Trigger: client.Do(req) in searchWeb returns non-nil while searchWeb was entered because searchImpl's API request had already failed (apiErr non-nil).

Common situations: Entire site blocked/DNS-poisoned from this host, site domain rotated (feikuai.tv -> feikuai.in) leaving both URLs stale, network outage or missing proxy for geo-blocked content.

Related errors


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

Appendix: source

Thrown at plugin/feikuai/feikuai.go:185

// searchWebFallback parses the current server-rendered external search page.
// Feikuai's legacy JSON endpoint now responds with 403, while this page still
// exposes both disk links and magnets in stable HTML markup.
func (p *FeikuaiPlugin) searchWeb(client *http.Client, keyword string, apiErr error) ([]model.SearchResult, error) {
	ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
	defer cancel()
	searchURL := SearchWebURL + "?wd=" + url.QueryEscape(keyword) + "&ext=1"
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL, nil)
	if err != nil {
		return nil, fmt.Errorf("[%s] 创建网页搜索请求失败: %w", p.Name(), err)
	}
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/136.0.0.0 Safari/537.36")
	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
	req.Header.Set("Referer", "https://feikuai.in/")
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("[%s] API 失败且网页搜索请求失败: %v (API: %v)", p.Name(), err, apiErr)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("[%s] API 失败且网页搜索返回状态码 %d (API: %v)", p.Name(), resp.StatusCode, apiErr)
	}
	doc, err := goquery.NewDocumentFromReader(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("[%s] 网页搜索 HTML 解析失败: %w", p.Name(), err)
	}

	results := make([]model.SearchResult, 0, 64)
	seen := make(map[string]struct{})
	add := func(linkURL, title, content string, datetime time.Time) {
		linkURL = strings.TrimSpace(linkURL)
		if linkURL == "" {
			return
		}
		linkType := util.GetLinkType(linkURL)

View on GitHub (pinned to beaa561337)