Module: Pikuri::Tool::Scraper
- Defined in:
- lib/pikuri/tool/scraper.rb
Overview
HTTP side of the web tools (WEB_SCRAPE and FETCH): GET the
URL with a real-browser User-Agent, follow redirects, and hand the body to
Extractor.extract with the response Content-Type as the hint.
HTML/XHTML renders via Extractor::HTML, other text/* passes through
verbatim, plug-in extractors extend the set (with pikuri-pdf,
application/pdf extracts — by header or %PDF- magic, so a lying header
still works); remaining types raise FetchError so the LLM observes the
failure rather than an empty rendering.
Split into a thin HTTP fetch (Scraper.fetch) and the extraction wrapper (Scraper.visit) so tests drive each in isolation and Fetch can reuse the HTTP half. Nothing here knows about the LLM — the wrapping tools own caching, truncation, and turning the result into an observation.
Defined Under Namespace
Classes: FetchError, Fetched
Constant Summary collapse
- USER_AGENT =
Returns User-Agent sent with each request; many sites reject a missing or obviously-bot UA.
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' \ '(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
- ACCEPT =
Returns
Acceptheader, so content-negotiating servers hand back something usable: HTML first, thenapplication/pdf, thentext/*. 'text/html,application/xhtml+xml,application/pdf,text/*;q=0.8'- MAX_REDIRECTS =
Returns maximum HTTP redirects to follow before giving up.
5- OPEN_TIMEOUT =
Returns connect timeout (s) for the Faraday request.
10- READ_TIMEOUT =
Returns read timeout (s) for the Faraday request.
20- ERROR_BODY_EXCERPT =
Returns max chars of an error response body in a FetchError message — enough to show what came back (a Cloudflare/WAF page) without flooding the observation.
200
Class Method Summary collapse
-
.extract(fetched) ⇒ String
Render a Fetched response as Markdown via Extractor.extract, re-raising both extraction failure modes as FetchError.
-
.fetch(url, limit: MAX_REDIRECTS) ⇒ Fetched
Download
url, manually following up to MAX_REDIRECTS redirects. -
.visit(url) ⇒ String
Fetch
urland render its main content as Markdown.
Class Method Details
.extract(fetched) ⇒ String
Render a Fetched response as Markdown via Extractor.extract,
re-raising both extraction failure modes as FetchError. The
content-type is passed verbatim (including "" for a missing header,
which matches no text arm — a body without transport metadata is
refused, not sniffed; only a strong magic sniff like %PDF- overrides).
77 78 79 80 81 82 83 |
# File 'lib/pikuri/tool/scraper.rb', line 77 def self.extract(fetched) Pikuri::Extractor.extract(StringIO.new(fetched.body), content_type: fetched.content_type) rescue Pikuri::Extractor::Unsupported raise FetchError, "unsupported content-type #{fetched.content_type.inspect} for #{fetched.url}" rescue Pikuri::Extractor::Error => e raise FetchError, e. end |
.fetch(url, limit: MAX_REDIRECTS) ⇒ Fetched
Download url, manually following up to MAX_REDIRECTS redirects. All
recoverable failures (HTTP 4xx/5xx, Faraday::Error, exhausted redirect
budget, 3xx without Location) surface as FetchError. Error bodies
are trimmed to ERROR_BODY_EXCERPT so a challenge page doesn't dump
kilobytes into the observation.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/pikuri/tool/scraper.rb', line 96 def self.fetch(url, limit: MAX_REDIRECTS) raise FetchError, "too many redirects fetching #{url}" if limit.zero? response = begin Faraday.new(request: { open_timeout: OPEN_TIMEOUT, timeout: READ_TIMEOUT }).get(url) do |req| req.headers['User-Agent'] = USER_AGENT req.headers['Accept'] = ACCEPT end rescue Faraday::Error => e raise FetchError, "#{e.class.name.split('::').last} fetching #{url}: #{e.}" end case response.status when 200..299 Fetched.new(body: response.body, content_type: normalize_content_type(response.headers['content-type']), url: url) when 300..399 location = response.headers['location'] raise FetchError, "HTTP #{response.status} from #{url} with no Location header" if location.nil? || location.empty? fetch(URI.join(url, location).to_s, limit: limit - 1) else raise FetchError, "HTTP #{response.status} fetching #{url}: #{excerpt(response.body)}" end end |
.visit(url) ⇒ String
Fetch url and render its main content as Markdown. No caching — every
call hits the network (wrap it to memoize; WebScrape.visit
does). The output is +strip+'d (leading/trailing blank lines are common
from PDF page-feeds and trailing newlines) but interior whitespace is
preserved (paragraph breaks and code indentation are load-bearing).
64 65 66 |
# File 'lib/pikuri/tool/scraper.rb', line 64 def self.visit(url) extract(fetch(url)).strip end |