Drivers

Drivers

Larascraper has two engines. You choose one with ->driver(...) on the fetch chain, with the $driver class property, or per call with with(driver: ...).

Driver Renders JS Actions Speed Needs Node and Chrome
browser (default) yes yes slower yes
http no no fast no
// Browser (Puppeteer), the default:
$this->scrape('https://shop.com/bikes/4')->run();

// Plain HTTP, no browser:
$this->scrape('https://shop.com/bikes/4')->driver('http')->run();

Make a scraper HTTP-only once, for every fetch it performs:

class PriceApiScraper extends Scraper
{
    protected string $driver = 'http';
}

Or decide from the outside, without touching the class:

BikeScraper::with(driver: 'http')->run($url);

Both drivers share the same chain, the same terminals and the same ScraperResponse, so handle() keeps its shape either way.

Choosing between them

The browser is not the safe default just because it always works. It is a whole Chromium per run, and the difference is not marginal. Measured against the same page and the same proxy: 1.6 s through Chromium, 0.10 s to 0.38 s through plain HTTP, four to fifteen times faster, with no Node dependency on that path.

Reach for http when the page arrives complete from the server, and that is more often than it looks:

  • APIs, feeds, sitemaps, anything JSON or XML.
  • Direct file URLs. A PDF at a plain URL does not need a browser to download.
  • Server rendered pages, including most of what modern frameworks emit. A Next.js page ships its data inside a __NEXT_DATA__ script tag in the initial HTML, so a single regex over an HTTP response gets you a typed JSON object and never touches Chrome. Nuxt does the same with __NUXT_DATA__.

Reach for browser when the content genuinely is not in the response body:

  • The markup you need is written by JavaScript after load.
  • You must interact first: a cookie wall, a search form, a control that repaints the page, infinite scroll.
  • You need to capture a file that a click produces, or read a viewer that never exposes a plain URL.

Check before you assume. Fetch the page with ->driver('http') and search the HTML for a value you know is on the page. If it is there, the browser is buying you nothing.

Mixing them in one scraper

The driver is a property of the fetch, not of the scraper, so one handle() can use both. Drive the page with the browser where you have to, then pull the resulting URLs over HTTP:

protected function handle(string $listUrl): array
{
    $urls = $this->scrape($listUrl)          // browser: the list is behind a control
        ->select('#year', '2026')
        ->waitForSelector('a.result')
        ->crawl('a.result')
        ->texts();

    return collect($urls)
        ->map(fn ($url) => $this->scrape($url)->driver('http')->run()->data)   // http: fast
        ->all();
}

What each driver refuses

The two drivers are not interchangeable, and the package tells you rather than failing quietly.

Actions need the browser. Combining ->driver('http') with click(), type(), select(), waitForSelector() or any other page action throws a LogicException. There is no page to act on.

Request options need HTTP. method(), body(), asJson(), asForm() and cookies() throw on the browser driver, which navigates like a real browser instead. See Requests and parameters.

Concurrency needs HTTP. A Spider's pool() overlaps work through Http::pool(), so only the http driver is actually pooled. Browser items still run, one at a time. See Spiders and sessions.

The shared cookie jar needs HTTP. Each Puppeteer run is an isolated browser, so the Session is a documented no-op on that driver.

User agents differ too

The two drivers derive their user agent differently, and for a good reason: the browser driver has a real Chrome to ask, and the HTTP driver does not. That is covered in Configuration, along with the full precedence table.

One thing worth knowing before you go looking for a browser: a 403 is very often about the user agent alone. Measured against a site that blocks scrapers, all over plain HTTP: no user agent gives 403, curl's default gives 403, and a normal Chrome user agent gives 200 with the complete document. No fingerprinting involved. Try that before paying fifteen times the cost for a headless browser.