{"record":{"id":"8fedee9bad382aeb","repo":"fish2018/pansou","slug":"id-index-d","errorCode":null,"errorMessage":"无法提取帖子ID: index=%d","messagePattern":"无法提取帖子ID: index=(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"plugin/hdr4k/hdr4k.go","lineNumber":235,"sourceCode":"\t\t\titems = append(items, s)\n\t\t}\n\t})\n\t\n\t// 并发处理每个搜索结果项\n\tfor i, s := range items {\n\t\twg.Add(1)\n\t\t\n\t\tgo func(index int, s *goquery.Selection) {\n\t\t\tdefer wg.Done()\n\t\t\t\n\t\t\t// 获取信号量\n\t\t\tsemaphore <- struct{}{}\n\t\t\tdefer func() { <-semaphore }()\n\t\t\t\n\t\t\t// 提取帖子ID\n\t\t\tpostID, exists := s.Attr(\"id\")\n\t\t\tif !exists || postID == \"\" {\n\t\t\t\terrorChan <- fmt.Errorf(\"无法提取帖子ID: index=%d\", index)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t\n\t\t\t// 提取标题\n\t\t\ttitleElement := s.Find(\"h3.xs3 a\")\n\t\t\ttitle := p.cleanHTML(titleElement.Text())\n\t\t\ttitle = strings.TrimSpace(title)\n\t\t\t\n\t\t\t// 提取内容描述\n\t\t\tcontentElement := s.Find(\"p\").First()\n\t\t\tcontent := p.cleanHTML(contentElement.Text())\n\t\t\tcontent = strings.TrimSpace(content)\n\t\t\t\n\t\t\t// 提取日期时间\n\t\t\tvar datetime time.Time\n\t\t\tdateElements := s.Find(\"p span\")\n\t\t\tif dateElements.Length() > 0 {\n\t\t\t\tdateStr := strings.TrimSpace(dateElements.First().Text())","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/hdr4k/hdr4k.go#L217-L253","documentation":"During result extraction, each candidate search-result element is expected to carry its post id in the HTML `id` attribute. If the attribute is missing or empty, the plugin pushes this error to errorChan for that index instead of a result. It means the DOM selector matched an element that does not look like a real post row.","triggerScenarios":"In the per-element goroutine of doSearch: s.Attr(\"id\") returns exists=false or an empty string — i.e. the `s` selector (post-row matcher) matched a non-post element such as an ad, sticky layout element, or a markup change on the site.","commonSituations":"Site template redesign (posts no longer carry id attributes), scraper matching sponsored/promoted rows, or pages served with a degraded/mobile layout.","solutions":["Update the selector used to collect result elements so it only matches real post rows.","Treat this as a non-fatal per-result error: log it at debug level and continue with remaining results instead of failing the whole search.","Verify the site HTML structure with the actual page and adjust the id extraction (e.g. fall back to parsing the href for the thread id).","Check whether the site changed its markup (browser devtools on a real search page)."],"exampleFix":"// before\npostID, exists := s.Attr(\"id\")\nif !exists || postID == \"\" {\n    errorChan <- fmt.Errorf(\"无法提取帖子ID: index=%d\", index)\n    return\n}\n// after\npostID, exists := s.Attr(\"id\")\nif !exists || postID == \"\" {\n    if href, ok := s.Find(\"h3.xs3 a\").Attr(\"href\"); ok {\n        if _, id, found := strings.Cut(href, \"thread-\"); found {\n            postID = strings.TrimSuffix(id, \".htm\")\n        }\n    }\n}\nif postID == \"\" {\n    errorChan <- fmt.Errorf(\"无法提取帖子ID: index=%d\", index)\n    return\n}","handlingStrategy":"validation","validationCode":"postID, exists := sel.Attr(\"id\")\nif !exists || strings.TrimSpace(postID) == \"\" {\n    // skip this element or attempt href-based fallback before indexing detail page\n    return\n}","typeGuard":"func hasPostID(s *goquery.Selection) (string, bool) {\n    id, exists := s.Attr(\"id\")\n    return strings.TrimSpace(id), exists && strings.TrimSpace(id) != \"\"\n}","tryCatchPattern":"results, err := plugin.Search(keyword, ext)\nif err != nil {\n    if strings.Contains(err.Error(), \"无法提取帖子ID\") {\n        log.Printf(\"some results skipped (markup change?): %v\", err)\n        return results, nil // or fall back to another plugin\n    }\n    return nil, err\n}","preventionTips":["Add regression tests against saved snapshots of the real search HTML","Prefer extracting ids from hrefs (thread-<id>.htm) which change less often than attributes","Treat per-result extraction failures as skippable, not fatal","Monitor for sudden drops in result count, an early sign of selector rot"],"tags":["html-parsing","scraping","dom-selector","go"],"backgroundTag":"unexpected-response-shape","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"}