ZhuLinsen/daily_stock_analysis · warning · IntelligenceServiceError

invalid RSS/Atom feed: {exc}

Error message

invalid RSS/Atom feed: {exc}

What it means

IntelligenceServiceError('invalid RSS/Atom feed: {exc}') is raised by _parse_feed when xml.etree.ElementTree.fromstring fails with ET.ParseError on the downloaded feed body. The message embeds the underlying parser message (e.g. 'syntax error', 'mismatched tag', 'not well-formed (invalid token)'). It means the bytes handed to the XML parser were not well-formed XML.

Source

Thrown at src/services/intelligence_service.py:600

            return normalized.encode("idna").decode("ascii")
        except UnicodeError:
            return normalized

    @staticmethod
    def _validate_addrinfos(addr_infos: Any) -> None:
        for info in addr_infos or []:
            try:
                ip = ipaddress.ip_address(info[4][0])
            except (IndexError, TypeError, ValueError):
                continue
            if IntelligenceService._is_blocked_ip(ip):
                raise IntelligenceServiceError("source url must not target private or local network addresses")

    def _parse_feed(self, content: bytes, *, source_name: str, limit: int) -> List[FeedEntry]:
        try:
            root = ET.fromstring(content)
        except ET.ParseError as exc:
            raise IntelligenceServiceError(f"invalid RSS/Atom feed: {exc}") from exc
        tag = self._strip_ns(root.tag).lower()
        if tag == "rss":
            nodes = root.findall("./channel/item")
            return [entry for entry in (self._parse_rss_item(node, source_name) for node in nodes[:limit]) if entry]
        if tag == "feed":
            nodes = root.findall("./{*}entry") or root.findall("./entry")
            return [entry for entry in (self._parse_atom_entry(node, source_name) for node in nodes[:limit]) if entry]
        raise IntelligenceServiceError("unsupported feed format; expected RSS or Atom")

    def _parse_newsnow_payload(self, payload: Any, *, source_name: str, limit: int) -> List[FeedEntry]:
        if not isinstance(payload, dict):
            raise IntelligenceServiceError("invalid NewsNow response: expected object")
        items = payload.get("items")
        if not isinstance(items, list):
            raise IntelligenceServiceError("invalid NewsNow response: missing items")
        entries = []
        for item in items[:limit]:
            if not isinstance(item, dict):

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Fetch the URL manually and inspect the first bytes: curl -sL <url> | head -c 400 — HTML output means the URL/auth is wrong, not the parser
  2. If the body is HTML, fix the source configuration (real feed URL, required headers, or cookies)
  3. If the body is XML but malformed, report it upstream or switch to the provider's valid feed endpoint
  4. Validate encoding: ensure the response is decoded per its declared charset before ET.fromstring
  5. Catch IntelligenceServiceError per source so one bad feed does not abort the whole refresh

Example fix

# before: URL actually serves an HTML portal page
source = IntelligenceSource(name="news", url="https://example.com/news")

# after: the real RSS endpoint
source = IntelligenceSource(name="news", url="https://example.com/news/rss.xml")
Defensive patterns

Strategy: try-catch

Validate before calling

def looks_like_feed_xml(content: bytes) -> bool:
    head = content.lstrip()[:256]
    return head.startswith(b"<?xml") or b"<rss" in head or b"<feed" in head

Try / catch

try:
    entries = svc._parse_feed(content, source_name=src, limit=20)
except IntelligenceServiceError as exc:
    if "invalid RSS/Atom feed" in str(exc):
        logger.warning("source %s returned non-XML body; check auth/URL", src)
        entries = []  # degrade per source

Prevention

When it happens

Trigger: A feed URL returning HTML (login pages, Cloudflare challenges, 404 pages served with status 200), truncated bodies from proxies, feeds containing unescaped ampersands or invalid UTF-8, or a payload that exceeded some downstream truncation before parsing.

Common situations: Source behind authentication that redirects to an HTML login, CDN bot protection returning an interstitial, an RSS generator emitting unescaped entities, charset mismatch (GBK content declared as UTF-8), or a truncated response from an aggressive proxy.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/f57a3cfff2c11874. Report an issue: GitHub.