html2rss 0.22.2 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -108
  3. data/lib/html2rss/{articles → article}/deduplicator.rb +3 -4
  4. data/lib/html2rss/{rss_builder → article}/enclosure.rb +6 -22
  5. data/lib/html2rss/article.rb +213 -0
  6. data/lib/html2rss/auto_source/discovery/dom_clustering/group_scorer.rb +80 -0
  7. data/lib/html2rss/auto_source/discovery/dom_clustering/overlap_resolver.rb +86 -0
  8. data/lib/html2rss/auto_source/discovery/dom_clustering.rb +119 -0
  9. data/lib/html2rss/auto_source/discovery/list_candidates.rb +94 -0
  10. data/lib/html2rss/auto_source/discovery/semantic_anchor_candidates.rb +219 -0
  11. data/lib/html2rss/auto_source/discovery/semantic_containers.rb +71 -0
  12. data/lib/html2rss/auto_source/discovery/sitemap.rb +159 -0
  13. data/lib/html2rss/auto_source/discovery.rb +14 -0
  14. data/lib/html2rss/auto_source/link_heuristics/anchor_signals.rb +28 -0
  15. data/lib/html2rss/auto_source/link_heuristics/container_assessor.rb +106 -0
  16. data/lib/html2rss/auto_source/link_heuristics/container_signals.rb +80 -0
  17. data/lib/html2rss/auto_source/link_heuristics/destination_facts.rb +42 -0
  18. data/lib/html2rss/auto_source/link_heuristics/href_extractor.rb +38 -0
  19. data/lib/html2rss/auto_source/link_heuristics/path_classifier.rb +221 -0
  20. data/lib/html2rss/auto_source/link_heuristics/text_classifier.rb +66 -0
  21. data/lib/html2rss/auto_source/link_heuristics.rb +139 -0
  22. data/lib/html2rss/auto_source/scraper/html.rb +22 -33
  23. data/lib/html2rss/auto_source/scraper/json_state.rb +1 -1
  24. data/lib/html2rss/auto_source/scraper/meta_oembed.rb +163 -0
  25. data/lib/html2rss/auto_source/scraper/microformats2.rb +146 -0
  26. data/lib/html2rss/auto_source/scraper/schema/category_extractor.rb +29 -114
  27. data/lib/html2rss/auto_source/scraper/schema/item_list.rb +50 -11
  28. data/lib/html2rss/auto_source/scraper/schema/thing.rb +35 -7
  29. data/lib/html2rss/auto_source/scraper/schema.rb +61 -18
  30. data/lib/html2rss/auto_source/scraper/semantic_html/{deduplicator.rb → entry_deduplicator.rb} +3 -3
  31. data/lib/html2rss/auto_source/scraper/semantic_html.rb +23 -171
  32. data/lib/html2rss/auto_source/scraper/sitemap.rb +153 -0
  33. data/lib/html2rss/auto_source/scraper/wordpress_api/page_scope/date_archive_range.rb +110 -0
  34. data/lib/html2rss/auto_source/scraper/wordpress_api/page_scope.rb +2 -78
  35. data/lib/html2rss/auto_source/scraper/wordpress_api.rb +9 -0
  36. data/lib/html2rss/auto_source/scraper.rb +10 -3
  37. data/lib/html2rss/auto_source.rb +30 -35
  38. data/lib/html2rss/channel.rb +198 -0
  39. data/lib/html2rss/cli.rb +5 -6
  40. data/lib/html2rss/config/auto_source_contract.rb +51 -0
  41. data/lib/html2rss/config/request_controls.rb +136 -0
  42. data/lib/html2rss/config/request_headers.rb +1 -1
  43. data/lib/html2rss/config/schema.rb +65 -114
  44. data/lib/html2rss/{selectors/config.rb → config/selectors_validator.rb} +56 -7
  45. data/lib/html2rss/config/validator.rb +6 -6
  46. data/lib/html2rss/config.rb +176 -19
  47. data/lib/html2rss/{configuration.rb → defaults.rb} +12 -10
  48. data/lib/html2rss/feed_builder/item_presentation.rb +42 -0
  49. data/lib/html2rss/feed_builder/json_feed/item.rb +112 -0
  50. data/lib/html2rss/feed_builder/json_feed.rb +81 -0
  51. data/lib/html2rss/feed_builder/rss/stylesheet.rb +65 -0
  52. data/lib/html2rss/feed_builder/rss.rb +116 -0
  53. data/lib/html2rss/feed_builder.rb +15 -0
  54. data/lib/html2rss/feed_pipeline/auto_fallback.rb +120 -60
  55. data/lib/html2rss/feed_pipeline/runtime_policy.rb +108 -0
  56. data/lib/html2rss/feed_pipeline/strategy_plan.rb +64 -0
  57. data/lib/html2rss/feed_pipeline.rb +83 -89
  58. data/lib/html2rss/feed_result.rb +92 -0
  59. data/lib/html2rss/html/article_extractor/category_extractor.rb +123 -0
  60. data/lib/html2rss/html/article_extractor/date_extractor.rb +22 -0
  61. data/lib/html2rss/html/article_extractor/enclosure_extractor.rb +95 -0
  62. data/lib/html2rss/html/article_extractor/heading_extractor.rb +54 -0
  63. data/lib/html2rss/html/article_extractor/id_generator.rb +69 -0
  64. data/lib/html2rss/html/article_extractor/image_extractor.rb +60 -0
  65. data/lib/html2rss/html/article_extractor.rb +134 -0
  66. data/lib/html2rss/html/navigator/text_extractor.rb +79 -0
  67. data/lib/html2rss/html/navigator.rb +146 -0
  68. data/lib/html2rss/html/rendering/audio_renderer.rb +38 -0
  69. data/lib/html2rss/html/rendering/description_builder.rb +89 -0
  70. data/lib/html2rss/html/rendering/image_renderer.rb +43 -0
  71. data/lib/html2rss/html/rendering/media_renderer.rb +39 -0
  72. data/lib/html2rss/html/rendering/pdf_renderer.rb +36 -0
  73. data/lib/html2rss/html/rendering/video_renderer.rb +38 -0
  74. data/lib/html2rss/html/rendering.rb +25 -0
  75. data/lib/html2rss/html.rb +8 -0
  76. data/lib/html2rss/request_service/blocked_surface.rb +67 -0
  77. data/lib/html2rss/request_service/botasaurus_contract.rb +33 -9
  78. data/lib/html2rss/request_service/botasaurus_strategy.rb +21 -30
  79. data/lib/html2rss/request_service/browserless_strategy.rb +2 -23
  80. data/lib/html2rss/request_service/budget.rb +97 -18
  81. data/lib/html2rss/request_service/context.rb +1 -3
  82. data/lib/html2rss/request_service/faraday_strategy.rb +12 -26
  83. data/lib/html2rss/request_service/local_file_strategy.rb +12 -4
  84. data/lib/html2rss/request_service/network_guard.rb +125 -0
  85. data/lib/html2rss/request_service/policy.rb +10 -82
  86. data/lib/html2rss/request_service/puppet_commander/navigation_guards.rb +148 -0
  87. data/lib/html2rss/request_service/puppet_commander/preload_runner.rb +86 -0
  88. data/lib/html2rss/request_service/puppet_commander.rb +22 -191
  89. data/lib/html2rss/request_service/response.rb +9 -1
  90. data/lib/html2rss/request_service/response_guard.rb +1 -1
  91. data/lib/html2rss/request_service/strategy.rb +123 -5
  92. data/lib/html2rss/request_service.rb +7 -2
  93. data/lib/html2rss/request_session/pager/base.rb +83 -0
  94. data/lib/html2rss/request_session/pager/custom_selector.rb +39 -0
  95. data/lib/html2rss/request_session/pager/json_cursor.rb +78 -0
  96. data/lib/html2rss/request_session/pager/offset.rb +48 -0
  97. data/lib/html2rss/request_session/pager/rel_next.rb +23 -0
  98. data/lib/html2rss/request_session/pager/url_template.rb +48 -0
  99. data/lib/html2rss/request_session/pager.rb +75 -0
  100. data/lib/html2rss/request_session.rb +29 -16
  101. data/lib/html2rss/selectors/item_scope.rb +30 -0
  102. data/lib/html2rss/selectors/post_processors/base.rb +1 -1
  103. data/lib/html2rss/selectors/post_processors/markdown_to_html.rb +0 -1
  104. data/lib/html2rss/selectors/post_processors/sanitize_html.rb +0 -2
  105. data/lib/html2rss/selectors/post_processors/template.rb +7 -3
  106. data/lib/html2rss/selectors.rb +84 -64
  107. data/lib/html2rss/status.rb +132 -0
  108. data/lib/html2rss/version.rb +1 -3
  109. data/lib/html2rss.rb +34 -22
  110. data/schema/html2rss-config.schema.json +109 -9
  111. metadata +71 -43
  112. data/lib/html2rss/auto_source/scraper/html/class_clustering.rb +0 -196
  113. data/lib/html2rss/auto_source/scraper/link_heuristics.rb +0 -401
  114. data/lib/html2rss/auto_source/scraper/schema/list_item.rb +0 -28
  115. data/lib/html2rss/auto_source/scraper/semantic_html/anchor_selector.rb +0 -49
  116. data/lib/html2rss/blocked_surface.rb +0 -65
  117. data/lib/html2rss/category_extractor.rb +0 -116
  118. data/lib/html2rss/config/class_methods.rb +0 -183
  119. data/lib/html2rss/html_extractor/date_extractor.rb +0 -20
  120. data/lib/html2rss/html_extractor/enclosure_extractor.rb +0 -91
  121. data/lib/html2rss/html_extractor/heading_extractor.rb +0 -50
  122. data/lib/html2rss/html_extractor/id_generator.rb +0 -67
  123. data/lib/html2rss/html_extractor/image_extractor.rb +0 -58
  124. data/lib/html2rss/html_extractor/list_candidates.rb +0 -88
  125. data/lib/html2rss/html_extractor/semantic_anchor_candidates.rb +0 -260
  126. data/lib/html2rss/html_extractor/semantic_containers.rb +0 -69
  127. data/lib/html2rss/html_extractor/text_extractor.rb +0 -77
  128. data/lib/html2rss/html_extractor.rb +0 -190
  129. data/lib/html2rss/html_navigator.rb +0 -71
  130. data/lib/html2rss/json_feed_builder/item.rb +0 -94
  131. data/lib/html2rss/json_feed_builder.rb +0 -59
  132. data/lib/html2rss/rendering/audio_renderer.rb +0 -36
  133. data/lib/html2rss/rendering/description_builder.rb +0 -87
  134. data/lib/html2rss/rendering/image_renderer.rb +0 -41
  135. data/lib/html2rss/rendering/media_renderer.rb +0 -37
  136. data/lib/html2rss/rendering/pdf_renderer.rb +0 -34
  137. data/lib/html2rss/rendering/video_renderer.rb +0 -36
  138. data/lib/html2rss/rendering.rb +0 -23
  139. data/lib/html2rss/request_controls.rb +0 -133
  140. data/lib/html2rss/request_session/rel_next_pager.rb +0 -70
  141. data/lib/html2rss/request_session/runtime_input.rb +0 -71
  142. data/lib/html2rss/request_session/runtime_policy.rb +0 -84
  143. data/lib/html2rss/rss_builder/article.rb +0 -208
  144. data/lib/html2rss/rss_builder/channel.rb +0 -118
  145. data/lib/html2rss/rss_builder/stylesheet.rb +0 -63
  146. data/lib/html2rss/rss_builder.rb +0 -101
