Tencent/WeKnora · warning
no readable content extracted
Error message
no readable content extracted
What it means
Returned when readability successfully parsed the HTML but its scoring algorithm found no readable article node (article.Node == nil). The readability heuristic requires a minimum amount of text-bearing, structured content; pages that are too small, JS-shell single-page apps, or pure link lists yield no candidate article.
Source
Thrown at internal/datasource/connector/rss/client.go:115
}
// extractArticle fetches an article page and returns the readability-cleaned
// main content as HTML, plus the extracted title (may be empty). Returns an
// error if the page can't be fetched or no readable content is found, so the
// caller can fall back to feed-provided content.
func (c *client) extractArticle(ctx context.Context, articleURL string) (contentHTML, title string, err error) {
body, err := c.fetch(ctx, articleURL, maxArticleSize, false)
if err != nil {
return "", "", err
}
pageURL, _ := url.Parse(articleURL)
article, err := readability.FromReader(bytes.NewReader(body), pageURL)
if err != nil {
return "", "", fmt.Errorf("readability parse: %w", err)
}
if article.Node == nil {
return "", "", fmt.Errorf("no readable content extracted")
}
var buf bytes.Buffer
if err := article.RenderHTML(&buf); err != nil {
return "", "", fmt.Errorf("render article html: %w", err)
}
return buf.String(), article.Title(), nil
}
View on GitHub (pinned to 988cbb0330)
Solutions
- Fall back to the feed-provided content (item.Content / item.Description) — resolveItem is designed to do exactly this; ensure that fallback path is in place.
- For JS-only sites, no static fetch will work; either exclude the item or use feed-provided content.
- Detect login/paywall stubs (small body size, keywords like 'subscribe') and skip extraction for those domains.
- Consider a rendering proxy (headless browser service) if full text from SPAs is a hard requirement.
Example fix
// before
contentHTML, _, err := cli.extractArticle(ctx, item.Link)
if err != nil {
return item.Summary
}
// after
contentHTML, _, err := cli.extractArticle(ctx, item.Link)
if err != nil || utf8.RuneCountInString(contentHTML) < 200 {
return coalesce(item.Content, item.Description, item.Title)
} Defensive patterns
Strategy: fallback
Validate before calling
func looksLikeArticle(html string) bool {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
return false
}
text := strings.TrimSpace(doc.Find("body").Text())
return utf8.RuneCountInString(text) > 200
} Try / catch
contentHTML, title, err := cli.extractArticle(ctx, item.Link)
if err != nil || contentHTML == "" {
// SPA/paywall/landing page — no readable node
contentHTML = coalesce(item.Content, item.Description)
title = item.Title
} Prevention
- Always keep the feed-provided content fallback path; never treat full-text extraction as guaranteed.
- Detect paywall/login stubs by size and keywords and skip extraction for those domains.
- Maintain a per-domain skip list for known JS-only sites.
- Accept shorter articles rather than treating small extractions as failures.
When it happens
Trigger: The article page is a JavaScript-rendered SPA whose HTML contains no server-rendered text; the page has less content than readability's scoring thresholds; the URL redirected to a homepage, login page, or paywall stub; the page is a bare link aggregator with no body text.
Common situations: Modern news sites serving empty shells to non-browser clients; paywalled articles returning a stub page; feeds whose item links point to landing pages instead of articles; very short link-list posts (e.g. linkblogs).
Related errors
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/63368ffd57c88fad.
Report an issue: GitHub.