{"record":{"id":"d2ae87b9dc017c4e","repo":"fish2018/pansou","slug":"document-has-no-share-link","errorCode":null,"errorMessage":"document has no share link","messagePattern":"document has no share link","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"plugin/sousou/sousou.go","lineNumber":265,"sourceCode":"\tif err != nil {\n\t\treturn model.SearchResult{}, err\n\t}\n\tsetSousouWebHeaders(req, SousouWebURL+\"?q=\")\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn model.SearchResult{}, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn model.SearchResult{}, fmt.Errorf(\"document returned status %d\", resp.StatusCode)\n\t}\n\tdoc, err := goquery.NewDocumentFromReader(resp.Body)\n\tif err != nil {\n\t\treturn model.SearchResult{}, err\n\t}\n\tlinkURL := strings.TrimSpace(doc.Find(\"a.jump-link[href]\").First().AttrOr(\"href\", \"\"))\n\tif linkURL == \"\" {\n\t\treturn model.SearchResult{}, fmt.Errorf(\"document has no share link\")\n\t}\n\tlinkType := util.GetLinkType(linkURL)\n\tif linkType == \"others\" || linkType == \"\" {\n\t\treturn model.SearchResult{}, fmt.Errorf(\"unsupported share link: %s\", linkURL)\n\t}\n\ttitle := cleanPansoTitle(doc.Find(\".resource-box h1\").First().Text())\n\tif title == \"\" {\n\t\ttitle = cleanPansoTitle(item.Title)\n\t}\n\tdatetime := item.Datetime\n\tif value := strings.TrimSpace(doc.Find(\".description-label\").FilterFunction(func(_ int, s *goquery.Selection) bool {\n\t\treturn strings.TrimSpace(s.Text()) == \"分享时间\"\n\t}).Next().Text()); value != \"\" {\n\t\tif parsed, err := time.Parse(\"2006-01-02\", value); err == nil {\n\t\t\tdatetime = parsed\n\t\t}\n\t}\n\tif datetime.IsZero() {","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/sousou/sousou.go#L247-L283","documentation":"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.","triggerScenarios":"goquery doc.Find(\"a.jump-link[href]\") yields no elements, or the first match's href attribute is empty/whitespace after TrimSpace.","commonSituations":"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.","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"],"exampleFix":"// before\nlinkURL := strings.TrimSpace(doc.Find(\"a.jump-link[href]\").First().AttrOr(\"href\", \"\"))\nif linkURL == \"\" {\n    return model.SearchResult{}, fmt.Errorf(\"document has no share link\")\n}\n// after\nlinkURL := strings.TrimSpace(doc.Find(\"a.jump-link[href]\").First().AttrOr(\"href\", \"\"))\nif linkURL == \"\" {\n    linkURL = strings.TrimSpace(doc.Find(\".down-link, a.btn-go[href]\").First().AttrOr(\"href\", \"\"))\n}\nif linkURL == \"\" {\n    return model.SearchResult{}, fmt.Errorf(\"document has no share link (selector may be stale)\")\n}","handlingStrategy":"validation","validationCode":"sel := doc.Find(\"a.jump-link[href]\")\nif sel.Length() == 0 || strings.TrimSpace(sel.First().AttrOr(\"href\", \"\")) == \"\" {\n    return fmt.Errorf(\"page has no jump link; selector may be stale\")\n}","typeGuard":null,"tryCatchPattern":"res, err := fetchPansoDocument(...)\nif err != nil {\n    if errors.Is(err, ErrNoShareLink) { continue /* skip item */ }\n    return err\n}","preventionTips":["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"],"tags":["scraping","dom","selector","empty-value"],"backgroundTag":"empty-required-field","analyzedSha":"beaa56133755a548ebc51b090b3816e2ae044aa6","analyzedAt":"2026-09-07T00:31:18.025Z","contentChangedAt":"2026-09-07T00:31:18.025Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}