Files and PDFs
Files and PDFs
When the response is a file rather than a page, you grab it in the chain with capture() and finish with the ->file() terminal, which runs the fetch and returns a CapturedFile.
From a click or a link
use EduLazaro\Larascraper\Scraper; class ReportScraper extends Scraper { protected function handle(string $pageUrl): array { $file = $this->scrape($pageUrl) ->click('a.download-pdf') ->capture('application/pdf') // grab the response the click triggers ->file(); // run the chain, return the CapturedFile $file->save(storage_path('app/report.pdf')); return ['bytes' => $file->size()]; } }
capture($expect) records the file-like responses the page produces and keeps the one matching expect, a content-type substring such as application/pdf. PDFs also match by their %PDF magic bytes. With no argument it takes the first file-like response.
The ->file() terminal returns the CapturedFile, or raises a no_file scrape failure, a ScrapeException folded into success = false, when nothing was captured.
It captures files that render inline, the browser's built-in PDF viewer being the usual case. A forced download, Content-Disposition: attachment, is not captured.
From a form
When the file only comes back from submitting a form, with its hidden fields, tokens and session, submit and capture:
->submit('form')->capture('application/pdf')->file()
submit() plus capture() is the composable pattern. The old one-call submitAndCapture() is deprecated.
From a direct URL
If the file lives at a plain URL you do not need a browser at all. The http driver downloads it directly, and a binary response is exposed through the same ->file() terminal, and on $this->request->file. Text responses (HTML, JSON, XML) still arrive as the response html.
class LawScraper extends Scraper { protected string $driver = 'http'; protected function handle(string $url): string { return $this->scrape($url)->file()->text() ?: ''; } } $text = LawScraper::run('https://example.com/law.pdf')->data;
Behind a captcha
Wrap the capture in repeatUntil(Condition::captured(), ...), solve the captcha first, and re-navigate each attempt when the site regenerates state:
use EduLazaro\Larascraper\Support\Condition; $file = $this->scrape($viewerUrl) ->repeatUntil( Condition::captured(), fn ($b) => $b ->visit($viewerUrl) // fresh state each try ->gotoAttr('object[type*="pdf"], embed[type*="pdf"]', 'data') // real URL and fresh captcha ->when( Condition::selectorExists('img[src*="captcha"]'), fn ($c) => $c->solveCaptcha('img[src*="captcha"]', 'input[name=captcha]'), ) ->submit('form') ->capture('application/pdf'), max: 8, delay: 400, ) ->file();
gotoAttr() is what handles a viewer that never exposes a link: the real document URL lives in an <object data="…"> attribute, so there is nothing to click.
Reading a PDF
$file = $this->scrape($url)->click('a.pdf')->capture()->file(); $text = $file->text(); // the PDF's text layer: fast, free if ($text === '') { // scanned PDF, no text layer $text = $file->vision('ai'); }
The CapturedFile API:
$file->text($engine = 'gs') |
Reads the existing text layer. No OCR, fast, free. Engines: gs (ghostscript), poppler (pdftotext), smalot (smalot/pdfparser). Returns '' for a scanned PDF, which is your signal to fall back. |
$file->vision($engine = 'ai') |
Rasterizes each page to an image and reads it. Engines: ai (a vision model) and tesseract. |
$file->bytes() / $file->save($path) |
The raw bytes. |
$file->contentType() / $file->size() |
Metadata. |
Try text() first, always. It is free and instant, and most PDFs published by an institution have a text layer. vision() costs money per page and should be the fallback, not the default.
System requirements
Each engine shells out to its tool, or for vision('ai') calls an OpenAI-compatible endpoint. A missing binary fails with a clear message. These are OS packages, so Composer cannot install them; add them to your image.
| Feature | Binary | Debian/Ubuntu |
|---|---|---|
text(), default gs |
gs |
apt-get install ghostscript |
text('poppler') and rasterization for vision() |
pdftotext, pdftoppm |
apt-get install poppler-utils |
vision('tesseract') |
tesseract |
apt-get install tesseract-ocr |
vision('ai') still needs poppler-utils: it rasterizes each page with pdftoppm before sending it to the model.
Configure the ai engine through config/larascraper.php with openai_key, vision_model, vision_lang and vision_dpi, or through the OPENAI_API_KEY environment variable.
A complete example
use EduLazaro\Larascraper\Scraper; use EduLazaro\Larascraper\Support\ScraperResponse; class LawScraper extends Scraper { protected function handle(string $pageUrl): ScraperResponse { $file = $this->scrape($pageUrl) ->click('a.download-pdf') ->capture('application/pdf') ->file(); // CapturedFile, or a 'no_file' failure $text = $file->text(); if ($text === '') { $text = $file->vision('ai'); } return trim($text) === '' ? $this->fail('no_text') : $this->ok(['text' => $text]); } }
The caller gets 'no_file' when nothing was captured and 'no_text' when the document was empty, which are different problems and worth telling apart.