{"record":{"id":"d4d41cebe87366a4","repo":"fish2018/pansou","slug":"s-s-d4d41c","errorCode":null,"errorMessage":"[%s] 获取详情失败: %s","messagePattern":"\\[(.+?)\\] 获取详情失败: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/qiwei/qiwei.go","lineNumber":343,"sourceCode":"\t\t\t}\n\t\t\tif isVerifyPage(body) {\n\t\t\t\tlastErr = fmt.Errorf(\"[%s] 详情页验证未通过: %s\", p.Name(), candidateURL)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tinfo, err := p.parseDetail(candidateURL, body, fallbackTitle, fallbackPic)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tp.detailCache.Store(detailURL, detailCacheEntry{Info: info, CachedAt: time.Now()})\n\t\treturn info, nil\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = fmt.Errorf(\"[%s] 获取详情失败: %s\", p.Name(), detailURL)\n\t}\n\treturn detailInfo{}, lastErr\n}\n\n// solveVerification completes the site's deterministic slider challenge. The\n// challenge is session-bound, so the caller and this method must share a\n// cookie jar on the same http.Client.\nfunc (p *QiweiPlugin) solveVerification(client *http.Client, pageURL, verifyHTML string) error {\n\tscriptMatch := verificationScriptRegex.FindStringSubmatch(verifyHTML)\n\tif len(scriptMatch) < 2 {\n\t\treturn fmt.Errorf(\"未找到滑动验证脚本\")\n\t}\n\tscriptURL := normalizeURL(pageURL, scriptMatch[1])\n\tjsBody, err := p.fetchBody(client, scriptURL, pageURL, detailTimeout)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"获取验证脚本失败: %w\", err)\n\t}\n","sourceCodeStart":325,"sourceCodeEnd":361,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/qiwei/qiwei.go#L325-L361","documentation":"This is the fallback error returned by getDetailInfo when every candidate detail URL failed but no specific lastErr was recorded — i.e. the candidate list was empty (or all iterations somehow set no error), so the plugin has no underlying cause to report. It simply states that detail information could not be obtained for the given URL.","triggerScenarios":"getDetailInfo exhausts p.detailURLCandidates(detailURL) with lastErr still nil — practically, when detailURLCandidates returns an empty slice for the input detailURL (e.g. an unrecognized/malformed URL yielding no candidates), and the cache holds no valid entry.","commonSituations":"Passing a detail URL in an unexpected format or from a different domain so the candidate generator produces nothing; calling with an empty string URL; a refactor removed the default candidate while keeping the loop; forceRefresh cleared a previously cached entry and then no candidates exist.","solutions":["Log the detailURL passed in and check what p.detailURLCandidates returns for it — likely zero candidates for this input format.","Validate/normalize detailURL before calling buildResult/getDetailInfo (correct scheme, host, non-empty).","Make the loop set a default error even when candidates exist but produce none, or guard against an empty candidate list early with a clearer message.","If the URL is valid, compare against the URL shapes detailURLCandidates expects and extend it to handle this URL form."],"exampleFix":"// before\nif lastErr == nil {\n    lastErr = fmt.Errorf(\"[%s] 获取详情失败: %s\", p.Name(), detailURL)\n}\n// after\ncandidates := p.detailURLCandidates(detailURL)\nif len(candidates) == 0 {\n    return detailInfo{}, fmt.Errorf(\"[%s] 无可用候选地址: %s\", p.Name(), detailURL)\n}\nif lastErr == nil {\n    lastErr = fmt.Errorf(\"[%s] 获取详情失败: %s\", p.Name(), detailURL)\n}","handlingStrategy":"validation","validationCode":"if detailURL == \"\" || !strings.HasPrefix(detailURL, \"http\") {\n    return errors.New(\"invalid detail URL: must be a non-empty absolute http(s) URL\")\n}","typeGuard":"func isValidDetailURL(u string) bool {\n    parsed, err := neturl.Parse(u)\n    return err == nil && (parsed.Scheme == \"http\" || parsed.Scheme == \"https\") && parsed.Host != \"\"\n}","tryCatchPattern":"info, err := plugin.GetDetailInfo(client, detailURL, title, pic, false)\nif err != nil {\n    if strings.Contains(err.Error(), \"获取详情失败\") {\n        // no usable candidate; skip this item rather than failing the batch\n        logger.Warnf(\"skipping %s: %v\", detailURL, err)\n        return nil\n    }\n    return err\n}","preventionTips":["Validate/normalize detail URLs before calling buildResult.","Check that detailURLCandidates always yields at least one candidate for accepted URL shapes.","Unit-test detailURLCandidates against all URL variants you feed the plugin.","Never pass empty or relative URLs into the detail pipeline."],"tags":["url","scraping","no-candidates","detail-fetch"],"backgroundTag":"resource-not-found","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"}