DOM Tree parsing con PHP: phpQuery
Quando ho scritto questo articolo, il 18 dicembre 2009, jQuery era da poco diventato la libreria JavaScript dominante per la manipolazione del DOM lato browser. Il suo motore di selezione (Sizzle CSS Selector Engine) aveva trasformato il modo in cui gli sviluppatori frontend scrivevano codice, e non c'era programmatore PHP serio che, dopo aver provato la sintassi $('selector') in jQuery, non si chiedesse se fosse possibile portare la stessa potenza espressiva sul server, dentro script PHP che dovessero estrarre dati da pagine HTML, fare scraping, automatizzare data mining.
La risposta del 2009 si chiamava phpQuery, una libreria open source rilasciata su Google Code da Tobiasz Cudnik che permetteva di usare i selettori jQuery in PHP per navigare e manipolare il DOM di documenti HTML scaricati da remoto. L'articolo originale documentava un esempio operativo: scaricare la homepage di Google Italia ed estrarre i link nel footer con tre righe di codice. Era un'eleganza che non si era mai vista prima nel mondo PHP server-side, e per qualche anno phpQuery diventò la scelta di default per molti progetti italiani di scraping editoriale, monitoraggio prezzi e automazione di interazioni con siti terzi.
Sedici anni dopo, il quadro è cambiato in modo radicale. Google Code ha cessato di esistere, phpQuery è diventato sostanzialmente non mantenuto, e l'ecosistema PHP ha sviluppato una stack alternativa molto più robusta e modulare. Questo aggiornamento documenta la storia di phpQuery come pezzo di archeologia software e descrive la stack che oggi, nel 2026, ho consolidato come standard per qualunque progetto serio di parsing HTML e scraping in PHP.
Cos'era phpQuery e perché ha avuto successo nel 2009?
phpQuery era una libreria PHP open source che riproduceva l'API di jQuery sul server-side, permettendo di lavorare su documenti HTML come si lavorava su DOM browser via jQuery. La firma del progetto era esattamente questa: prendere il modello mentale che gli sviluppatori frontend conoscevano benissimo (selettori CSS, traversing del DOM, lettura di attributi e contenuti) e renderlo disponibile a uno script PHP che girava in server-side senza bisogno di un browser.
Il codice tipico di phpQuery sembrava jQuery con minime variazioni sintattiche: la funzione globale pq() sostituiva la $() jQuery, e la sintassi di chaining era preservata. L'esempio originale dell'articolo del 2009 mostrava bene il pattern: caricare un documento HTML da URL, selezionare elementi con un selettore CSS standard (in quel caso span#footer a per i link nel footer di google.it), iterare sui risultati, leggere attributi e contenuti.
require_once 'class.phpquery.php';
$document = phpQuery::newDocumentHTML(file_get_contents('http://www.google.it'));
phpQuery::selectDocument($document);
foreach (pq('span#footer a') as $link) {
if (pq($link)->html() != 'Privacy') {
echo 'Link: href=' . pq($link)->attr('href') . '; testo=' . pq($link)->html() . PHP_EOL;
}
}Il successo di phpQuery negli anni 2009-2012 era proporzionale alla diffusione di jQuery nel frontend: ogni programmatore che imparava jQuery finiva per cercare un equivalente PHP, e phpQuery era la risposta canonica. La libreria diventò abbastanza centrale da essere referenziata nella stessa documentazione ufficiale di PHP DOM su php.net, che la indicava come "interfaccia semplice al DOM" per chi cercava un'alternativa più leggera della classe DOMDocument standard di PHP.
Perché phpQuery ha smesso di essere mantenibile come progetto?
La traiettoria di phpQuery dal 2012 al 2016 è un caso di studio interessante per chiunque dipenda da librerie open source su piattaforme che possono diventare archivi storici. Il progetto era hostato su Google Code, una piattaforma di hosting per progetti open source che Google aveva lanciato nel 2006 e che, nel marzo 2015, Google annunciò di voler chiudere entro gennaio 2016. La motivazione ufficiale era che GitHub, GitLab e Bitbucket avevano consolidato la loro posizione come piattaforme dominanti, e Google Code non aveva più giustificazione strategica.
La chiusura di Google Code non fu una sorpresa, ma molti progetti che ci abitavano da anni si trovarono in una situazione operativa difficile: maintainer originali poco attivi, community frammentata, fork sparsi su GitHub senza una autorità centrale. phpQuery seguì esattamente questa traiettoria. Il maintainer originale Tobiasz Cudnik aveva ridotto significativamente la sua attività già prima del 2015, e quando arrivò la chiusura di Google Code emersero diversi fork su GitHub (notabilmente punkave/phpQuery e coderkk/phpQuery) ciascuno con piccoli fix per bug specifici che i singoli maintainer avevano risolto per le proprie esigenze produttive. Nessuno di questi fork divenne autorità di riferimento del progetto, e nel corso del 2016-2018 lo sviluppo si è effettivamente fermato. Oggi, nel 2026, phpQuery è raggiungibile solo come archivio storico (code.google.com/archive/p/phpquery) o via uno dei fork GitHub, ma nessuno di questi è considerabile mantenuto in modo serio.
L'altro problema, indipendente dalla questione del maintainer, era tecnico. phpQuery era stato sviluppato in un'epoca in cui PHP 5.2-5.3 era lo standard, e la libreria conteneva pattern di codice (gestione globale dello stato, riferimenti agli oggetti DOM tramite callable globali) che invecchiavano male con l'evoluzione del linguaggio. PHP 7 e poi PHP 8 hanno introdotto strict typing, deprecazione di vari pattern dinamici, miglioramenti dell'engine DOM nativo che rendevano molte delle astrazioni di phpQuery o ridondanti o problematiche.
Qual è lo stack moderno per il DOM parsing in PHP nel 2026?
L'evoluzione delle ultime sei-sette anni ha consolidato uno stack composto da quattro componenti Symfony che insieme coprono ogni scenario di parsing HTML/XML in PHP, e che sono attivamente mantenuti dal team Symfony Foundation. Questa è la combinazione che applico oggi in tutti i progetti italiani che richiedono scraping serio o automazione di interazione con siti remoti.
Symfony HttpClient è il componente che gestisce le richieste HTTP. Sostituisce gli storici file_get_contents, cURL raw, e Guzzle nei progetti Symfony moderni. Supporta HTTP/2, streaming, autenticazione, retry policy, e produce response oggetti tipizzati. Si installa via composer require symfony/http-client.
Symfony BrowserKit simula il comportamento di un browser web programmaticamente. Mantiene cookie, segue redirect, gestisce form submission, history di navigazione. La classe centrale è HttpBrowser, che integra HttpClient per le richieste effettive. Questa è la classe che ha ereditato il ruolo che Goutte aveva avuto fra il 2010 e il 2023: Goutte è stato ufficialmente archiviato il 1 aprile 2023, e nelle sue ultime versioni era diventato un semplice proxy a HttpBrowser. La migrazione da Goutte è una sostituzione di una sola classe.
Symfony DomCrawler è il successore concettuale di phpQuery per la parte di traversing del DOM. La classe Crawler permette di interrogare documenti HTML/XML usando XPath o, se si installa il componente symfony/css-selector, selettori CSS in stile jQuery. La documentazione ufficiale di DomCrawler è particolarmente curata e contiene esempi diretti per tutti gli scenari comuni. Da Symfony 4.3 il componente integra automaticamente la libreria HTML5-PHP per documenti HTML5 conformi agli standard, risolvendo le inconsistenze storiche del parser DOM nativo di PHP su HTML5.
Symfony Panther è il componente che gestisce siti che richiedono rendering JavaScript. Quando il contenuto target è generato dinamicamente da React, Vue, Angular, o da qualunque altro framework SPA, BrowserKit non basta perché non esegue JavaScript. Panther usa il protocollo W3C WebDriver per controllare browser reali (Chrome, Firefox) tramite ChromeDriver o GeckoDriver. L'API di Panther è esattamente identica a quella di BrowserKit/DomCrawler, il che significa che la stessa logica di scraping può essere eseguita in due modi (lightweight via HTTP semplice o full-browser via WebDriver) cambiando solo la classe del client.
L'esempio del 2009 dell'articolo originale, riscritto con stack 2026, diventa qualcosa del genere:
use Symfony\Component\BrowserKit\HttpBrowser;
use Symfony\Component\HttpClient\HttpClient;
$browser = new HttpBrowser(HttpClient::create());
$crawler = $browser->request('GET', 'https://www.google.it');
$crawler->filter('span#footer a')->each(function ($node) {
if ($node->text() !== 'Privacy') {
printf("Link: href=%s; testo=%s\n", $node->attr('href'), $node->text());
}
});L'API è asciutta, type-hinted, integrabile in qualunque progetto Symfony o Laravel moderno, mantenibile a lungo termine. La dipendenza dichiarata in composer.json è "symfony/browser-kit": "^7.0", "symfony/dom-crawler": "^7.0", "symfony/css-selector": "^7.0", "symfony/http-client": "^7.0", tutte versioni attivamente sviluppate.
Per chi cerca librerie con sintassi più strettamente jQuery-like e indipendenti dall'ecosistema Symfony, esistono alternative oneste come HtmlPageDom, QueryPath, FluentDOM e DomQuery. Sono progetti più piccoli, talvolta gestiti da singoli maintainer, e portano un trade-off: sintassi più familiare a chi viene da jQuery, ma minori garanzie di mantenimento a lungo termine rispetto allo stack Symfony. La scelta dipende dal contesto. Per progetti che hanno già Symfony o Laravel come framework, lo stack ufficiale è la scelta razionale. Per script standalone dove la jQuery-like è più importante della massima robustezza, queste alternative restano valide.
Quando usare Panther invece di BrowserKit?
La distinzione è importante perché Panther costa significativamente di più in termini di risorse: avvia un browser reale, consuma memoria nell'ordine dei centinaia di MB per istanza, è più lento di un fattore 10-50× rispetto a una richiesta HTTP semplice. Va usato solo quando necessario.
Le tre situazioni dove serve davvero Panther sono: siti SPA dove il contenuto target è generato da JavaScript dopo il caricamento iniziale (la maggior parte dei portali aziendali moderni); siti con anti-bot protection che validano comportamenti browser-like (rendering canvas, esecuzione di codice cripto-validato lato client); workflow che richiedono interazione (click, scroll infiniti, form complessi con validazione JavaScript). In tutti gli altri scenari, BrowserKit sopra HttpClient è la scelta giusta perché è più veloce, più economico, più affidabile.
Una pratica che applico nei progetti di scraping che gestisco è dimostrare prima il caso d'uso con BrowserKit e passare a Panther solo se BrowserKit fallisce per limiti tecnici reali. Sembra ovvio, ma in molti progetti vedo iniziare con Panther per inerzia ("usiamo il browser così non ci sono sorprese") e finire con scraping che costa cento volte più del necessario in tempo CPU e RAM.
Quando Panther è effettivamente necessario, il setup richiede ChromeDriver o GeckoDriver installato sul server, e va prevista la gestione delle istanze browser (avvio, attesa di rendering completo via condizioni esplicite anziché sleep arbitrari, chiusura ordinata anche in caso di eccezione). Le pratiche operative che applico per Panther in produzione includono: pool di sessioni browser persistenti per evitare l'avvio cold di ogni richiesta, timeout aggressivo sulle attese di elementi, screenshot automatici nei casi di fallimento per debugging post-mortem, isolamento Docker dell'ambiente browser per evitare side-effect tra job. Il costo operativo è non banale, ma ben strutturato è gestibile e affidabile.
Cosa cambia rispetto al 2009 nel parsing PHP serio?
Tre cose sono cambiate in modo strutturale tra il 2009 di phpQuery e il 2026 di BrowserKit/DomCrawler/Panther. Prima cosa: la composabilità. phpQuery era un'unica libreria monolitica che faceva tutto. Lo stack moderno è quattro componenti separati che si combinano in funzione del bisogno, ognuno con responsabilità specifica e mantenimento indipendente.
Seconda cosa: le aspettative legali e di compliance. Nel 2009 lo scraping di siti terzi era un'area grigia poco regolamentata. Nel 2026, sotto GDPR per dati personali, sotto regolamenti settoriali (PSD2 per dati bancari, IVASS per dati assicurativi), sotto le sentenze recenti che chiariscono i limiti del riutilizzo di dati pubblicamente accessibili, e sotto il Digital Services Act europeo, l'approccio legalmente sicuro richiede sempre: rispetto del file robots.txt, rate limiting compatibile con la capacità del server target, identificazione tramite User-Agent dichiarato, conservazione dei log delle estrazioni per accountability, valutazione caso per caso del titolo legittimo al riutilizzo dei dati estratti.
Terza cosa: l'integrazione di feedback nei sistemi moderni. Nel 2009 lo scraping era principalmente "una tantum o cron", oggi è frequentemente parte di pipeline più complesse: scraping → normalizzazione → ingestione in vector database → indicizzazione semantica → uso da parte di agenti AI. Le pratiche moderne di scraping si integrano con osservabilità (OpenTelemetry su ogni richiesta), con governance dei dati (lineage tracking), con monitoring di qualità (anomaly detection sui pattern di estrazione). Per chi sta strutturando questo tipo di pipeline integrata, ho documentato il pattern di agente AI per analisi di codebase PHP legacy con tool use che applica la stessa filosofia all'analisi di codice anziché di HTML, ma il framework concettuale è il medesimo.
Le risorse autorevoli che continuo a consultare per il dominio del DOM parsing in PHP sono la documentazione ufficiale del componente DomCrawler di Symfony, il manuale ufficiale dell'estensione DOM di PHP su php.net, e la documentazione di Panther sul repository GitHub di Symfony. Sono le tre pagine che ogni programmatore PHP che fa scraping serio dovrebbe avere come bookmark fissi.
Per chi sta valutando come strutturare un progetto di automazione che interagisce con sistemi terzi via HTTP (scraping informativo, monitoring competitivo, sincronizzazione di cataloghi, automazione di workflow inter-aziendali), il mio profilo professionale include esperienze concrete su pipeline di estrazione dati robuste, con particolare attenzione agli aspetti di compliance GDPR, rate limiting etico, e integrazione in architetture moderne con messaging asincrono e osservabilità. La differenza fra uno script di scraping che funziona e una pipeline di scraping affidabile in produzione è quasi tutta nel layer operativo: gestione degli errori HTTP transitori con backoff esponenziale, rotazione di User-Agent, persistenza dello stato di estrazione, normalizzazione dei dati estratti, monitoring continuo della qualità dell'output. Se il tuo progetto richiede questo tipo di robustezza, contattami direttamente per inquadrare un'architettura adeguata fin dall'inizio. Ricostruire scraping fragile dopo che è andato in produzione costa significativamente più che progettarlo bene la prima volta.