fish2018/pansou · error
[ ] 创建请求失败
Error message
[%s] 创建请求失败: %w
What it means
ClmaoPlugin.searchPage fails while constructing the outbound http.Request via http.NewRequestWithContext before any network I/O happens. This wraps a Go standard-library error such as a malformed URL (net/url.Parse error), invalid method, or a bad body passed to NewRequest. The request context with TimeoutSeconds is created just before, so context creation itself is not the cause.
Solutions
- Print/inspect the wrapped error — it names the exact URL parse problem.
- Ensure the keyword is escaped before interpolation: url.QueryEscape(keyword) or use url.Values{"q": {keyword}}.Encode().
- Validate searchURL with url.Parse and log it (sanitized) before creating the request.
- Fix the base URL constant if the site changed domains or paths.
Example fix
// before
searchURL := fmt.Sprintf("https://example.com/search?q=%s", keyword)
req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil)
// after
searchURL := fmt.Sprintf("https://example.com/search?q=%s", url.QueryEscape(keyword))
if _, perr := url.Parse(searchURL); perr != nil {
return nil, fmt.Errorf("invalid search url: %w", perr)
}
req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil) Defensive patterns
Strategy: validation
Validate before calling
func validSearchURL(base string, keyword string) (string, error) {
if strings.TrimSpace(keyword) == "" { return "", errors.New("empty keyword") }
u := fmt.Sprintf("%s?q=%s", base, url.QueryEscape(keyword))
if _, err := url.Parse(u); err != nil { return "", fmt.Errorf("invalid url: %w", err) }
return u, nil
} Try / catch
results, err := p.searchPage(client, keyword, page)
if err != nil {
if strings.Contains(err.Error(), "创建请求失败") {
// URL construction problem: fix keyword escaping/base URL, not retryable
return nil, fmt.Errorf("bad request construction: %w", err)
}
return nil, err
} Prevention
- Always url.QueryEscape user-supplied keywords before building URLs.
- Validate the final URL with url.Parse before issuing the request.
- Keep the base URL in configuration and verify it when the site migrates domains.
- Reject empty keywords early.
When it happens
Trigger: searchPage builds searchURL (derived from the keyword) and calls http.NewRequestWithContext(ctx, "GET", searchURL, nil). If searchURL is empty, contains characters invalid for a URL, or the keyword was not escaped, url.Parse fails and the error is wrapped as "[clmao] 创建请求失败: %w".
Common situations: Keyword containing spaces, CJK characters, or special characters (&, #, %) that were not url.QueryEscape'd into searchURL; a configuration change broke the base URL constant; programmatic construction of the URL from an empty/unvalidated keyword.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/c72e5c6a050dceaf.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/clmao/clmao.go:179
}
// searchPage 搜索指定页面
func (p *ClmaoPlugin) searchPage(client *http.Client, keyword string, page int) ([]model.SearchResult, error) {
// clm64 encodes the keyword as base64 and uses query-style pagination.
encodedKeyword := base64.StdEncoding.EncodeToString([]byte(keyword))
searchURL := fmt.Sprintf("%s/search?word=%s&sort=time", BaseURL, url.QueryEscape(encodedKeyword))
if page > 1 {
searchURL += fmt.Sprintf("&p=%d", page)
}
// 创建带超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), TimeoutSeconds*time.Second)
defer cancel()
// 创建请求
req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil)
if err != nil {
return nil, fmt.Errorf("[%s] 创建请求失败: %w", p.Name(), err)
}
// 设置请求头
p.setRequestHeaders(req)
// 发送HTTP请求
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
defer resp.Body.Close()
// 检查状态码
if resp.StatusCode != 200 {
return nil, fmt.Errorf("[%s] 请求返回状态码: %d", p.Name(), resp.StatusCode)
}
// 读取响应体内容View on GitHub (pinned to beaa561337)