@@ -109,6 +109,31 @@
109
109
  },
110
110
  "required": []
111
111
  },
112
+ "sitemap": {
113
+ "type": "object",
114
+ "properties": {
115
+ "enabled": {
116
+ "type": "boolean",
117
+ "not": {
118
+ "type": "null"
119
+ }
120
+ },
121
+ "min_priority": {
122
+ "type": "number",
123
+ "not": {
124
+ "type": "null"
125
+ }
126
+ },
127
+ "max_age_days": {
128
+ "type": "integer",
129
+ "not": {
130
+ "type": "null"
131
+ },
132
+ "exclusiveMinimum": 0
133
+ }
134
+ },
135
+ "required": []
136
+ },
112
137
  "schema": {
113
138
  "type": "object",
114
139
  "properties": {
@@ -133,6 +158,18 @@
133
158
  },
134
159
  "required": []
135
160
  },
161
+ "microformats2": {
162
+ "type": "object",
163
+ "properties": {
164
+ "enabled": {
165
+ "type": "boolean",
166
+ "not": {
167
+ "type": "null"
168
+ }
169
+ }
170
+ },
171
+ "required": []
172
+ },
136
173
  "json_state": {
137
174
  "type": "object",
138
175
  "properties": {
@@ -145,6 +182,18 @@
145
182
  },
146
183
  "required": []
147
184
  },
