fish2018/pansou · warning
无法提取帖子ID: index=
Error message
无法提取帖子ID: index=%d
What it means
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.
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).
Example fix
// before
postID, exists := s.Attr("id")
if !exists || postID == "" {
errorChan <- fmt.Errorf("无法提取帖子ID: index=%d", index)
return
}
// after
postID, exists := s.Attr("id")
if !exists || postID == "" {
if href, ok := s.Find("h3.xs3 a").Attr("href"); ok {
if _, id, found := strings.Cut(href, "thread-"); found {
postID = strings.TrimSuffix(id, ".htm")
}
}
}
if postID == "" {
errorChan <- fmt.Errorf("无法提取帖子ID: index=%d", index)
return
} Defensive patterns
Strategy: validation
Validate before calling
postID, exists := sel.Attr("id")
if !exists || strings.TrimSpace(postID) == "" {
// skip this element or attempt href-based fallback before indexing detail page
return
} Type guard
func hasPostID(s *goquery.Selection) (string, bool) {
id, exists := s.Attr("id")
return strings.TrimSpace(id), exists && strings.TrimSpace(id) != ""
} Try / catch
results, err := plugin.Search(keyword, ext)
if err != nil {
if strings.Contains(err.Error(), "无法提取帖子ID") {
log.Printf("some results skipped (markup change?): %v", err)
return results, nil // or fall back to another plugin
}
return nil, err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Site template redesign (posts no longer carry id attributes), scraper matching sponsored/promoted rows, or pages served with a degraded/mobile layout.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/8fedee9bad382aeb.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/hdr4k/hdr4k.go:235
items = append(items, s)
}
})
// 并发处理每个搜索结果项
for i, s := range items {
wg.Add(1)
go func(index int, s *goquery.Selection) {
defer wg.Done()
// 获取信号量
semaphore <- struct{}{}
defer func() { <-semaphore }()
// 提取帖子ID
postID, exists := s.Attr("id")
if !exists || postID == "" {
errorChan <- fmt.Errorf("无法提取帖子ID: index=%d", index)
return
}
// 提取标题
titleElement := s.Find("h3.xs3 a")
title := p.cleanHTML(titleElement.Text())
title = strings.TrimSpace(title)
// 提取内容描述
contentElement := s.Find("p").First()
content := p.cleanHTML(contentElement.Text())
content = strings.TrimSpace(content)
// 提取日期时间
var datetime time.Time
dateElements := s.Find("p span")
if dateElements.Length() > 0 {
dateStr := strings.TrimSpace(dateElements.First().Text())View on GitHub (pinned to beaa561337)