fish2018/pansou · warning
document has no share link
Error message
document has no share link
What it means
After parsing the panso document page, the code looks for the first a.jump-link[href] element holding the actual share URL. If the selector matches nothing or href is empty, the page structure changed or the resource has no link, so this error is thrown rather than returning a result with an empty link.
Solutions
- Dump the HTML on failure and update the CSS selector to match the site's current markup
- Add fallback selectors (try multiple candidate selectors before giving up)
- Skip the item gracefully (return a sentinel/empty result) instead of failing the whole search
- Check whether the returned page is actually a redirect/login page and handle that case separately
Example fix
// before
linkURL := strings.TrimSpace(doc.Find("a.jump-link[href]").First().AttrOr("href", ""))
if linkURL == "" {
return model.SearchResult{}, fmt.Errorf("document has no share link")
}
// after
linkURL := strings.TrimSpace(doc.Find("a.jump-link[href]").First().AttrOr("href", ""))
if linkURL == "" {
linkURL = strings.TrimSpace(doc.Find(".down-link, a.btn-go[href]").First().AttrOr("href", ""))
}
if linkURL == "" {
return model.SearchResult{}, fmt.Errorf("document has no share link (selector may be stale)")
} Defensive patterns
Strategy: validation
Validate before calling
sel := doc.Find("a.jump-link[href]")
if sel.Length() == 0 || strings.TrimSpace(sel.First().AttrOr("href", "")) == "" {
return fmt.Errorf("page has no jump link; selector may be stale")
} Try / catch
res, err := fetchPansoDocument(...)
if err != nil {
if errors.Is(err, ErrNoShareLink) { continue /* skip item */ }
return err
} Prevention
- Add tests that parse a saved copy of the real page HTML to catch selector rot
- Keep fallback selectors for the share link
- Log raw HTML when the selector misses to ease debugging
- Treat missing links as skip-worthy, not fatal
When it happens
Trigger: goquery doc.Find("a.jump-link[href]") yields no elements, or the first match's href attribute is empty/whitespace after TrimSpace.
Common situations: The panso site redesigned its DOM and renamed/removed the jump-link class; the resource page is a stub or paywalled page without a share link; an interstitial/consent page was served instead of the resource page.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/d2ae87b9dc017c4e.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/sousou/sousou.go:265
if err != nil {
return model.SearchResult{}, err
}
setSousouWebHeaders(req, SousouWebURL+"?q=")
resp, err := client.Do(req)
if err != nil {
return model.SearchResult{}, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return model.SearchResult{}, fmt.Errorf("document returned status %d", resp.StatusCode)
}
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return model.SearchResult{}, err
}
linkURL := strings.TrimSpace(doc.Find("a.jump-link[href]").First().AttrOr("href", ""))
if linkURL == "" {
return model.SearchResult{}, fmt.Errorf("document has no share link")
}
linkType := util.GetLinkType(linkURL)
if linkType == "others" || linkType == "" {
return model.SearchResult{}, fmt.Errorf("unsupported share link: %s", linkURL)
}
title := cleanPansoTitle(doc.Find(".resource-box h1").First().Text())
if title == "" {
title = cleanPansoTitle(item.Title)
}
datetime := item.Datetime
if value := strings.TrimSpace(doc.Find(".description-label").FilterFunction(func(_ int, s *goquery.Selection) bool {
return strings.TrimSpace(s.Text()) == "分享时间"
}).Next().Text()); value != "" {
if parsed, err := time.Parse("2006-01-02", value); err == nil {
datetime = parsed
}
}
if datetime.IsZero() {View on GitHub (pinned to beaa561337)