185
+ "meta_oembed": {
186
+ "type": "object",
187
+ "properties": {
188
+ "enabled": {
189
+ "type": "boolean",
190
+ "not": {
191
+ "type": "null"
192
+ }
193
+ }
194
+ },
195
+ "required": []
196
+ },
148
197
  "semantic_html": {
149
198
  "type": "object",
150
199
  "properties": {
@@ -224,15 +273,26 @@
224
273
  "wordpress_api": {
225
274
  "enabled": true
226
275
  },
276
+ "sitemap": {
277
+ "enabled": true,
278
+ "min_priority": 0.3,
279
+ "max_age_days": 30
280
+ },
227
281
  "schema": {
228
282
  "enabled": true
229
283
  },
230
284
  "microdata": {
231
285
  "enabled": true
232
286
  },
287
+ "microformats2": {
288
+ "enabled": true
289
+ },
233
290
  "json_state": {
234
291
  "enabled": true
235
292
  },
293
+ "meta_oembed": {
294
+ "enabled": true
295
+ },
236
296
  "semantic_html": {
237
297
  "enabled": true,
238
298
  "fallback_anchorless": true
@@ -277,18 +337,58 @@
277
337
  }
278
338
  },
279
339
  "pagination": {
280
- "type": "object",
281
- "properties": {
282
- "max_pages": {
340
+ "description": "Pagination configuration or maximum page count integer.",
341
+ "oneOf": [
342
+ {
283
343
  "type": "integer",
284
- "not": {
285
- "type": "null"
286
- },
287
344
  "exclusiveMinimum": 0
345
+ },
346
+ {
347
+ "type": "object",
348
+ "properties": {
349
+ "strategy": {
350
+ "type": "string",
351
+ "enum": [
352
+ "rel_next",
353
+ "custom_selector",
354
+ "url_template",
355
+ "offset",
356
+ "json_cursor"
357
+ ]
358
+ },
359
+ "max_pages": {
360
+ "type": "integer",
361
+ "exclusiveMinimum": 0
362
+ },
363
+ "selector": {
364
+ "type": "string"
365
+ },
366
+ "param": {
367
+ "type": "string"
368
+ },
369
+ "start_page": {
370
+ "type": "integer"
371
+ },
372
+ "step": {
373
+ "type": "integer",
374
+ "exclusiveMinimum": 0
375
+ },
376
+ "start_offset": {
377
+ "type": "integer"
378
+ },
379
+ "increment": {
380
+ "type": "integer",
381
+ "exclusiveMinimum": 0
382
+ },
383
+ "cursor_path": {
384
+ "type": "string"
385
+ },
386
+ "next_url_path": {
387
+ "type": "string"
388
+ }
389
+ },
390
+ "additionalProperties": true
288
391
  }
289
- },
290
- "required": [
291
- "max_pages"
292
392
  ]
293
393
  }
294
394
  },
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: html2rss
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.2
4
+ version: 0.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gil Desmarais
@@ -273,63 +273,88 @@ files:
273
273
  - exe/html2rss
274
274
  - html2rss.gemspec
275
275
  - lib/html2rss.rb
276
- - lib/html2rss/articles/deduplicator.rb
276
+ - lib/html2rss/article.rb
277
+ - lib/html2rss/article/deduplicator.rb
278
+ - lib/html2rss/article/enclosure.rb
277
279
  - lib/html2rss/auto_source.rb
278
280
  - lib/html2rss/auto_source/cleanup.rb
281
+ - lib/html2rss/auto_source/discovery.rb
282
+ - lib/html2rss/auto_source/discovery/dom_clustering.rb
283
+ - lib/html2rss/auto_source/discovery/dom_clustering/group_scorer.rb
284
+ - lib/html2rss/auto_source/discovery/dom_clustering/overlap_resolver.rb
285
+ - lib/html2rss/auto_source/discovery/list_candidates.rb
286
+ - lib/html2rss/auto_source/discovery/semantic_anchor_candidates.rb
287
+ - lib/html2rss/auto_source/discovery/semantic_containers.rb
288
+ - lib/html2rss/auto_source/discovery/sitemap.rb
289
+ - lib/html2rss/auto_source/link_heuristics.rb
290
+ - lib/html2rss/auto_source/link_heuristics/anchor_signals.rb
291
+ - lib/html2rss/auto_source/link_heuristics/container_assessor.rb
292
+ - lib/html2rss/auto_source/link_heuristics/container_signals.rb
293
+ - lib/html2rss/auto_source/link_heuristics/destination_facts.rb
294
+ - lib/html2rss/auto_source/link_heuristics/href_extractor.rb
295
+ - lib/html2rss/auto_source/link_heuristics/path_classifier.rb
296
+ - lib/html2rss/auto_source/link_heuristics/text_classifier.rb
279
297
  - lib/html2rss/auto_source/scraper.rb
280
298
  - lib/html2rss/auto_source/scraper/html.rb
281
- - lib/html2rss/auto_source/scraper/html/class_clustering.rb
282
299
  - lib/html2rss/auto_source/scraper/json_state.rb
283
- - lib/html2rss/auto_source/scraper/link_heuristics.rb
300
+ - lib/html2rss/auto_source/scraper/meta_oembed.rb
284
301
  - lib/html2rss/auto_source/scraper/microdata.rb
302
+ - lib/html2rss/auto_source/scraper/microformats2.rb
285
303
  - lib/html2rss/auto_source/scraper/schema.rb
286
304
  - lib/html2rss/auto_source/scraper/schema/category_extractor.rb
287
305
  - lib/html2rss/auto_source/scraper/schema/item_list.rb
288
- - lib/html2rss/auto_source/scraper/schema/list_item.rb
289
306
  - lib/html2rss/auto_source/scraper/schema/thing.rb
290
307
  - lib/html2rss/auto_source/scraper/semantic_html.rb
291
- - lib/html2rss/auto_source/scraper/semantic_html/anchor_selector.rb
292
- - lib/html2rss/auto_source/scraper/semantic_html/deduplicator.rb
308
+ - lib/html2rss/auto_source/scraper/semantic_html/entry_deduplicator.rb
309
+ - lib/html2rss/auto_source/scraper/sitemap.rb
293
310
  - lib/html2rss/auto_source/scraper/wordpress_api.rb
294
311
  - lib/html2rss/auto_source/scraper/wordpress_api/page_scope.rb
312
+ - lib/html2rss/auto_source/scraper/wordpress_api/page_scope/date_archive_range.rb
295
313
  - lib/html2rss/auto_source/scraper/wordpress_api/posts_endpoint.rb
296
- - lib/html2rss/blocked_surface.rb
297
- - lib/html2rss/category_extractor.rb
314
+ - lib/html2rss/channel.rb
298
315
  - lib/html2rss/cli.rb
299
316
  - lib/html2rss/config.rb
300
- - lib/html2rss/config/class_methods.rb
317
+ - lib/html2rss/config/auto_source_contract.rb
301
318
  - lib/html2rss/config/dynamic_params.rb
302
319
  - lib/html2rss/config/multiple_feeds_config.rb
320
+ - lib/html2rss/config/request_controls.rb
303
321
  - lib/html2rss/config/request_headers.rb
304
322
  - lib/html2rss/config/schema.rb
323
+ - lib/html2rss/config/selectors_validator.rb
305
324
  - lib/html2rss/config/validator.rb
306
- - lib/html2rss/configuration.rb
325
+ - lib/html2rss/defaults.rb
307
326
  - lib/html2rss/error.rb
327
+ - lib/html2rss/feed_builder.rb
328
+ - lib/html2rss/feed_builder/item_presentation.rb
329
+ - lib/html2rss/feed_builder/json_feed.rb
330
+ - lib/html2rss/feed_builder/json_feed/item.rb
331
+ - lib/html2rss/feed_builder/rss.rb
332
+ - lib/html2rss/feed_builder/rss/stylesheet.rb
308
333
  - lib/html2rss/feed_pipeline.rb
309
334
  - lib/html2rss/feed_pipeline/auto_fallback.rb
335
+ - lib/html2rss/feed_pipeline/runtime_policy.rb
336
+ - lib/html2rss/feed_pipeline/strategy_plan.rb
337
+ - lib/html2rss/feed_result.rb
310
338
  - lib/html2rss/hash_util.rb
311
- - lib/html2rss/html_extractor.rb
312
- - lib/html2rss/html_extractor/date_extractor.rb
313
- - lib/html2rss/html_extractor/enclosure_extractor.rb
314
- - lib/html2rss/html_extractor/heading_extractor.rb
315
- - lib/html2rss/html_extractor/id_generator.rb
316
- - lib/html2rss/html_extractor/image_extractor.rb
317
- - lib/html2rss/html_extractor/list_candidates.rb
318
- - lib/html2rss/html_extractor/semantic_anchor_candidates.rb
319
- - lib/html2rss/html_extractor/semantic_containers.rb
320
- - lib/html2rss/html_extractor/text_extractor.rb
321
- - lib/html2rss/html_navigator.rb
322
- - lib/html2rss/json_feed_builder.rb
323
- - lib/html2rss/json_feed_builder/item.rb
324
- - lib/html2rss/rendering.rb
325
- - lib/html2rss/rendering/audio_renderer.rb
326
- - lib/html2rss/rendering/description_builder.rb
327
- - lib/html2rss/rendering/image_renderer.rb
328
- - lib/html2rss/rendering/media_renderer.rb
329
- - lib/html2rss/rendering/pdf_renderer.rb
330
- - lib/html2rss/rendering/video_renderer.rb
331
- - lib/html2rss/request_controls.rb
339
+ - lib/html2rss/html.rb
340
+ - lib/html2rss/html/article_extractor.rb
341
+ - lib/html2rss/html/article_extractor/category_extractor.rb
342
+ - lib/html2rss/html/article_extractor/date_extractor.rb
343
+ - lib/html2rss/html/article_extractor/enclosure_extractor.rb
344
+ - lib/html2rss/html/article_extractor/heading_extractor.rb
345
+ - lib/html2rss/html/article_extractor/id_generator.rb
346
+ - lib/html2rss/html/article_extractor/image_extractor.rb
347
+ - lib/html2rss/html/navigator.rb
348
+ - lib/html2rss/html/navigator/text_extractor.rb
349
+ - lib/html2rss/html/rendering.rb
350
+ - lib/html2rss/html/rendering/audio_renderer.rb
351
+ - lib/html2rss/html/rendering/description_builder.rb
352
+ - lib/html2rss/html/rendering/image_renderer.rb
353
+ - lib/html2rss/html/rendering/media_renderer.rb
354
+ - lib/html2rss/html/rendering/pdf_renderer.rb
355
+ - lib/html2rss/html/rendering/video_renderer.rb
332
356
  - lib/html2rss/request_service.rb
357
+ - lib/html2rss/request_service/blocked_surface.rb
333
358
  - lib/html2rss/request_service/botasaurus_contract.rb
334
359
  - lib/html2rss/request_service/botasaurus_strategy.rb
335
360
  - lib/html2rss/request_service/browserless_strategy.rb
@@ -337,28 +362,30 @@ files:
337
362
  - lib/html2rss/request_service/context.rb
338
363
  - lib/html2rss/request_service/faraday_strategy.rb
339
364
  - lib/html2rss/request_service/local_file_strategy.rb
365
+ - lib/html2rss/request_service/network_guard.rb
340
366
  - lib/html2rss/request_service/policy.rb
341
367
  - lib/html2rss/request_service/puppet_commander.rb
368
+ - lib/html2rss/request_service/puppet_commander/navigation_guards.rb
369
+ - lib/html2rss/request_service/puppet_commander/preload_runner.rb
342
370
  - lib/html2rss/request_service/response.rb
343
371
  - lib/html2rss/request_service/response_guard.rb
344
372
  - lib/html2rss/request_service/strategy.rb
345
373
  - lib/html2rss/request_session.rb
346
- - lib/html2rss/request_session/rel_next_pager.rb
347
- - lib/html2rss/request_session/runtime_input.rb
348
- - lib/html2rss/request_session/runtime_policy.rb
349
- - lib/html2rss/rss_builder.rb
350
- - lib/html2rss/rss_builder/article.rb
351
- - lib/html2rss/rss_builder/channel.rb
352
- - lib/html2rss/rss_builder/enclosure.rb
353
- - lib/html2rss/rss_builder/stylesheet.rb
374
+ - lib/html2rss/request_session/pager.rb
375
+ - lib/html2rss/request_session/pager/base.rb
376
+ - lib/html2rss/request_session/pager/custom_selector.rb
377
+ - lib/html2rss/request_session/pager/json_cursor.rb
378
+ - lib/html2rss/request_session/pager/offset.rb
379
+ - lib/html2rss/request_session/pager/rel_next.rb
380
+ - lib/html2rss/request_session/pager/url_template.rb
354
381
  - lib/html2rss/selectors.rb
355
- - lib/html2rss/selectors/config.rb
356
382
  - lib/html2rss/selectors/extractors.rb
357
383
  - lib/html2rss/selectors/extractors/attribute.rb
358
384
  - lib/html2rss/selectors/extractors/href.rb
359
385
  - lib/html2rss/selectors/extractors/html.rb
360
386
  - lib/html2rss/selectors/extractors/static.rb
361
387
  - lib/html2rss/selectors/extractors/text.rb
388
+ - lib/html2rss/selectors/item_scope.rb
362
389
  - lib/html2rss/selectors/object_to_xml_converter.rb
363
390
  - lib/html2rss/selectors/post_processors.rb
364
391
  - lib/html2rss/selectors/post_processors/base.rb
@@ -372,6 +399,7 @@ files:
372
399
  - lib/html2rss/selectors/post_processors/sanitize_html.rb
373
400
  - lib/html2rss/selectors/post_processors/substring.rb
374
401
  - lib/html2rss/selectors/post_processors/template.rb
402
+ - lib/html2rss/status.rb
375
403
  - lib/html2rss/url.rb
376
404
  - lib/html2rss/version.rb
377
405
  - lib/tasks/config_schema.rake
@@ -381,7 +409,7 @@ licenses:
381
409
  - MIT
382
410
  metadata:
383
411
  allowed_push_host: https://rubygems.org
384
- changelog_uri: https://github.com/html2rss/html2rss/releases/tag/v0.22.2
412
+ changelog_uri: https://github.com/html2rss/html2rss/releases/tag/v0.24.0
385
413
  rubygems_mfa_required: 'true'
386
414
  rdoc_options: []
387
415
  require_paths:
@@ -397,7 +425,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
397
425
  - !ruby/object:Gem::Version
398
426
  version: '0'
399
427
  requirements: []
400
- rubygems_version: 4.0.10
428
+ rubygems_version: 4.0.16
401
429
  specification_version: 4
402
430
  summary: Generates RSS feeds from websites by scraping a URL and using CSS selectors
403
431
  to extract item.
@@ -1,196 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Html2rss
4
- class AutoSource
5
- module Scraper
6
- class Html
7
- ##
8
- # ClassClustering clusters DOM elements on anchorless pages by class lists and scores
9
- # candidate groups to find the best list of content cards/articles.
10
- # rubocop:disable Metrics/ClassLength
11
- class ClassClustering
12
- # Node tags considered layout containers
13
- LAYOUT_TAG_NAMES = Set['div', 'section', 'article', 'li', 'ul', 'ol'].freeze
14
- # HTML/layout tags excluded from candidate nodes
15
- EXCLUDED_TAGS = Set['html', 'body', 'nav', 'footer', 'header', 'svg', 'script', 'style'].freeze
16
-
17
- class << self
18
- ##
19
- # Clusters elements in parsed_body and returns the best set of content card nodes.
20
- #
21
- # @param parsed_body [Nokogiri::HTML::Document] parsed HTML document
22
- # @param minimum_selector_frequency [Integer] minimum frequency for class groups
23
- # @return [Array<Nokogiri::XML::Node>] candidate nodes of the top-scoring class group
24
- def call(parsed_body, minimum_selector_frequency:)
25
- new(parsed_body, minimum_selector_frequency:).call
26
- end
27
- end
28
-
29
- # @param parsed_body [Nokogiri::HTML::Document]
30
- # @param minimum_selector_frequency [Integer]
31
- def initialize(parsed_body, minimum_selector_frequency:)
32
- @parsed_body = parsed_body
33
- @minimum_frequency = minimum_selector_frequency
34
- @text_words = {}.compare_by_identity
35
- @has_date = {}.compare_by_identity
36
- end
37
-
38
- # @return [Array<Nokogiri::XML::Node>]
39
- def call
40
- candidate_groups = collect_candidate_groups
41
- return [] if candidate_groups.empty?
42
-
43
- non_containers = filter_containers(candidate_groups)
44
- final_groups = filter_1_to_1_overlap(non_containers)
45
-
46
- select_best_group(final_groups)
47
- end
48
-
49
- private
50
-
51
- def collect_candidate_groups
52
- class_groups = Hash.new { |h, k| h[k] = [] }
53
- cache = {}.compare_by_identity
54
-
55
- @parsed_body.css('[class]').each { |node| add_node_to_groups(node, class_groups, cache) }
56
-
57
- class_groups.select { |_, nodes| nodes.size >= @minimum_frequency }
58
- end
59
-
60
- def add_node_to_groups(node, class_groups, cache)
61
- return if EXCLUDED_TAGS.include?(node.name) || HtmlExtractor.ignored_container_path?(node, cache)
62
-
63
- cls = normalize_class(node['class'])
64
- class_groups[cls] << node unless cls.empty?
65
- end
66
-
67
- def normalize_class(class_attr)
68
- class_str = class_attr.to_s.strip
69
- return '' if class_str.empty?
70
-
71
- # Bypass split/sort/join allocation for single-class lists
72
- if class_str.include?(' ')
73
- class_str.split(/\s+/).sort.join(' ')
74
- else
75
- class_str
76
- end
77
- end
78
-
79
- # Discard group A if any node of A contains > 1 node of another group B
80
- def filter_containers(groups)
81
- groups.reject do |cls_a, nodes_a|
82
- groups.any? { |cls_b, nodes_b| cls_a != cls_b && container_of?(nodes_a, nodes_b) }
83
- end
84
- end
85
-
86
- # rubocop:disable Metrics/MethodLength
87
- def container_of?(nodes_a, nodes_b)
88
- return false unless LAYOUT_TAG_NAMES.include?(nodes_b.first.name)
89
-
90
- nodes_a.any? do |node_a|
91
- count = 0
92
- nodes_b.each do |node_b|
93
- next if node_a == node_b
94
-
95
- if HtmlNavigator.descendant_of?(node_b, node_a)
96
- count += 1
97
- break if count > 1
98
- end
99
- end
100
- count > 1
101
- end
102
- end
103
- # rubocop:enable Metrics/MethodLength
104
-
105
- # If group A contains group B, and they have the same size:
106
- # - If B (the descendant) contains >= 80% of A's words, AND B's tag is div/section/article,
107
- # B is the actual content card. Discard A.
108
- # - Otherwise, B is a sub-element (header, metadata line, button). Discard B.
109
- def filter_1_to_1_overlap(groups)
110
- discarded = Set.new
111
- groups.each_key do |cls_a|
112
- groups.each_key do |cls_b|
113
- next if cls_a == cls_b || discarded.include?(cls_a) || discarded.include?(cls_b)
114
-
115
- resolve_1_to_1_overlap(cls_a, cls_b, groups, discarded)
116
- end
117
- end
118
-
119
- groups.except(*discarded)
120
- end
121
-
122
- def resolve_1_to_1_overlap(cls_a, cls_b, groups, discarded)
123
- nodes_a = groups[cls_a]
124
- nodes_b = groups[cls_b]
125
- return if nodes_a.size != nodes_b.size
126
- return unless nodes_a.zip(nodes_b).all? { |a, b| a != b && HtmlNavigator.descendant_of?(b, a) }
127
-
128
- discarded << (keep_descendant?(nodes_a, nodes_b) ? cls_a : cls_b)
129
- end
130
-
131
- def keep_descendant?(nodes_a, nodes_b)
132
- avg_words(nodes_b) >= 0.8 * avg_words(nodes_a) &&
133
- LAYOUT_TAG_NAMES.include?(nodes_b.first.name)
134
- end
135
-
136
- def select_best_group(groups)
137
- best_nodes = []
138
- best_score = -1
139
-
140
- groups.each_value do |nodes|
141
- score = score_group(nodes)
142
- next if score.negative?
143
-
144
- (best_nodes = nodes) && (best_score = score) if score > best_score
145
- end
146
-
147
- best_nodes
148
- end
149
-
150
- def score_group(nodes)
151
- avg_w = avg_words(nodes)
152
- return -1 if avg_w < 5
153
-
154
- score = nodes.size + (avg_w / 5.0)
155
- score += 20 if nodes_heading?(nodes)
156
- score += 20 if nodes_time?(nodes)
157
- score += 40 if nodes_date?(nodes)
158
- score
159
- end
160
-
161
- def nodes_heading?(nodes)
162
- nodes.any? do |n|
163
- n.at_css(HtmlExtractor::HEADING_TAGS.join(',')) ||
164
- n.at_css('.font-bold, .font-semibold')
165
- end
166
- end
167
-
168
- def nodes_time?(nodes)
169
- nodes.any? { |n| n.at_css('time, [datetime]') }
170
- end
171
-
172
- def nodes_date?(nodes)
173
- nodes.any? { |n| date?(n) }
174
- end
175
-
176
- def avg_words(nodes)
177
- nodes.sum { |n| text_words(n) } / nodes.size.to_f
178
- end
179
-
180
- def text_words(node)
181
- @text_words[node] ||= HtmlExtractor.extract_visible_text(node).to_s.scan(/\p{Alnum}+/).size
182
- end
183
-
184
- def date?(node)
185
- @has_date[node] ||= begin
186
- text = HtmlExtractor.extract_visible_text(node).to_s
187
- text.match?(%r{\b\d{4}[-/]\d{2}[-/]\d{2}\b}) ||
188
- text.match?(/\b(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\b/i)
189
- end
190
- end
191
- # rubocop:enable Metrics/ClassLength
192
- end
193
- end
194
- end
195
- end
196
- end