Skip to content
On this page

Web scraping with PHP means fetching pages with the Guzzle HTTP client and extracting data with Symfony's DomCrawler component, which supports CSS selectors and XPath. This Guzzle + DomCrawler stack is the modern, correct-for-2026 approach. The older Goutte library is deprecated — its functionality was merged into Symfony's BrowserKit and HttpClient. For JavaScript-rendered pages, Symfony Panther drives a real browser.

Quick facts

HTTP clientGuzzle — the standard PHP HTTP client
HTML parsingSymfony DomCrawler (+ CssSelector) — CSS and XPath
Goutte statusDeprecated — use Symfony BrowserKit HttpBrowser instead
JavaScript pagesSymfony Panther or php-webdriver
Installcomposer require guzzlehttp/guzzle symfony/dom-crawler symfony/css-selector

Your first PHP scraper with Guzzle + DomCrawler

Install the modern stack with Composer: composer require guzzlehttp/guzzle symfony/dom-crawler symfony/css-selector. The CssSelector component lets DomCrawler accept CSS selectors (not just XPath).

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;

$client = new Client(['headers' => ['User-Agent' => 'Mozilla/5.0']]);
$html = $client->get('https://books.toscrape.com/')->getBody()->getContents();

$crawler = new Crawler($html);
$crawler->filter('article.product_pod')->each(function (Crawler $node) {
    $title = $node->filter('h3 a')->attr('title');
    $price = $node->filter('.price_color')->text();
    echo "$title | $price\n";
});

filter() takes a CSS selector and returns a new Crawler; each() iterates matches; text() reads text and attr() reads an attribute. For XPath instead of CSS, use filterXPath('//...').

The modern replacement for Goutte

Many older tutorials still teach Goutte. It is now deprecated and archived — its code was merged into Symfony. The direct replacement is Symfony\Component\BrowserKit\HttpBrowser, which adds clicking links and submitting forms on top of DomCrawler. Install with composer require symfony/browser-kit symfony/http-client:

<?php
require 'vendor/autoload.php';

use Symfony\Component\BrowserKit\HttpBrowser;
use Symfony\Component\HttpClient\HttpClient;

$browser = new HttpBrowser(HttpClient::create());
$crawler = $browser->request('GET', 'https://books.toscrape.com/');

while (true) {
    $crawler->filter('article.product_pod h3 a')->each(function ($node) {
        echo $node->attr('title') . "\n";
    });

    $next = $crawler->filter('li.next a');
    if ($next->count() === 0) break;
    $crawler = $browser->click($next->link());   // follow pagination
}

This gives you the same convenience Goutte offered — a browsing session that follows links and submits forms — using maintained Symfony components.

Scraping JavaScript-rendered pages with Panther

Guzzle and DomCrawler do not run JavaScript. For client-side-rendered pages, Symfony Panther drives a real Chrome/Firefox browser and shares the DomCrawler API you already know. Install: composer require symfony/panther.

<?php
require 'vendor/autoload.php';

use Symfony\Component\Panther\Client;

$client = Client::createChromeClient();
$client->request('GET', 'https://quotes.toscrape.com/js/');
$client->waitFor('.quote');   // wait for JS to render

$crawler = $client->getCrawler();
$crawler->filter('.quote')->each(function ($node) {
    $text = $node->filter('.text')->text();
    $author = $node->filter('.author')->text();
    echo "$author: $text\n";
});

$client->quit();

Panther uses the same selectors as DomCrawler, so moving from static to dynamic scraping is mostly swapping the client. php-webdriver is the lower-level alternative if you need direct Selenium control.

Which PHP library should you use?

LibraryTypeRuns JS?Best for
GuzzleHTTP clientNoFetching pages and APIs
Symfony DomCrawlerHTML parserNoExtraction — CSS and XPath
BrowserKit (HttpBrowser)HTTP + browsing sessionNoLinks, forms — the Goutte replacement
Symfony PantherBrowser automationYesJavaScript-rendered pages
Goutte(deprecated)NoAvoid — use BrowserKit instead

The canonical 2026 stack is Guzzle + Symfony DomCrawler, with HttpBrowser for sessions and Panther for JavaScript.

The hard part: handling anti-bot blocking

The PHP code is straightforward; anti-bot defenses are the real obstacle. Guzzle's TLS fingerprint and headers flag it as non-browser to Cloudflare, DataDome, and Akamai, and headless Panther is detectable too. DomCrawler cannot extract data from a challenge page.

Solving this means residential proxies, a real browser fingerprint, and CAPTCHA handling. A scraping API does it server-side — your PHP code posts the URL with Guzzle and parses the returned HTML with DomCrawler:

Code example

php
<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;

$client = new Client();
$resp = $client->post('https://api.your-scraping-provider.com/v1?key=YOUR_API_KEY', [
    'json' => ['cmd' => 'request.get', 'url' => 'https://example.com/protected'],
]);

$data = json_decode($resp->getBody()->getContents(), true);

// Fully rendered, unblocked HTML -- parse it with DomCrawler as usual.
$html = $data['solution']['response'];
$crawler = new Crawler($html);
echo $crawler->filter('title')->text();

Next in Scraping in every language · 6 of 8

Where readability is the selling point.

Web Scraping With Ruby: A Complete 2026 Guide

Related terms

Concept map

Concept map

How Web Scraping With PHP: A Complete 2026 Guide connects

The terms most directly tied to this one. Hover a node to see its neighbours, click to preview, drag to rearrange.

0 terms · 0 connections
You are here · Web Scraping by Language
Building map…

Frequently asked questions

What is the best library for web scraping with PHP?

The modern stack is Guzzle (HTTP client) plus Symfony DomCrawler with the CssSelector component (parsing). This pairing is maintained, supports CSS and XPath, and is the correct choice in 2026. Add Symfony BrowserKit (HttpBrowser) for sessions and forms, and Symfony Panther for JavaScript-rendered pages.

Is Goutte still used for PHP web scraping?

No — Goutte is deprecated and archived. Its functionality was merged into Symfony, and the direct replacement is Symfony\Component\BrowserKit\HttpBrowser (built on symfony/browser-kit and symfony/http-client). Tutorials that still lead with Goutte are out of date; use Guzzle + DomCrawler, with HttpBrowser when you need a browsing session.

Can PHP scrape JavaScript-rendered websites?

Yes. Guzzle and DomCrawler only see server HTML, so for JavaScript-rendered pages use Symfony Panther, which drives a real Chrome or Firefox browser and shares the DomCrawler selector API. php-webdriver is a lower-level Selenium alternative. You can also call the page’s underlying JSON API directly with Guzzle.

Why does my PHP scraper get blocked, and how do I fix it?

Set a realistic User-Agent, throttle requests, and rotate residential proxies. For sites behind major anti-bot systems you also need a browser-grade TLS fingerprint , which Guzzle cannot provide on its own. Routing those requests through a scraping API that handles proxies, fingerprinting, and challenges server-side is the most reliable fix.

Last updated: 2026-06-08