Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsPhantomJS does not have a documented, universal “enable gzip” switch. A reliable diagnosis is to set any required page.settings values before navigation, record the page.open result, inspect outgoing request headers with onResourceRequested, and compare the rendered document through page.content or page.plainText. A closed PhantomJS 2 issue reported an empty resource-event body alongside a Content-Encoding: gzip response, even though loading finished; that report is evidence of one case, not proof that all PhantomJS builds fail with gzip or that a confirmed fix exists. Read the archived issue for the original report.
What the evidence actually shows
PhantomJS exposes hooks for examining requests and changing their headers, but its API reference does not promise that manually sending Accept-Encoding: gzip,deflate enables decompression or repairs a broken response. The issue most often cited for this topic was opened on January 20, 2016. The reporter described PhantomJS 2 sending Accept-Encoding: gzip,deflate, receiving a response marked Content-Encoding: gzip, and seeing an empty body in a resource event. The page still reached a load-finished state. GitHub now marks the ariya/phantomjs repository archived and read-only, and the issue is closed as a duplicate without a confirmed resolution.
That distinction matters. An empty body in a resource callback does not by itself prove that the browser failed to decode the page. Conversely, a success callback does not prove that every compressed subresource was usable. You need to inspect the request, the response metadata available to your build, and the document PhantomJS actually rendered.
Run a baseline capture first
Start without changing compression headers. This gives you a control result and separates navigation failure from a gzip-specific symptom.
#1 Best Overall
- Create a page with
require('webpage').create(). - Install any settings your test requires before calling
page.open. The settings reference states that settings apply during the initial open call; changing them after navigation has started is too late for that load. - Call
page.open(url, callback)and record whether the callback reportssuccessorfail. The callback is delivered through PhantomJS’s load-finished mechanism, as documented for page.open. - When loading succeeds, print both
page.contentandpage.plainText. The former exposes the main-frame HTML/XML markup; the latter exposes text with markup removed.
var webpage = require('webpage');
var system = require('system');
var page = webpage.create();
var target = system.args[1] || 'https://example.com/';
// Put required page.settings assignments here, before page.open(target, ...).
page.open(target, function (status) {
console.log('open status: ' + status);
if (status === 'success') {
console.log('content length: ' + page.content.length);
console.log('plain text length: ' + page.plainText.length);
console.log(page.content);
} else {
console.error('Navigation failed for ' + target);
}
phantom.exit(status === 'success' ? 0 : 1);
});
Save this as baseline.js and run phantomjs baseline.js https://your-site.example/. Keep the exact PhantomJS binary, URL, timestamp, and status output with your test. Repeating the same request against the same server is more informative than changing several variables at once.
Inspect what PhantomJS sends
onResourceRequested receives request metadata and a networkRequest object. Its setHeader(key, value) method lets you change a header before the request is sent. Log the existing headers first. Do not assume that adding an Accept-Encoding value is necessary, and do not treat it as a proven decompression fix.
var webpage = require('webpage');
var system = require('system');
var page = webpage.create();
var target = system.args[1] || 'https://example.com/';
page.onResourceRequested = function (requestData, networkRequest) {
console.log('REQUEST ' + requestData.id + ' ' + requestData.method + ' ' + requestData.url);
if (requestData.headers) {
requestData.headers.forEach(function (header) {
console.log(' ' + header.name + ': ' + header.value);
});
}
// Diagnostic experiment only. Compare with a run that leaves the header alone.
// networkRequest.setHeader('Accept-Encoding', 'gzip,deflate');
};
page.open(target, function (status) {
console.log('open status: ' + status);
if (status === 'success') {
console.log('rendered markup bytes (characters): ' + page.content.length);
console.log('rendered text characters: ' + page.plainText.length);
}
phantom.exit(status === 'success' ? 0 : 1);
});
If you uncomment the header assignment, run both versions and keep all other settings identical. A changed result is a clue for further investigation, not evidence that the header is a universal solution.
Rank #2
Compare response metadata with the rendered page
The reported case is useful because it separates three observations: the request advertised gzip, the response carried a gzip encoding header, and a resource-event body was empty. Add response logging where your PhantomJS build exposes it, then compare that information with page.content and page.plainText. A resource event concerns a network resource; page.content concerns the main frame after rendering. They are not interchangeable.
var webpage = require('webpage');
var system = require('system');
var page = webpage.create();
var target = system.args[1] || 'https://example.com/';
page.onResourceRequested = function (requestData, networkRequest) {
console.log('REQUEST ' + requestData.id + ' ' + requestData.url);
};
// PhantomJS builds that provide this event can expose response headers and body details.
page.onResourceReceived = function (response) {
var encoding = 'not reported';
if (response.headers) {
response.headers.forEach(function (header) {
if (header.name.toLowerCase() === 'content-encoding') {
encoding = header.value;
}
});
}
console.log('RESPONSE ' + response.id + ' status=' + response.status +
' content-encoding=' + encoding + ' body-length=' +
(response.body ? response.body.length : 0));
};
page.open(target, function (status) {
console.log('OPEN ' + status);
console.log('MAIN FRAME MARKUP LENGTH ' + page.content.length);
console.log('MAIN FRAME TEXT LENGTH ' + page.plainText.length);
phantom.exit(status === 'success' ? 0 : 1);
});
If your build does not provide the response event or does not include a body, do not infer a failure from the missing field. Use the data that is available: request headers, the open status, and the rendered main-frame output.
Use a repeatable diagnostic script
The following script combines the useful checks into one run. It records every requested URL, prints request headers, reports response encoding when the event supplies it, and writes the rendered HTML to standard output. It deliberately leaves Accept-Encoding unchanged; add the commented experiment only when you need an A/B comparison.
var webpage = require('webpage');
var system = require('system');
var page = webpage.create();
var target = system.args[1];
if (!target) {
console.error('Usage: phantomjs gzip-diagnose.js https://host.example/');
phantom.exit(2);
}
page.onResourceRequested = function (requestData, networkRequest) {
console.log('[request ' + requestData.id + '] ' + requestData.method + ' ' + requestData.url);
(requestData.headers || []).forEach(function (header) {
console.log('[request-header] ' + header.name + ': ' + header.value);
});
// networkRequest.setHeader('Accept-Encoding', 'gzip,deflate');
};
page.onResourceReceived = function (response) {
var contentEncoding = 'not reported';
(response.headers || []).forEach(function (header) {
if (header.name.toLowerCase() === 'content-encoding') {
contentEncoding = header.value;
}
});
console.log('[response ' + response.id + '] status=' + response.status +
' encoding=' + contentEncoding +
' body=' + (response.body ? response.body.length : 0));
};
page.open(target, function (status) {
console.log('[open] ' + status);
if (status === 'success') {
console.log('[content-length] ' + page.content.length);
console.log('[text-length] ' + page.plainText.length);
console.log(page.content);
}
phantom.exit(status === 'success' ? 0 : 1);
});
Run it twice if needed: once with the server-selected headers and once with the explicit header assignment. Compare the same URL, PhantomJS build, and server response. Do not compare a cached result with a fresh result and call the difference a compression fix.
Interpret the results without overclaiming
| Observation | What it establishes | What it does not establish |
|---|---|---|
page.open returns success |
The load-finished callback reported a successful navigation. | It does not prove that every subresource decoded or that every script completed correctly. |
page.open returns fail |
Navigation failed for that run. | It does not identify gzip as the cause; DNS, TLS, server errors, scripts, or other network conditions may be involved. |
Request headers include Accept-Encoding: gzip,deflate |
That value was present in the request metadata you logged. | It does not prove the server compressed the response or that PhantomJS decoded it. |
Response metadata includes Content-Encoding: gzip |
The response event reported gzip encoding for that resource. | It does not prove the resource-event body will contain decoded text. |
Resource body is empty but page.content contains the expected markup |
The main frame rendered usable content despite the empty event body. | It does not prove all resources behaved the same way. |
| Both rendered properties are empty or incomplete | The page did not produce the expected main-frame output in that run. | It does not identify a single remedy; test the server response and build separately. |
Common failure modes and fixes
“I set the header after calling page.open.”
Move the setting or request-hook logic before navigation. The settings documentation says settings apply only during the initial open call. A late assignment cannot alter a request that has already been created.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →“The callback says success, but my logged body is empty.”
Check page.content and page.plainText before concluding that decompression failed. The archived issue describes precisely this kind of mismatch. If the main-frame output is correct, the empty network-event body may be an event-reporting limitation or a difference between the resource and the rendered document.
Rank #4
“The callback says fail.”
Remove the gzip experiment and rerun the baseline. Capture the target URL, status, request headers, and PhantomJS build. If the baseline also fails, investigate navigation independently of compression. If only the header experiment fails, compare the server’s response metadata and avoid presenting the experiment as a fix.
“Only one API or image fails.”
Log every requested URL and identify the failing resource. A successful document does not guarantee that a compressed XHR, stylesheet, font, or image was usable. Test that resource’s server response separately where possible, while continuing to use the main-frame properties to judge what the page rendered.
“The result changes between runs.”
Hold the URL, build, settings, and request headers constant. Record whether the server response was cached, redirected, or changed. A server-side variation can look like a PhantomJS compression problem, so compare response metadata before changing client headers.
Best Value
“I need a confirmed patch or version switch.”
The available material does not establish a version-specific gzip patch, a universal limitation, or an officially confirmed resolution. Treat build-to-build testing as a diagnostic method, not as a published compatibility result. The PhantomJS repository is archived and read-only, so document the exact binary used in any bug report.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Operational guidance for production captures
- Log enough to reproduce: URL, PhantomJS build, navigation status, request headers, response status and encoding when exposed, and lengths of
page.contentandpage.plainText. - Separate network evidence from rendered evidence: an event body and the main-frame DOM answer different questions.
- Keep experiments reversible: run with the server-selected headers first, then test an explicit
Accept-Encodingvalue as a comparison. - Check failure scope: determine whether the symptom affects the main document or only a subresource.
- Preserve exact conditions: server behavior, redirects, cache state, and the PhantomJS executable can all affect the observation.
For a maintained capture workflow, avoid promising gzip behavior that the PhantomJS API does not document. If you must continue using PhantomJS, make the diagnostic output part of your monitoring so a server or build change is visible rather than mistaken for a decompression fix.
Or skip the browser setup
If your goal is a clean screenshot rather than debugging PhantomJS’s network stack, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. AI agents can call its MCP tools—take_screenshot, get_page_info, and capture_pdf—from Claude, Cursor, or another MCP client.
One GET request returns PNG, JPEG, WebP, or PDF output. The API supports full-page captures with lazy images loaded, CSS-selector element captures, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper and margin controls, custom CSS and JavaScript, pre-capture clicks, selector waits, delays or network-idle waits, ad/tracker/request/resource blocking, custom headers, cookies, user agents and Authorization, timezone and geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed public-image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, an OpenAPI specification, and familiar parameter names for easier migration.
cURL (see the ScreenshotNeo API docs):
curl -G 'https://api.screenshotneo.com/v1/shot' -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get('https://api.screenshotneo.com/v1/shot', params={'access_key': 'YOUR_API_KEY', 'url': 'https://stripe.com'}, timeout=90)
open('shot.webp', 'wb').write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
const data = new Uint8Array(await res.arrayBuffer());
await Bun.write('shot.webp', data);
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; yearly billing provides two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to try the capture without a card.
Frequently Asked Questions
Can every compressed response be assumed to use gzip?
No. Confirm the actual Content-Encoding reported for the resource; a request advertising gzip does not force a server to choose it.
What is the minimum evidence to attach to a PhantomJS bug report?
Include the exact PhantomJS build, target URL, open status, logged request and response metadata, and the resulting page.content or page.plainText lengths so another person can distinguish navigation, resource, and rendering symptoms.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.

