Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

122 linhas
4.8 KiB

  1. import logging
  2. import string
  3. logger = logging.getLogger(__name__)
  4. def get_stopwords(path):
  5. logger.info("Attempting to load stopwords from file: '%s'", path)
  6. try:
  7. with open(path, 'r', encoding='utf-8-sig') as stopwords_file:
  8. logger.debug("Opened stopwords file '%s'", path)
  9. lines = stopwords_file.read().splitlines()
  10. stopwords = set(lines)
  11. logger.info("Successfully loaded %d unique stopwords from '%s' (%d total lines)", len(stopwords), path, len(lines))
  12. return stopwords
  13. except FileNotFoundError:
  14. logger.error("Stopwords file not found at path: '%s'", path, exc_info=True)
  15. raise
  16. except PermissionError:
  17. logger.error("Permission denied accessing stopwords file: '%s'", path, exc_info=True)
  18. raise
  19. except Exception as e:
  20. logger.error("Failed to load stopwords from '%s': %s", path, e, exc_info=True)
  21. raise
  22. def get_excluded_phrases(path):
  23. """Load boilerplate link phrases from a file (one per line, '#' comments).
  24. These are strings that appear in navigation/footer links ("skip to content",
  25. "your privacy choices", "terms of service") and should never be treated as
  26. headlines. Returns a list of stripped, non-empty phrases in file order.
  27. """
  28. logger.info("Attempting to load excluded phrases from file: '%s'", path)
  29. try:
  30. with open(path, 'r', encoding='utf-8-sig') as phrases_file:
  31. lines = phrases_file.read().splitlines()
  32. except FileNotFoundError:
  33. logger.error("Excluded phrases file not found at path: '%s'", path, exc_info=True)
  34. raise
  35. except PermissionError:
  36. logger.error("Permission denied accessing excluded phrases file: '%s'", path, exc_info=True)
  37. raise
  38. except Exception as e:
  39. logger.error("Failed to load excluded phrases from '%s': %s", path, e, exc_info=True)
  40. raise
  41. phrases = []
  42. for line in lines:
  43. stripped = line.strip()
  44. if not stripped or stripped.startswith('#'):
  45. continue
  46. phrases.append(stripped)
  47. logger.info("Successfully loaded %d excluded phrases from '%s'", len(phrases), path)
  48. return phrases
  49. def canonicalize(text):
  50. """Lowercase, turn punctuation into spaces, and collapse whitespace.
  51. Produces a comparable form of free text for phrase matching — e.g.
  52. ``"Terms-of-Service."`` becomes ``"terms of service"``.
  53. """
  54. text = (text or '').lower()
  55. for char in string.punctuation:
  56. text = text.replace(char, ' ')
  57. return ' '.join(text.split())
  58. def is_excluded(text, excluded_phrases):
  59. """Return True if ``text`` contains an excluded phrase as a contiguous
  60. token subsequence (case-insensitive, punctuation-insensitive).
  61. Matching is sub-phrase aware, so the entry ``privacy choices`` also matches
  62. the link text "Your Privacy Choices" without needing a separate exact row.
  63. """
  64. if not excluded_phrases:
  65. return False
  66. tokens = canonicalize(text).split()
  67. if not tokens:
  68. return False
  69. for phrase in excluded_phrases:
  70. phrase_tokens = canonicalize(phrase).split()
  71. if not phrase_tokens:
  72. continue
  73. for start in range(len(tokens) - len(phrase_tokens) + 1):
  74. if tokens[start:start + len(phrase_tokens)] == phrase_tokens:
  75. logger.debug("Text excluded by phrase %r: %r", phrase, text)
  76. return True
  77. return False
  78. def remove_stopwords(text, stopwords):
  79. logger.debug("Removing stopwords from text (%d chars): %r (available stopwords: %d)", len(text), text, len(stopwords))
  80. # split() without arguments handles all whitespace (spaces, tabs, newlines)
  81. words = text.split()
  82. sentence_words = []
  83. for word in words:
  84. # Strip surrounding punctuation and lowercase for comparison
  85. cleaned_word = word.strip(string.punctuation).lower()
  86. if cleaned_word and cleaned_word not in stopwords:
  87. sentence_words.append(word)
  88. elif cleaned_word in stopwords:
  89. logger.debug("Word filtered out as stopword: %r (original: %r)", cleaned_word, word)
  90. else:
  91. logger.debug("Word dropped (empty after stripping punctuation): %r", word)
  92. logger.debug("Filtered words: input=%d words, output=%d words -> %s", len(words), len(sentence_words), sentence_words)
  93. return sentence_words
  94. def normalize_headline(text, stopwords):
  95. logger.debug("Starting normalization of headline: %r", text)
  96. headline = text.strip().lower()
  97. punctuation = string.punctuation
  98. for char in punctuation:
  99. headline = headline.replace(char, '')
  100. logger.debug("Headline after lowercasing and punctuation removal: %r", headline)
  101. normalized = remove_stopwords(headline, stopwords)
  102. logger.debug("Completed normalization for %r -> %s", text, normalized)
  103. return normalized