html2rss 0.21.0 → 0.22.1
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.
- checksums.yaml +4 -4
- data/lib/html2rss/auto_source/scraper/html/class_clustering.rb +196 -0
- data/lib/html2rss/auto_source/scraper/html.rb +14 -2
- data/lib/html2rss/auto_source/scraper/json_state.rb +1 -1
- data/lib/html2rss/auto_source/scraper/microdata.rb +16 -4
- data/lib/html2rss/auto_source/scraper/semantic_html/deduplicator.rb +1 -1
- data/lib/html2rss/auto_source/scraper/semantic_html.rb +28 -20
- data/lib/html2rss/auto_source.rb +6 -2
- data/lib/html2rss/cli.rb +61 -10
- data/lib/html2rss/config/class_methods.rb +3 -3
- data/lib/html2rss/config/validator.rb +1 -0
- data/lib/html2rss/config.rb +2 -2
- data/lib/html2rss/html_extractor/heading_extractor.rb +50 -0
- data/lib/html2rss/html_extractor/id_generator.rb +67 -0
- data/lib/html2rss/html_extractor/semantic_anchor_candidates.rb +4 -18
- data/lib/html2rss/html_extractor/semantic_containers.rb +28 -3
- data/lib/html2rss/html_extractor/text_extractor.rb +77 -0
- data/lib/html2rss/html_extractor.rb +78 -61
- data/lib/html2rss/html_navigator.rb +17 -0
- data/lib/html2rss/rendering.rb +2 -2
- data/lib/html2rss/request_service/local_file_strategy.rb +29 -0
- data/lib/html2rss/request_service/puppet_commander.rb +4 -0
- data/lib/html2rss/request_service.rb +2 -1
- data/lib/html2rss/selectors/extractors/attribute.rb +1 -1
- data/lib/html2rss/selectors/extractors/href.rb +1 -1
- data/lib/html2rss/selectors/extractors/html.rb +1 -1
- data/lib/html2rss/selectors/extractors/static.rb +1 -1
- data/lib/html2rss/selectors/extractors/text.rb +1 -1
- data/lib/html2rss/selectors/post_processors/sanitize_html.rb +8 -1
- data/lib/html2rss/selectors.rb +9 -4
- data/lib/html2rss/url.rb +26 -13
- data/lib/html2rss/version.rb +1 -1
- data/lib/html2rss.rb +28 -6
- data/schema/html2rss-config.schema.json +20 -2
- metadata +7 -2
data/lib/html2rss/url.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'addressable/uri'
|
|
4
4
|
require 'cgi'
|
|
5
|
+
require 'nokogiri'
|
|
5
6
|
|
|
6
7
|
module Html2rss
|
|
7
8
|
##
|
|
@@ -55,10 +56,9 @@ module Html2rss
|
|
|
55
56
|
# @return [Url, nil] the sanitized URL, or nil if no valid URL found
|
|
56
57
|
def self.sanitize(raw_url)
|
|
57
58
|
match = raw_url.to_s.match(%r{(?:(?:https?|ftp|mailto)://|mailto:)[^\s<>"]+})
|
|
58
|
-
|
|
59
|
-
return nil if url.empty?
|
|
59
|
+
return unless match
|
|
60
60
|
|
|
61
|
-
new(Addressable::URI.parse(
|
|
61
|
+
new(Addressable::URI.parse(match[0].strip).normalize)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
##
|
|
@@ -70,10 +70,9 @@ module Html2rss
|
|
|
70
70
|
def self.from_absolute(url_string)
|
|
71
71
|
return url_string if url_string.is_a?(self)
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
url
|
|
73
|
+
new(Addressable::URI.parse(url_string.to_s.strip).normalize).tap do |url|
|
|
74
|
+
raise ArgumentError, 'URL must be absolute' unless url.absolute?
|
|
75
|
+
end
|
|
77
76
|
rescue Addressable::URI::InvalidURIError
|
|
78
77
|
raise ArgumentError, 'URL must be absolute'
|
|
79
78
|
end
|
|
@@ -92,14 +91,28 @@ module Html2rss
|
|
|
92
91
|
# Url.for_channel('/relative/path')
|
|
93
92
|
# # => raises ArgumentError: "URL must be absolute"
|
|
94
93
|
def self.for_channel(url_string)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
stripped = url_string.strip
|
|
94
|
+
stripped = url_string.to_s.strip
|
|
98
95
|
return nil if stripped.empty?
|
|
99
96
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
from_absolute(stripped).tap { validate_channel_url(_1) }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
# Extracts a base URL from HTML metadata tags.
|
|
102
|
+
#
|
|
103
|
+
# @param html [String] raw HTML content
|
|
104
|
+
# @return [Url, nil] the extracted absolute URL, or nil if none is found
|
|
105
|
+
def self.extract_from_html(html)
|
|
106
|
+
doc = Nokogiri::HTML(html)
|
|
107
|
+
tags = { 'link[rel="canonical"]' => 'href', 'meta[property="og:url"]' => 'content',
|
|
108
|
+
'meta[name="twitter:url"]' => 'content', 'base[href]' => 'href' }
|
|
109
|
+
tags.each do |sel, attr|
|
|
110
|
+
val = doc.at_css(sel)&.[](attr).to_s.strip
|
|
111
|
+
return from_absolute(val) unless val.empty?
|
|
112
|
+
rescue ArgumentError
|
|
113
|
+
next
|
|
114
|
+
end
|
|
115
|
+
nil
|
|
103
116
|
end
|
|
104
117
|
|
|
105
118
|
##
|
data/lib/html2rss/version.rb
CHANGED
data/lib/html2rss.rb
CHANGED
|
@@ -52,6 +52,8 @@ module Html2rss
|
|
|
52
52
|
FeedPipeline.new(raw_config).to_json_feed
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# rubocop:disable Metrics/ParameterLists
|
|
56
|
+
|
|
55
57
|
##
|
|
56
58
|
# Scrapes the provided URL and returns an RSS object.
|
|
57
59
|
#
|
|
@@ -60,9 +62,15 @@ module Html2rss
|
|
|
60
62
|
# @param items_selector [String, nil] optional selector hint for item extraction
|
|
61
63
|
# @param max_redirects [Integer, nil] optional redirect limit override
|
|
62
64
|
# @param max_requests [Integer, nil] optional request budget override
|
|
65
|
+
# @param local_file_path [String, nil] optional local HTML file path
|
|
63
66
|
# @return [RSS::Rss] generated RSS feed
|
|
64
|
-
def self.auto_source(url,
|
|
65
|
-
|
|
67
|
+
def self.auto_source(url,
|
|
68
|
+
strategy: :auto,
|
|
69
|
+
items_selector: nil,
|
|
70
|
+
max_redirects: nil,
|
|
71
|
+
max_requests: nil,
|
|
72
|
+
local_file_path: nil)
|
|
73
|
+
feed(build_auto_source_config(url:, strategy:, items_selector:, max_redirects:, max_requests:, local_file_path:))
|
|
66
74
|
end
|
|
67
75
|
|
|
68
76
|
##
|
|
@@ -73,11 +81,20 @@ module Html2rss
|
|
|
73
81
|
# @param items_selector [String, nil] optional selector hint for item extraction
|
|
74
82
|
# @param max_redirects [Integer, nil] optional redirect limit override
|
|
75
83
|
# @param max_requests [Integer, nil] optional request budget override
|
|
84
|
+
# @param local_file_path [String, nil] optional local HTML file path
|
|
76
85
|
# @return [Hash] JSONFeed-compliant hash
|
|
77
|
-
def self.auto_json_feed(url,
|
|
78
|
-
|
|
86
|
+
def self.auto_json_feed(url,
|
|
87
|
+
strategy: :auto,
|
|
88
|
+
items_selector: nil,
|
|
89
|
+
max_redirects: nil,
|
|
90
|
+
max_requests: nil,
|
|
91
|
+
local_file_path: nil)
|
|
92
|
+
json_feed(build_auto_source_config(url:, strategy:, items_selector:, max_redirects:, max_requests:,
|
|
93
|
+
local_file_path:))
|
|
79
94
|
end
|
|
80
95
|
|
|
96
|
+
# rubocop:enable Metrics/ParameterLists
|
|
97
|
+
|
|
81
98
|
# rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
82
99
|
class << self
|
|
83
100
|
##
|
|
@@ -125,12 +142,17 @@ module Html2rss
|
|
|
125
142
|
class << self
|
|
126
143
|
private
|
|
127
144
|
|
|
128
|
-
def build_auto_source_config(url:, strategy:, items_selector:, max_redirects:, max_requests:)
|
|
129
|
-
Config.auto_source_config(
|
|
145
|
+
def build_auto_source_config(url:, strategy:, items_selector:, max_redirects:, max_requests:, local_file_path: nil) # rubocop:disable Metrics/ParameterLists
|
|
146
|
+
config = Config.auto_source_config(
|
|
130
147
|
url:,
|
|
131
148
|
items_selector:,
|
|
132
149
|
request_controls: shortcut_request_controls(strategy:, max_redirects:, max_requests:)
|
|
133
150
|
)
|
|
151
|
+
if local_file_path
|
|
152
|
+
config[:request] ||= {}
|
|
153
|
+
config[:request][:local_file_path] = local_file_path
|
|
154
|
+
end
|
|
155
|
+
config
|
|
134
156
|
end
|
|
135
157
|
|
|
136
158
|
def shortcut_request_controls(strategy:, max_redirects:, max_requests:)
|
|
@@ -153,6 +153,12 @@
|
|
|
153
153
|
"not": {
|
|
154
154
|
"type": "null"
|
|
155
155
|
}
|
|
156
|
+
},
|
|
157
|
+
"fallback_anchorless": {
|
|
158
|
+
"type": "boolean",
|
|
159
|
+
"not": {
|
|
160
|
+
"type": "null"
|
|
161
|
+
}
|
|
156
162
|
}
|
|
157
163
|
},
|
|
158
164
|
"required": []
|
|
@@ -179,6 +185,12 @@
|
|
|
179
185
|
"type": "null"
|
|
180
186
|
},
|
|
181
187
|
"exclusiveMinimum": 0
|
|
188
|
+
},
|
|
189
|
+
"fallback_anchorless": {
|
|
190
|
+
"type": "boolean",
|
|
191
|
+
"not": {
|
|
192
|
+
"type": "null"
|
|
193
|
+
}
|
|
182
194
|
}
|
|
183
195
|
},
|
|
184
196
|
"required": []
|
|
@@ -222,12 +234,14 @@
|
|
|
222
234
|
"enabled": true
|
|
223
235
|
},
|
|
224
236
|
"semantic_html": {
|
|
225
|
-
"enabled": true
|
|
237
|
+
"enabled": true,
|
|
238
|
+
"fallback_anchorless": true
|
|
226
239
|
},
|
|
227
240
|
"html": {
|
|
228
241
|
"enabled": true,
|
|
229
242
|
"minimum_selector_frequency": 2,
|
|
230
|
-
"use_top_selectors": 5
|
|
243
|
+
"use_top_selectors": 5,
|
|
244
|
+
"fallback_anchorless": true
|
|
231
245
|
}
|
|
232
246
|
},
|
|
233
247
|
"cleanup": {
|
|
@@ -535,6 +549,10 @@
|
|
|
535
549
|
}
|
|
536
550
|
},
|
|
537
551
|
"required": []
|
|
552
|
+
},
|
|
553
|
+
"local_file_path": {
|
|
554
|
+
"type": "string",
|
|
555
|
+
"minLength": 1
|
|
538
556
|
}
|
|
539
557
|
},
|
|
540
558
|
"required": []
|
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.
|
|
4
|
+
version: 0.22.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gil Desmarais
|
|
@@ -278,6 +278,7 @@ files:
|
|
|
278
278
|
- lib/html2rss/auto_source/cleanup.rb
|
|
279
279
|
- lib/html2rss/auto_source/scraper.rb
|
|
280
280
|
- lib/html2rss/auto_source/scraper/html.rb
|
|
281
|
+
- lib/html2rss/auto_source/scraper/html/class_clustering.rb
|
|
281
282
|
- lib/html2rss/auto_source/scraper/json_state.rb
|
|
282
283
|
- lib/html2rss/auto_source/scraper/link_heuristics.rb
|
|
283
284
|
- lib/html2rss/auto_source/scraper/microdata.rb
|
|
@@ -310,10 +311,13 @@ files:
|
|
|
310
311
|
- lib/html2rss/html_extractor.rb
|
|
311
312
|
- lib/html2rss/html_extractor/date_extractor.rb
|
|
312
313
|
- lib/html2rss/html_extractor/enclosure_extractor.rb
|
|
314
|
+
- lib/html2rss/html_extractor/heading_extractor.rb
|
|
315
|
+
- lib/html2rss/html_extractor/id_generator.rb
|
|
313
316
|
- lib/html2rss/html_extractor/image_extractor.rb
|
|
314
317
|
- lib/html2rss/html_extractor/list_candidates.rb
|
|
315
318
|
- lib/html2rss/html_extractor/semantic_anchor_candidates.rb
|
|
316
319
|
- lib/html2rss/html_extractor/semantic_containers.rb
|
|
320
|
+
- lib/html2rss/html_extractor/text_extractor.rb
|
|
317
321
|
- lib/html2rss/html_navigator.rb
|
|
318
322
|
- lib/html2rss/json_feed_builder.rb
|
|
319
323
|
- lib/html2rss/json_feed_builder/item.rb
|
|
@@ -332,6 +336,7 @@ files:
|
|
|
332
336
|
- lib/html2rss/request_service/budget.rb
|
|
333
337
|
- lib/html2rss/request_service/context.rb
|
|
334
338
|
- lib/html2rss/request_service/faraday_strategy.rb
|
|
339
|
+
- lib/html2rss/request_service/local_file_strategy.rb
|
|
335
340
|
- lib/html2rss/request_service/policy.rb
|
|
336
341
|
- lib/html2rss/request_service/puppet_commander.rb
|
|
337
342
|
- lib/html2rss/request_service/response.rb
|
|
@@ -376,7 +381,7 @@ licenses:
|
|
|
376
381
|
- MIT
|
|
377
382
|
metadata:
|
|
378
383
|
allowed_push_host: https://rubygems.org
|
|
379
|
-
changelog_uri: https://github.com/html2rss/html2rss/releases/tag/v0.
|
|
384
|
+
changelog_uri: https://github.com/html2rss/html2rss/releases/tag/v0.22.1
|
|
380
385
|
rubygems_mfa_required: 'true'
|
|
381
386
|
rdoc_options: []
|
|
382
387
|
require_paths:
|