您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. # Anya
  2. A news headline aggregator that clusters near-duplicate headlines into **stories**
  3. and reports only the stories covered by a minimum number of distinct outlets.
  4. Instead of printing every pairwise "duplicate found" alert, Anya groups matching
  5. headlines transitively (union-find over cosine similarity) and answers the useful
  6. question: *which stories are multiple independent sources reporting right now?*
  7. ## How it works
  8. 1. **Load** source URLs and stopwords from `resources/`.
  9. 2. **Fetch** each source and extract candidate headline text from `<a>`/`<span>`
  10. tags, associating each headline with the nearest `<time>` publication timestamp.
  11. 3. **Normalize** each headline (lowercase, strip punctuation, remove stopwords).
  12. 4. **Cluster** headlines into stories using pairwise cosine similarity, linking
  13. matches transitively so a chain of near-duplicates collapses into one story.
  14. 5. **Filter & report** stories with at least `--min-sources` distinct outlets,
  15. optionally restricted to a date window.
  16. ## Requirements
  17. - Python 3.11+ (developed and tested on 3.14)
  18. - `requests`
  19. ```bash
  20. pip install requests
  21. ```
  22. ## Setup
  23. ```bash
  24. git clone https://ikibani.com/jbell730/anya.git
  25. cd anya
  26. pip install requests
  27. ```
  28. Run from the project root — the resource paths are relative (`./resources/...`).
  29. ## Configuration
  30. - `resources/sources.txt` — one news source URL per line.
  31. - `resources/stopwords.txt` — one stopword per line.
  32. - Defaults live at the top of `main.py` (`SIMILARITY_THRESHOLD = 0.75`,
  33. `MIN_SOURCES = 2`) and can be overridden on the command line.
  34. ## Usage
  35. ```bash
  36. python main.py [options]
  37. ```
  38. | Option | Description | Default |
  39. | --- | --- | --- |
  40. | `--min-sources N` | Only output stories reported by at least N distinct sources | `2` |
  41. | `--threshold T` | Cosine similarity used to consider two headlines the same story | `0.75` |
  42. | `--since YYYY-MM-DD` | Only consider headlines published on or after this date | *(none)* |
  43. | `--until YYYY-MM-DD` | Only consider headlines published on or before this date | *(none)* |
  44. | `--verbose` | Enable debug logging | off |
  45. ### Examples
  46. ```bash
  47. # Stories reported by 3+ distinct outlets
  48. python main.py --min-sources 3
  49. # Stories from the last week, need 2+ outlets
  50. python main.py --since 2026-09-07
  51. # A specific range with a stricter similarity threshold
  52. python main.py --since 2026-09-01 --until 2026-09-14 --threshold 0.8
  53. ```
  54. Sample output:
  55. ```
  56. News stories covered by at least 2 distinct sources (published on or after 2026-09-13)
  57. ============================================================
  58. [3 source(s)] Federal Reserve holds interest rates steady (2026-09-14)
  59. cnn.com, foxnews.com, reuters.com
  60. [2 source(s)] Senate passes major infrastructure bill (2026-09-14)
  61. npr.org, nbcnews.com
  62. ```
  63. ## Design notes
  64. - **A "source" is a distinct domain**, not a distinct URL. `www.cnn.com/us` and
  65. `cnn.com/politics` both normalize to `cnn.com`, so one outlet counts once even
  66. when listed under multiple sections.
  67. - **Date windowing drops undated headlines.** When `--since` or `--until` is set,
  68. any headline whose page carries no parseable timestamp is excluded because its
  69. recency can't be established (the count is logged). Without a date flag,
  70. everything is included.
  71. - **Publication dates** are drawn from `<time>` elements (their `datetime`/`title`
  72. attributes or inner text) and associated with headlines in document order; the
  73. association resets at each `<article>`/`<li>` boundary so undated headlines don't
  74. inherit a neighboring story's date.
  75. ## Tests
  76. ```bash
  77. python -m unittest discover -s tests -p 'test_*.py'
  78. ```
  79. ## Project structure
  80. ```
  81. anya/
  82. ├── main.py # CLI entry point and orchestration
  83. ├── services/
  84. │ ├── headlines.py # fetch + parse headlines (and timestamps)
  85. │ ├── normalization.py # stopword loading and headline normalization
  86. │ ├── similarity.py # cosine similarity over token lists
  87. │ ├── sources.py # load source URLs
  88. │ ├── stories.py # cluster headlines into stories
  89. │ └── dates.py # date parsing and window filtering
  90. ├── structs/
  91. │ ├── headline.py # Headline model (text, domain, published_at)
  92. │ └── story.py # Story model (sources, representative, latest_date)
  93. ├── resources/
  94. │ ├── sources.txt # one source URL per line
  95. │ └── stopwords.txt # one stopword per line
  96. └── tests/ # unit tests
  97. ```