ask-web-fetch 0.5.4 → 0.5.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9126d245115b48e3be3d0f08166ae1bdae5d917d9bc84603c90b99a81ada84d
4
- data.tar.gz: 5a6b70c34ab86157f850a5158838efc94361c46032eb184b8f611558150a6590
3
+ metadata.gz: db21a4a35495ff89b8b91b769d4a15743da85beb961a5ddbbc430bdde37988f5
4
+ data.tar.gz: 61fb64631bbfe1d478b3b6ed6587de8e5b9b3890dd5348924ca574929f85acb7
5
5
  SHA512:
6
- metadata.gz: 31f43065058dc40f29aeeced52b8d11c900c2e7a49b7bbf41cf9dd090a3b0b8ccd09507c2df4596ff73ef60a134828410005d4c19c2c4ae6e326a3f4abcb4d62
7
- data.tar.gz: 94d089feccbc51350c7c0f7ebf06636ca53246c3e960dcb2c9aa8ca820bc96a2a1c3e2ebbc2b29a1faacbcea906388f2c56ad9faae4f7cb4a148967b5eb5b7a8
6
+ metadata.gz: f53d538cf613c02e1411c9f09901c3b93db98c51c33d0d3dfca2d7ea58420f9e463f074c1f2e9a6c97da59023037abcfce59d757ef3ab689eada87fc6155939d
7
+ data.tar.gz: fde5f3000a2ec77294d1bceec3484d9d4ec68b3b3d0b91d8e29083b3bb711f31b391092a1cd544b329858ffb510e116d39c39ed4a23323c110af133db6cef928
@@ -3,6 +3,7 @@
3
3
  require 'ferrum'
4
4
  require 'json'
5
5
  require 'net/http'
6
+ require 'set'
6
7
  require 'uri'
7
8
  require_relative '../backend'
8
9
  require_relative '../content_filter'
@@ -80,6 +81,13 @@ module Ask
80
81
 
81
82
  attr_writer :challenge_timeout, :poll_interval
82
83
 
84
+ # Domains already given a warm pass this process — a failed warm
85
+ # (DataDome-class wall) is not re-paid at CHALLENGE_TIMEOUT on
86
+ # every fetch of that domain.
87
+ def warmed_domains
88
+ @warmed_domains ||= Set.new
89
+ end
90
+
83
91
  # A shared browser, built once and reused. Ferrum is loaded lazily
84
92
  # so consumers who never hit this backend pay nothing for it.
85
93
  def browser
@@ -153,6 +161,30 @@ module Ask
153
161
  raise FetchError, 'ferrum gem unavailable' unless self.class.browser
154
162
 
155
163
  page = self.class.browser.create_page
164
+ fetch_attempt(page, url)
165
+ rescue Ferrum::TimeoutError, Ferrum::ProcessTimeoutError, Ferrum::DeadBrowserError => e
166
+ raise TimeoutError, "#{e.class}: #{e.message}"
167
+ rescue Ferrum::StatusError => e
168
+ raise FetchError, "browser could not load #{url}: #{e.message}"
169
+ rescue Ferrum::Error => e
170
+ raise ServerError, "browser #{e.class}: #{e.message}"
171
+ rescue Errno::ECONNREFUSED, SocketError => e
172
+ raise TimeoutError, "browser connection #{e.class}: #{e.message}"
173
+ ensure
174
+ page&.close
175
+ end
176
+
177
+ # One fetch of +url+ on the page, with the warm-and-retry pass: a
178
+ # challenge page means this domain hasn't issued the profile a
179
+ # clearance cookie yet. Visiting the DOMAIN ROOT first (where a
180
+ # managed challenge auto-solves for a trusted browser) earns the
181
+ # cookie — pinned to this browser + IP and persisted in the
182
+ # profile — then the URL is retried once. Subsequent fetches for
183
+ # the same domain find the cookie and never warm again. Bounded:
184
+ # one warm per domain per process (warmed_domains), one retry per
185
+ # fetch — a still-challenged URL fails as before, never wedging
186
+ # the queue on a DataDome-class wall.
187
+ def fetch_attempt(page, url, warmed: false)
156
188
  page.go_to(url)
157
189
  wait_for_challenge(page)
158
190
  wait_for_idle(page)
@@ -161,23 +193,42 @@ module Ask
161
193
  raise FetchError, "got #{status} at #{url}" if status && status >= 400
162
194
 
163
195
  body = page.body
164
- raise FetchError, "challenge page at #{url}" if challenge_page?(body)
196
+ if challenge_page?(body)
197
+ raise FetchError, "challenge page at #{url}" if warmed
198
+ raise FetchError, "challenge page at #{url}" unless warm_domain(page, url)
199
+
200
+ return fetch_attempt(page, url, warmed: true)
201
+ end
165
202
 
166
203
  result = Markdown.generate(body, base_url: url, filter: self.class.content_filter)
167
204
  result[:outlinks] = outlink_urls(body, url)
168
205
  raise EmptyContentError, "no readable content at #{url}" unless usable_content?(result[:content])
169
206
 
170
207
  result
171
- rescue Ferrum::TimeoutError, Ferrum::ProcessTimeoutError, Ferrum::DeadBrowserError => e
172
- raise TimeoutError, "#{e.class}: #{e.message}"
173
- rescue Ferrum::StatusError => e
174
- raise FetchError, "browser could not load #{url}: #{e.message}"
175
- rescue Ferrum::Error => e
176
- raise ServerError, "browser #{e.class}: #{e.message}"
177
- rescue Errno::ECONNREFUSED, SocketError => e
178
- raise TimeoutError, "browser connection #{e.class}: #{e.message}"
179
- ensure
180
- page&.close
208
+ end
209
+
210
+ # Earns the domain's clearance cookie: navigates to the domain
211
+ # root (the challenge JS runs there, not on the deep URL), waits
212
+ # for the managed challenge to auto-solve, and lets the cookie
213
+ # land in the profile. Returns true when the domain is now warm.
214
+ # Domains already attempted this process are skipped — a failed
215
+ # warm is not re-paid at CHALLENGE_TIMEOUT per fetch.
216
+ def warm_domain(page, url)
217
+ domain = URI(url).host
218
+ return false if self.class.warmed_domains.include?(domain)
219
+
220
+ self.class.warmed_domains << domain
221
+ page.go_to(domain_root(url))
222
+ wait_for_challenge(page)
223
+ wait_for_idle(page)
224
+ true
225
+ rescue Ferrum::Error, URI::InvalidURIError
226
+ false
227
+ end
228
+
229
+ def domain_root(url)
230
+ uri = URI(url)
231
+ "#{uri.scheme}://#{uri.host}"
181
232
  end
182
233
 
183
234
  private
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  module WebFetch
5
- VERSION = '0.5.4'
5
+ VERSION = '0.5.5'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-web-fetch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto