ask-web-fetch 0.6.1 → 0.6.2
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/ask/web_fetch/backend.rb +26 -1
- data/lib/ask/web_fetch/backends/browser.rb +4 -5
- data/lib/ask/web_fetch/backends/crawl4ai.rb +6 -6
- data/lib/ask/web_fetch/backends/jina.rb +5 -6
- data/lib/ask/web_fetch/backends/local.rb +6 -10
- data/lib/ask/web_fetch/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5e7b287b5b62f821cb0b3085082b385583d3f1a7ef2dfc0816eb311931d1e218
|
|
4
|
+
data.tar.gz: 534bce458033fb046914c169b7bb7bad982f8fcd3ffb999d6feb2b495953d90c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 48e47dd345f498f1a50b6dc3ddb09a5f55078691ee34a617b02936fb9b7d639c2ff55c309f6fc2ecec2a4d067c487edd0975c983b4ed301471d50536e73e6bd0
|
|
7
|
+
data.tar.gz: 7bf1c58c8fb17a9c5575b41f8b2d44c6deb1c01a8b82b56b3d73f5d1df4c5d72fadf09ca60de1fc3995b8dc62581611f4aa50bcb4c47ceb9894f5bb6749490fc
|
|
@@ -44,7 +44,9 @@ module Ask
|
|
|
44
44
|
# Markdown.generate, backends fed pre-converted markdown (Jina,
|
|
45
45
|
# Crawl4AI) must call it explicitly so the shared noise removal
|
|
46
46
|
# and whitespace normalization apply everywhere
|
|
47
|
-
# 5.
|
|
47
|
+
# 5. run the extracted page through #guard_page! — the parked-domain
|
|
48
|
+
# and empty-content verdicts are identical in every backend
|
|
49
|
+
# 6. register the class in Ask::Tools::WebFetch.backends
|
|
48
50
|
#
|
|
49
51
|
# The tool tries each backend in order and returns the first success.
|
|
50
52
|
class Backend
|
|
@@ -92,6 +94,29 @@ module Ask
|
|
|
92
94
|
raise NotImplementedError, "#{self.class} must implement #fetch(url)"
|
|
93
95
|
end
|
|
94
96
|
|
|
97
|
+
# The shared page guard, run by EVERY backend at the same point in
|
|
98
|
+
# its flow — after extraction, before returning: a registrar parking
|
|
99
|
+
# page raises ParkedDomainError, content below the minimum raises
|
|
100
|
+
# EmptyContentError. Same verdicts, same messages, everywhere; a
|
|
101
|
+
# backend's only job is to pass the strings it has.
|
|
102
|
+
#
|
|
103
|
+
# raw_body: the raw HTML the backend saw, where it saw it (Local,
|
|
104
|
+
# Browser) — the HTML-only markers (ap:"parking", parking-lander,
|
|
105
|
+
# LANDER_SYSTEM="PW") live in scripts and assets that never survive
|
|
106
|
+
# conversion to markdown. content: what the backend would return
|
|
107
|
+
# (all four) — the prose markers survive conversion, so a backend
|
|
108
|
+
# that only ever sees rendered text (Jina, Crawl4AI) still rejects
|
|
109
|
+
# the ad.
|
|
110
|
+
#
|
|
111
|
+
# Parked is checked BEFORE the content minimum on purpose: a parking
|
|
112
|
+
# page can render above it (puncta.ai: 395c of Namecheap auction
|
|
113
|
+
# ads) and must still be rejected.
|
|
114
|
+
def guard_page!(url, content, raw_body: nil)
|
|
115
|
+
raise ParkedDomainError,
|
|
116
|
+
"parked domain at #{url} — registrar parking page, not site content" if parked_domain?(raw_body) || parked_domain?(content)
|
|
117
|
+
raise EmptyContentError, "no readable content at #{url}" unless usable_content?(content)
|
|
118
|
+
end
|
|
119
|
+
|
|
95
120
|
# --- outlinks (crawler discovery) ---
|
|
96
121
|
|
|
97
122
|
# The page's raw outlinks from HTML: every <a href> resolved against
|
|
@@ -201,13 +201,12 @@ module Ask
|
|
|
201
201
|
end
|
|
202
202
|
# Browser renders the parked page a JS redirect lands on (the
|
|
203
203
|
# server shell hands /lander to JS) — Local never sees it. The
|
|
204
|
-
# shared
|
|
205
|
-
#
|
|
206
|
-
|
|
207
|
-
|
|
204
|
+
# shared guard catches it on the rendered HTML (raw_body) and
|
|
205
|
+
# the converted content; the distinct ParkedDomainError lets the
|
|
206
|
+
# pipeline classify (never retry) it.
|
|
208
207
|
result = Markdown.generate(body, base_url: url, filter: self.class.content_filter)
|
|
209
208
|
result[:outlinks] = outlink_urls(body, url)
|
|
210
|
-
|
|
209
|
+
guard_page!(url, result[:content], raw_body: body)
|
|
211
210
|
|
|
212
211
|
result
|
|
213
212
|
end
|
|
@@ -56,12 +56,12 @@ module Ask
|
|
|
56
56
|
|
|
57
57
|
page = to_page(body, url)
|
|
58
58
|
# A registrar parking page renders fine in headless Chrome too —
|
|
59
|
-
# the shared
|
|
60
|
-
#
|
|
61
|
-
# the site's content. Crawl4AI leads
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
# the shared guard's prose markers catch it (the HTML-only
|
|
60
|
+
# markers never reach a markdown-only backend), so the ad is
|
|
61
|
+
# rejected, not returned as the site's content. Crawl4AI leads
|
|
62
|
+
# the default chain, so this guard is what keeps parked domains
|
|
63
|
+
# out of every result.
|
|
64
|
+
guard_page!(url, page[:content])
|
|
65
65
|
|
|
66
66
|
page
|
|
67
67
|
rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED,
|
|
@@ -35,18 +35,17 @@ module Ask
|
|
|
35
35
|
body = res.body.to_s
|
|
36
36
|
raise FetchError, 'challenge page from Jina' if challenge_page?(body)
|
|
37
37
|
|
|
38
|
+
content = Markdown.clean(body)
|
|
38
39
|
# Jina only sees rendered markdown — outlinks come from its
|
|
39
40
|
# links, resolved against the requested URL. Content runs
|
|
40
41
|
# through the same Markdown.clean as the converting backends,
|
|
41
42
|
# so decorative symbol noise is stripped here too; a page
|
|
42
43
|
# whose only "content" was noise falls through as empty. A
|
|
43
44
|
# registrar parking page renders fine through Jina — the
|
|
44
|
-
# shared
|
|
45
|
-
#
|
|
46
|
-
# the site's content.
|
|
47
|
-
content
|
|
48
|
-
raise ParkedDomainError, "parked domain at #{url} — registrar parking page, not site content" if parked_domain?(content)
|
|
49
|
-
raise EmptyContentError, 'empty response from Jina' unless usable_content?(content)
|
|
45
|
+
# shared guard's prose markers catch it (the HTML-only
|
|
46
|
+
# markers never reach a markdown-only backend), so the ad is
|
|
47
|
+
# rejected, not returned as the site's content.
|
|
48
|
+
guard_page!(url, content)
|
|
50
49
|
|
|
51
50
|
{ title: nil, description: nil, content: content, outlinks: markdown_outlinks(body, url) }
|
|
52
51
|
when '429'
|
|
@@ -48,16 +48,12 @@ module Ask
|
|
|
48
48
|
# Parked-domain pages are not content: the domain owner parked it
|
|
49
49
|
# with a registrar and the page is an ad for buying the domain
|
|
50
50
|
# (GoDaddy/Namecheap/Sedo parking). A content company must never
|
|
51
|
-
# store these as if they were the site.
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
if parked_domain?(body)
|
|
58
|
-
raise ParkedDomainError, "parked domain at #{url} — registrar parking page, not site content"
|
|
59
|
-
end
|
|
60
|
-
raise EmptyContentError, "no readable content at #{url}" unless usable_content?(page[:content])
|
|
51
|
+
# store these as if they were the site. The shared guard checks
|
|
52
|
+
# the raw server HTML first (parked pages are fully
|
|
53
|
+
# server-rendered — the HTML-only markers live in scripts and
|
|
54
|
+
# assets), then the content minimum, then the JS-shell
|
|
55
|
+
# completeness signal below.
|
|
56
|
+
guard_page!(url, page[:content], raw_body: body)
|
|
61
57
|
# The completeness signal: a JS-app shell whose server HTML
|
|
62
58
|
# renders little is a TRUNCATED page, not a complete one — the
|
|
63
59
|
# real content awaits client-side JS that Local cannot run.
|