vxsilisk/pulsarx
Composer 安装命令:
composer require vxsilisk/pulsarx
包简介
Browser-impersonating HTTP client for PHP — like curl_cffi/requests, but native. 32 TLS fingerprints, Sec-Fetch/Referer stealth, async (curl_multi), session cookies & multipart. Zero dependencies.
关键字:
README 文档
README
✦ PulsarX
A requests-style HTTP client for PHP, built on cURL
The browser-impersonating HTTP client PHP was missing. Guzzle sends requests — PulsarX sends requests that look like Chrome.
In-memory session cookies · browser impersonation · async parallel requests · multipart uploads
⋆ ˚ 。 ⋆ ୨ ⋆ ˚ 。 ⋆
Why PulsarX · Install · Quick start · Sessions · Impersonate · Anti-detection · Retries · Async · Uploads · API
✦ Features
| Feature | What it does | |
|---|---|---|
| ❍ | Session model | Cookies persist in memory across requests, scoped by domain / path / expiry — like requests.Session. No files touch disk. |
| ◈ | Impersonation | 32 version-pinned browser fingerprints (Chrome, Edge, Firefox, Safari — desktop, Android & iOS). |
| ✸ | Anti-detection | Coherent Sec-Fetch-Site + Referer chains (stealth()) and whole-profile rotation (rotate()). |
| ⟡ | Async | Parallel requests over curl_multi with a rolling concurrency window and near-zero idle CPU. |
| ❖ | Multipart | multipart/form-data and file uploads via a fluent Mime builder (on-disk or in-memory). |
| ⌗ | JSON-first | json: body param, plus Response::json(), ok() and getElapsed(). |
| ⬡ | Zero deps | One class per file, a tiny autoloader, and optional Composer. Just PHP + ext-curl. |
✦ Why PulsarX?
PHP has great HTTP clients — but none of them can look like a browser. That's the gap
PulsarX fills: it's the PHP answer to Python's curl_cffi.
| PulsarX | Guzzle | Symfony HttpClient | curl_cffi (Python) | |
|---|---|---|---|---|
| Language | PHP | PHP | PHP | Python |
| Browser impersonation | ✓ 32 targets | ✗ | ✗ | ✓ |
Anti-detection (Sec-Fetch / Referer) |
✓ | ✗ | ✗ | partial |
| Fingerprint rotation | ✓ | ✗ | ✗ | ✓ |
| Async / parallel | ✓ curl_multi |
✓ | ✓ | ✓ |
| Scoped session cookies | ✓ | ✓ | ✓ | ✓ |
| Multipart / file uploads | ✓ | ✓ | ✓ | ✓ |
| Runtime dependencies | none | several | none | C / curl-impersonate |
On stock OpenSSL the TLS layer is approximate, not a byte-exact JA3 — see Anti-detection. For an exact fingerprint, run PHP against a
curl-impersonatelibcurl (auto-detected).
⬡ Installation
With Composer
composer require vxsilisk/pulsarx
Without Composer — require the bundled autoloader:
require __DIR__ . '/autoload.php';
Requirements: PHP ≥ 8.1 with the
curlandjsonextensions.
✷ Quick start
require __DIR__ . '/autoload.php'; $s = new Pulsar(); $r = $s->get('https://example.com'); $r->getStatusCode(); // 200 $r->ok(); // true $r->getBody(); // raw body $r->json(); // decoded JSON $r->getElapsed(); // transfer time in seconds
❍ Sessions & cookies
A Pulsar instance is a session. Cookies set by the server are stored in memory
and re-sent automatically on later requests — correctly scoped by domain, path and
expiry.
$s = new Pulsar(); $s->get('https://site.com/login'); // server sets cookies $s->post('https://site.com/cart', $payload); // cookies sent automatically
Cookies are isolated by host — a cookie from shop.com is never leaked to
api.stripe.com. You can also pre-seed the jar:
$jar = new CookieJar(); $jar->add(new Cookie(name: 'session', value: 'abc123')); $s->get('https://site.com', cookie: $jar);
◈ Impersonation
Mimic a real browser's fingerprint — User-Agent, the full header set in browser order, TLS cipher list, EC curves, HTTP/2 and Brotli/ZSTD. Chainable:
$s = (new Pulsar())->impersonate('chrome131'); $r = $s->get('https://protected-site.com');
Your own headers merge on top of the profile (overriding by name, preserving order):
$s->impersonate('chrome')->get($url, headers: ['Referer: https://google.com']);
◇ All 32 targets (or call Pulsar::impersonateTargets() at runtime)
| Browser | Targets |
|---|---|
| ❯ Chrome (desktop) | chrome99 chrome110 chrome116 chrome119 chrome120 chrome124 chrome131 chrome133 chrome136 chrome142 chrome146 chrome |
| ❯ Chrome (Android) | chrome99_android chrome131_android chrome_android |
| ❯ Edge | edge99 edge101 edge131 edge |
| ❯ Firefox | firefox133 firefox135 firefox144 firefox |
| ❯ Safari (macOS) | safari153 safari170 safari180 safari260 safari |
| ❯ Safari (iOS) | safari172_ios safari180_ios safari_ios |
| ❯ Tor | tor |
Bare names like
chrome,safari,firefoxare aliases for the latest stable build.
$s->impersonate('chrome131_android'); // mobile Chrome $s->impersonate('safari172_ios'); // iOS Safari
Note
About JA3 accuracy. PulsarX runs on stock OpenSSL, so impersonation matches the HTTP layer and the TLS cipher ordering — strong, but not a byte-exact JA3/JA4 (TLS extension order, GREASE, ALPS and HTTP/2 SETTINGS require BoringSSL).
If you run PHP against a curl-impersonate
libcurl, PulsarX auto-detects the extra options (ALPS, cert compression, extension
permutation, no-server-push) and produces an exact fingerprint — no code change needed.
✸ Anti-detection
Bot detection checks coherence across layers — it catches you when something doesn't line up. PulsarX gives you two tools beyond raw impersonation, both fully working on stock OpenSSL.
Behavioural coherence — stealth()
A naive client always sends Sec-Fetch-Site: none with no Referer. A real browser
derives both from where it navigated from. Enable stealth() and PulsarX maintains that
context across the session automatically:
$s = (new Pulsar())->impersonate('chrome131')->stealth(); $s->get('https://shop.com/'); // Sec-Fetch-Site: none (direct, no Referer) $s->get('https://shop.com/cart'); // Sec-Fetch-Site: same-origin · Referer: https://shop.com/ $s->get('https://api.shop.com/data'); // Sec-Fetch-Site: same-site · Referer: https://shop.com/cart $s->get('https://other.com/'); // Sec-Fetch-Site: cross-site · Referer: https://api.shop.com/ (origin only)
The Referer honours the default strict-origin-when-cross-origin policy — full URL
within a site, origin-only across sites. Anything you set by hand always wins.
Fingerprint rotation — rotate()
Since Chrome 110, browsers randomise TLS extension order — so a single static fingerprint is itself suspicious. Rotation picks a fresh, coherent profile (UA + sec-ch-ua + TLS all in sync) on every request:
$s = (new Pulsar())->rotate(); // realistic pool of current browsers $s = (new Pulsar())->rotate(['chrome146', 'firefox144', 'safari260']); // your own pool $s = (new Pulsar())->impersonate('random'); // one random profile, fixed for the session
Important
Rotation varies whole profiles, never fields within one. Randomising the
sec-ch-ua brand order or mixing a Chrome UA with a Firefox header set is a mismatch
signal that makes you easier to flag, not harder — so PulsarX never does it.
None of this forges a byte-exact JA3/JA4 on OpenSSL, and no HTTP client clears
JavaScript challenges (Cloudflare Turnstile et al.). For hardened anti-bots you still
need a curl-impersonate libcurl or a headless browser.
⟡ Async (parallel)
Build promises with getAsync() / postAsync() / requestAsync(), then resolve a
batch with pool(). It uses curl_multi with a rolling concurrency window and
curl_multi_select, so idle CPU stays near zero while requests are in flight.
$s = new Pulsar(); $promises = [ $s->getAsync('https://api.com/a', key: 'a'), $s->getAsync('https://api.com/b', key: 'b'), $s->postAsync('https://api.com/c', json: ['x' => 1], key: 'c'), ]; $responses = $s->pool($promises, concurrency: 10); // array keyed by `key` echo $responses['a']->getStatusCode();
Prefer callbacks? Each promise resolves as soon as it finishes:
$s->getAsync($url)->then(fn(Response $r) => print($r->getStatusCode())); $s->pool($promises);
❖ Multipart & file uploads
A fluent multipart/form-data builder — PulsarX's take on curl_cffi's CurlMime:
$mime = (new Mime) ->addPart('username', data: 'andy') ->addPart('avatar', filename: 'a.png', contentType: 'image/png', localPath: '/tmp/a.png') ->addPart('inline', filename: 'note.txt', data: 'in-memory bytes'); // no temp file needed $s->post($url, $mime);
Or declaratively, from a list:
$mime = Mime::fromList([ ['name' => 'username', 'data' => 'andy'], ['name' => 'avatar', 'filename' => 'a.png', 'local_path' => '/tmp/a.png'], ]);
⌗ JSON body
$s->post($url, json: ['id' => 1, 'tags' => ['a', 'b']]); // sets Content-Type: application/json
A plain array passed as
$datais still JSON-encoded (legacy behaviour). To send multipart instead, pass aMimeor an array containing aCURLFile.
↻ Retries, params & redirects
Retries with backoff — resilient against transient network errors and 429/5xx:
$s = (new Pulsar())->retries(3, baseDelay: 0.5); // 3 tries, exponential backoff + jitter $s->retries(5, 0.5, on: [429, 503]); // customise which statuses retry
Query params — built and appended for you:
$s->get('https://api.com/search', params: ['q' => 'x y', 'page' => 2]); // -> https://api.com/search?q=x+y&page=2
Per-request timeout and redirect control:
$s->get($url, timeout: 5); // override the default 60s for this call $s->redirects(follow: true, max: 10); // session-wide redirect policy $r = $s->get($url); $r->getUrl(); // final URL after redirects $r->getRedirectCount(); // how many hops
Throw on error — opt into exceptions instead of checking ok() (sync requests):
$s->throwOnError(); try { $s->get('https://api.com/missing'); // 404 -> throws } catch (PulsarException $e) { /* ... */ }
Important
TLS verification is ON by default (secure). Disable it explicitly when a target has a broken/self-signed certificate:
$s = new Pulsar(verify: false);
⇄ Proxy
// HTTP tunnel $s->get($url, server: ['method' => 'tunnel', 'server' => 'http://1.2.3.4:8080']); // Authenticated proxy $s->get($url, server: ['method' => 'custom', 'server' => 'http://1.2.3.4:8080', 'auth' => 'user:pass']);
◇ Constructor options
Override any cURL default by passing options to the constructor:
$s = new Pulsar([ CURLOPT_TIMEOUT => 120, CURLOPT_SSL_VERIFYPEER => true, ]);
❯ API reference
HTTP methods — every method returns a Response.
All methods accept params: (query array) and timeout: (seconds).
| Sync | Async (returns Promise) |
|---|---|
get($url, $headers?, $cookie?, $server?, $params?, $timeout?) |
getAsync(..., $params?, $key?) |
post($url, $data?, $headers?, $cookie?, $server?, $json?, $params?, $timeout?) |
postAsync(..., $json?, $params?, $key?) |
put / patch / delete($url, $data?, …) |
requestAsync($method, $url, …, $key?) |
custom($url, $method, $data?, …) |
pool(array $promises, int $concurrency = 10): Response[] |
Session policy (chainable)
| Method | Effect |
|---|---|
retries($times = 3, $baseDelay = 0.5, $on = null) |
retry transport errors + given statuses with backoff |
redirects($follow = true, $max = 20) |
redirect-following policy |
throwOnError($on = true) |
throw PulsarException on 4xx/5xx (sync) |
new Pulsar($config = [], verify: true) |
TLS verification (on by default) |
Impersonation
| Method | Returns |
|---|---|
impersonate(string|Profile $target) |
$this — fixed profile ('random' picks one) |
rotate(?array $targets = null) |
$this — fresh coherent profile per request |
stealth(bool $on = true) |
$this — auto Sec-Fetch-Site + Referer chain |
clearImpersonation() |
$this |
Pulsar::impersonateTargets() |
string[] of every target |
Response
| Method | Description |
|---|---|
isSuccess() |
transport succeeded |
ok() |
status in [200, 400) |
getStatusCode() |
HTTP status code |
getBody() |
raw response body |
json($assoc = true) |
decoded JSON body |
getHeaders() |
request + response headers |
getReason() |
error message, if any |
getElapsed() |
transfer time in seconds |
getUrl() |
final URL after redirects |
getRedirectCount() |
number of redirects followed |
❏ Project layout
PulsarX/
├── autoload.php # zero-dependency autoloader
├── composer.json # classmap autoload of src/
├── example.php # runnable demo
└── src/
├── Pulsar.php # the client (session · impersonate · async)
├── Response.php # immutable response (ok / json / elapsed …)
├── Profile.php # 32 impersonation targets
├── Mime.php # multipart/form-data + file uploads
├── Promise.php # deferred async request
├── CookieJar.php # domain/path/expiry-scoped cookie jar
├── CookieJarInterface.php
├── Cookie.php
├── Helper.php # header parsing
└── PulsarException.php
Run
php example.phpto see sessions, impersonation, multipart, JSON and async in action.
⋆ ˚ 。 ⋆ ୨ ⋆ ˚ 。 ⋆
PulsarX — made by Vxsilisk · MIT License
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 1
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-06-24