Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

◍ Quasar

A zero-dependency HTML scraper for PHP

Fetch it with PulsarX, tear it apart with Quasar. A real CSS-selector engine and XPath, over the broken HTML the web actually ships.

CSS selectors · XPath · broken-HTML tolerant · fluent traversal · zero dependencies

⋆ ˚ 。 ⋆ ୨ ⋆ ˚ 。 ⋆

PHP License tests Zero deps Author


Why Quasar · Install · Quick start · Selectors · Traversal · Extraction · With PulsarX · API


◈ Features

Feature What it does
CSS engine A genuine CSS-selector → XPath compiler — combinators, attribute operators and pseudo-classes, not just getElementById.
XPath too Drop to raw XPath on the same document whenever CSS runs out of road.
Broken-HTML tolerant Built on libxml recovery mode — unclosed tags, missing <html>, real-world soup all parse.
Fluent API find(), first(), text(), attr(), parent(), children(), next() — everything chains.
Vectorised Pull texts() / attr() from a whole match set at once, or each() / map() / filter() over it.
UTF-8 clean No mojibake, no deprecated HTML-ENTITIES hack — code points survive round-trips.
Zero deps One class per file, a tiny autoloader, and optional Composer. Just PHP + ext-dom.

◍ Why Quasar?

PHP's HTML-scraping options are a tired bunch: the built-in DOMDocument is fast but XPath-only and verbose, Simple HTML DOM is abandoned and leaks memory, and Symfony DomCrawler drags a component tree behind it. Quasar is the middle path — a clean, modern, zero-dependency scraper with a real CSS engine on top of libxml's speed.

Quasar DomCrawler Simple HTML DOM Raw DOMDocument
CSS selectors ✓ compiler ✓ (needs css-selector) partial
XPath
Combinators > + ~ manual
:nth-child / :not / :contains partial manual
Broken-HTML recovery manual
Runtime dependencies none several none none
Actively maintained n/a

⬡ Installation

With Composer

composer require vxsilisk/quasar

Without Composer — require the bundled autoloader:

require __DIR__ . '/autoload.php';

Requirements: PHP ≥ 8.1 with the dom, libxml and mbstring extensions (all standard).


✷ Quick start

require __DIR__ . '/autoload.php';

$doc = Quasar::html($html);

$doc->title();                        // "Nebula — Latest Posts"
$doc->first('h1')->text();            // heading text, whitespace-collapsed
$doc->find('article a[href]')         // a QuasarList of matches
    ->attr('href');                   // -> ['/a', '/b', ...]

foreach ($doc->find('article h2 a') as $link) {
    echo $link->text(), ' -> ', $link->attr('href'), "\n";
}

find() returns a QuasarList (countable, iterable, indexable). first() returns a single QuasarNode or null — so ?-> is your friend.


◍ CSS selectors

Quasar ships a real selector compiler, not a lookup table. All of this works:

$doc->find('div.card > a[href^="https"]');   // child + attribute prefix
$doc->find('nav a.link ~ a');                 // general sibling
$doc->find('ul.tags li:not(.hot)');           // negation
$doc->find('table tr:nth-child(odd) td');     // structural pseudo
$doc->find('h2 + p.lead');                    // adjacent sibling
$doc->find('article, aside, footer');         // grouping
◇ Everything the engine understands
Group Selectors
Simple * · type · #id · .class · compound (div.a#b)
Attribute [attr] [attr=v] [attr^=v] [attr$=v] [attr*=v] [attr~=v] [attr|=v]
Combinators descendant (space) · child > · adjacent + · general sibling ~
Pseudo :first-child :last-child :only-child :empty :root :nth-child(n | odd | even | an+b) :not(...)
jQuery-style :contains("text")
Grouping a, b, c

Need something the compiler doesn't cover? Every node speaks XPath directly:

$doc->xpath('//article[@data-id]//a[contains(@href, "ex.com")]');
$node->xpath('.//span[last()]');   // scoped to the node

⟡ Traversal

Selection is scoped — find() on a node only searches its descendants:

