Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.

65 righe
2.1 KiB

  1. import logging
  2. import xml.etree.ElementTree as ET
  3. from services.dates import parse_datetime
  4. logger = logging.getLogger(__name__)
  5. # Distinctive root tags used to sniff feed (XML) content apart from HTML.
  6. FEED_MARKERS = ('<rss', '<feed', '<rdf:rdf')
  7. def looks_like_feed(content):
  8. """Best-effort sniff for RSS/Atom XML content (vs. HTML pages)."""
  9. if not content or not isinstance(content, str):
  10. return False
  11. head = content[:4000].lower()
  12. return any(marker in head for marker in FEED_MARKERS)
  13. def _local(tag):
  14. """Return an element's local name, ignoring any XML namespace prefix."""
  15. return tag.rsplit('}', 1)[-1] if '}' in tag else tag
  16. def _child_text(elem, names):
  17. """First direct child whose local name is in ``names``, with text content."""
  18. for child in elem:
  19. if _local(child.tag) in names:
  20. text = (child.text or '').strip()
  21. if text:
  22. return text
  23. return None
  24. def parse_feed(content):
  25. """Extract ``(title, published_at)`` tuples from an RSS or Atom feed.
  26. Understands RSS 2.0 (``<item>``), RSS 1.0 (``<rdf:RDF>`` items) and Atom
  27. (``<entry>``). Publication timestamps are taken from ``pubDate``,
  28. ``published``, ``updated`` or ``dc:date`` (``None`` when absent or
  29. unparseable).
  30. """
  31. items = []
  32. try:
  33. root = ET.fromstring(content)
  34. except ET.ParseError as e:
  35. logger.error("Failed to parse feed XML: %s", e)
  36. return items
  37. except Exception as e:
  38. logger.error("Unexpected error parsing feed: %s", e, exc_info=True)
  39. return items
  40. # Work over every <item>/<entry> element regardless of nesting, which covers
  41. # RSS 2.0, RSS 1.0 (RDF) and Atom in one pass.
  42. entries = [el for el in root.iter() if _local(el.tag) in ('item', 'entry')]
  43. for entry in entries:
  44. title = _child_text(entry, ('title',))
  45. if not title:
  46. continue
  47. published = _child_text(entry, ('pubDate', 'published', 'updated', 'date'))
  48. items.append((title, parse_datetime(published)))
  49. logger.info("Parsed %d entries from feed", len(items))
  50. return items