symfony/dom-crawler
Symfony DomCrawler makes it easy to parse and navigate HTML/XML using a fluent, CSS/XPath-friendly API. Ideal for scraping, testing responses, and extracting data from markup, with smooth integration across the Symfony ecosystem.
Install via Composer:
composer require symfony/dom-crawler
First Use Case: Extracting Links from HTML
use Symfony\Component\DomCrawler\Crawler;
// Fetch HTML (e.g., from a Laravel HTTP client or file)
$html = file_get_contents('https://example.com');
// Create a Crawler instance
$crawler = new Crawler($html);
// Extract all links
$links = $crawler->filter('a')->each(function (Crawler $node) {
return $node->attr('href');
});
Key Starting Points:
Guzzle or Illuminate\Support\Facades\Http for fetching HTML.filter(), filterXPath(), and filterHtml().Leverage method chaining for readability and maintainability:
$crawler->filter('article')->filter('.summary')->each(function (Crawler $node) {
return $node->text();
});
Extract form fields and values for scraping or testing:
$formData = $crawler->selectButton('Submit')->form()->getValues();
Fetch and parse HTML in a single flow:
use Illuminate\Support\Facades\Http;
$response = Http::get('https://example.com');
$crawler = new Crawler($response->body());
$titles = $crawler->filter('h1')->text();
Process large volumes of HTML asynchronously:
// Job: ParseHtmlJob.php
public function handle()
{
$html = $this->fetchHtml(); // From DB, API, or file
$crawler = new Crawler($html);
$data = $crawler->filter('div.product')->each(fn($node) => [
'name' => $node->filter('h2')->text(),
'price' => $node->filter('.price')->text(),
]);
// Store or process $data
}
Simulate user interactions in Laravel tests:
public function test_form_submission()
{
$crawler = new Crawler($this->get('/form-page'));
$form = $crawler->selectButton('Submit')->form();
$form['email'] = 'test@example.com';
$crawler->submit($form);
$this->assertRouteIs('dashboard');
}
Use XPath when CSS selectors are insufficient:
$nodes = $crawler->filterXPath('//div[@class="product" and contains(@id, "active")]');
Leverage HTML5 parsing for robustness:
$crawler = new Crawler($html, 'https://example.com'); // Auto-detects charset
XXE Vulnerabilities:
addXmlContent(); use addHtmlContent() for HTML.validateOnParse (as in CVE-2026-45071).Case-Sensitive Selectors:
filter('a[href*="example"]') carefully.strtolower() if needed:
$crawler->filter(strtolower($selector));
Orphaned Nodes:
filter()->count() to debug:
if ($crawler->filter('.missing-class')->count() === 0) {
// Handle missing nodes
}
Attribute Extraction:
attr() returns null if the attribute doesn’t exist. Use filter()->attr() or provide defaults:
$href = $crawler->filter('a')->attr('href', 'default-link.com');
Performance with Large Documents:
filter() early to narrow results:
// Bad: Loads everything first
$crawler->filter('body')->filter('.target');
// Good: Narrow scope early
$crawler->filter('body .target');
Inspect Nodes:
$node = $crawler->filter('.target')->first();
dump($node->html()); // View raw HTML
Log Selectors:
$selector = '.product';
$count = $crawler->filter($selector)->count();
logger()->debug("Selector '$selector' matched $count nodes");
Use each() for Iteration:
each() over loops for cleaner iteration:
$data = $crawler->filter('div.item')->each(fn($node) => [
'id' => $node->attr('id'),
'text' => $node->text(),
]);
Custom Node Filters: Create reusable filter logic:
$crawler->filter(function (Crawler $node) {
return $node->attr('data-role') === 'active';
});
Integrate with Laravel Collections: Convert results to collections for Laravel-friendly processing:
use Illuminate\Support\Collection;
$collection = collect($crawler->filter('li')->each(fn($node) => $node->text()));
Event Listeners for Crawling:
Extend Crawler for project-specific logic:
class CustomCrawler extends Crawler {
public function extractProductData() {
return $this->filter('.product')->each(fn($node) => [
'name' => $node->filter('h3')->text(),
'price' => $node->filter('.price')->text(),
]);
}
}
Charset Handling:
Crawler for proper charset detection:
$crawler = new Crawler($html, 'https://example.com');
HTML5 vs. Legacy Parsing:
$crawler = new Crawler($html, null, null, ['html5' => false]);
Memory Limits:
filter() to reduce the DOM size early:
$crawler->filter('body')->filter('.target'); // Narrow scope
How can I help you explore Laravel packages today?