$post = $doc->first('article.post');

$post->first('h2 a')->text();     // within this article only
$post->find('.tags li')->count();

$node->parent();      // ?QuasarNode
$node->children();    // QuasarList (elements only)
$node->next();        // next element sibling, or null
$node->prev();        // previous element sibling, or null
$node->tag();         // 'a', 'div', ...

◈ Extraction

Pull data from one node — or a whole match set — without a loop:

$node->text();                 // "Lead paragraph."   (whitespace-collapsed)
$node->text(trim: false);      // raw textContent
$node->html();                 // outer HTML
$node->innerHtml();            // inner HTML
$node->attr('href');           // attribute, or null
$node->attr('rel', 'noopener'); // with a fallback
$node->attrs();                // ['href' => '/a', 'class' => 'link', ...]
$node->classes();              // ['link', 'active']
$node->hasClass('active');     // true

$doc->find('ul.tags li')->texts();          // ['alpha', 'beta', 'gamma']
$doc->find('a')->attr('href');               // every href, in document order
$doc->find('a')->map(fn($n) => $n->text());  // any projection
$doc->find('article')->filter(fn($n) => $n->hasClass('draft'));
$doc->find('tr')->each(fn($row, $i) => print("{$i}: {$row->text()}\n"));

Page-level shortcuts:

$doc->title();    // <title>, trimmed
$doc->body();     // <body> node
$doc->links();    // every href on the page
$doc->images();   // every img src
$doc->text();     // whole-document text

⇄ With PulsarX

Quasar is the second half of the PulsarX story — one fetches like a browser, the other reads like jQuery:

require __DIR__ . '/Pulsar/autoload.php';
require __DIR__ . '/Quasar/autoload.php';

$r   = (new Pulsar())->impersonate('chrome')->get('https://news.ycombinator.com');
$doc = Quasar::html($r->getBody());

foreach ($doc->find('.titleline > a') as $story) {
    echo $story->text(), "\n";
}

❯ API reference

DocumentQuasar

Method Returns
Quasar::html(string $html) Quasar — parse an HTML string
find(string $css) QuasarList
first(string $css) ?QuasarNode
xpath(string $expr) QuasarList
title() / text() ?string / string
body() ?QuasarNode
links() / images() string[]
outerHtml() string
document() DOMDocument

ElementQuasarNode

Method Returns
find($css) / first($css) / xpath($expr) QuasarList / ?QuasarNode / QuasarList
text(bool $trim = true) string
html() / innerHtml() string
attr($name, $default = null) / hasAttr($name) ?string / bool
attrs() / classes() / hasClass($c) array / array / bool
tag() string
parent() / next() / prev() ?QuasarNode
children() QuasarList
raw() DOMNode

CollectionQuasarList (implements Countable, IteratorAggregate, ArrayAccess)

Method Returns
get($i) / first() / last() ?QuasarNode
all() / isEmpty() / count() array / bool / int
text() / texts() string / string[]
attr($name) string[]
each($fn) / map($fn) / filter($fn) self / array / self

❏ Project layout

Quasar/
├── autoload.php          # zero-dependency autoloader
├── composer.json         # classmap autoload of src/
├── example.php           # runnable demo
├── tests/run.php         # framework-free test suite
└── src/
    ├── Quasar.php            # entry point + document
    ├── QuasarNode.php        # a single element (traversal + extraction)
    ├── QuasarList.php        # a match collection (vectorised helpers)
    ├── CssToXPath.php        # the CSS-selector → XPath compiler
    └── QuasarException.php

Run php example.php for a live demo, or php tests/run.php for the test suite.


⋆ ˚ 。 ⋆ ୨ ⋆ ˚ 。 ⋆

Quasar — made by Vxsilisk · MIT License

Part of the Nebula toolkit: PulsarX (fetch) · Quasar (parse)

About

Zero-dependency HTML scraper for PHP — a real CSS-selector engine plus XPath over broken, real-world markup. The parse layer that pairs with PulsarX.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages