Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesTo capture a page with PyQt4 and QWebKit, load the URL into a QWebPage, wait for loadFinished(bool), set the viewport, render the main QWebFrame into a QImage with QPainter, and save the image. The complete widget-less example below produces a full-frame PNG; a fixed viewport produces a browser-window-style capture instead.
The capture pipeline at a glance
Qt WebKit has two practical routes. QWebView is the convenient visible widget, while QWebPage can run without a browser widget and gives you explicit control over rendering. In both cases, the document is represented by a page and its main frame.
| Route | Use it when | Capture control |
|---|---|---|
QWebView |
Your application already embeds a browser or you need to see the page while it loads. | The widget displays the viewport; render its page or frame after loading. |
QWebPage without a widget |
You need a background or batch capture pipeline and no visible browser window. | You set the viewport, render the main frame, and choose the output image size directly. |
Neither Qt’s examples nor its API reference establish a general performance winner. Choose based on whether a widget is part of your application and how much control your capture code needs.
Save a full-page image with widget-less QWebPage
The following PyQt4-flavored program follows Qt’s documented C++ rendering sequence. It is an adaptation rather than an executed test, so verify signal and import syntax against the exact PyQt4 release installed on your system.
#1 Best Overall
- Create a
QWebPageand obtain its main frame. - Connect
loadFinishedbefore starting navigation. - When loading succeeds, set the viewport to the frame’s
contentsSize(). - Create a
QImagewith the same dimensions. - Paint the frame into that image and save it.
from PyQt4.QtCore import QUrl
from PyQt4.QtGui import QImage, QPainter
from PyQt4.QtWebKit import QWebPage
page = QWebPage()
frame = page.mainFrame()
def save_capture(ok):
if not ok:
return
page.setViewportSize(frame.contentsSize())
image = QImage(page.viewportSize(), QImage.Format_ARGB32)
painter = QPainter(image)
frame.render(painter)
painter.end()
image.save("capture.png")
page.loadFinished.connect(save_capture)
frame.load(QUrl("https://example.com/"))
The application must keep running an event loop, normally by creating a QApplication before this code and calling app.exec_(). Without the event loop, the asynchronous load cannot complete and the signal will not fire. In a real program, quit the application after image.save() returns so a batch job does not remain alive indefinitely.
Why the viewport assignment matters
setViewportSize(frame.contentsSize()) asks Qt to lay out and render the entire current frame contents. Allocate the image only after that assignment, because the image dimensions must match the viewport you intend to capture. Qt’s documented example uses this sequence for a full-content image and scales a separate copy for a thumbnail; keep the original at its rendered size unless a smaller output is your explicit goal.
Capture a fixed browser viewport
A full-page image is not always what you want. For a 1366-by-768 desktop view, set a fixed viewport and create an image with those dimensions instead of using contentsSize():
from PyQt4.QtCore import QSize
page.setViewportSize(QSize(1366, 768))
image = QImage(page.viewportSize(), QImage.Format_ARGB32)
painter = QPainter(image)
frame.render(painter)
painter.end()
image.save("viewport.png")
Viewport width can change responsive breakpoints, column wrapping, scrollbar visibility, and other layout details. A capture made at 375 pixels wide is therefore a different rendering, not merely a cropped version of a desktop page. If you need comparable results, record the viewport dimensions alongside each image.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Use QWebView when a visible widget is useful
QWebView is backed by a QWebPage and is the simpler choice for an embedded browser. Connect its page’s loadFinished(bool) signal, call load(QUrl(...)), and render the page’s main frame using the same painter-to-image approach. The widget can be shown normally or kept out of sight; the underlying page and frame remain the objects that perform the rendering.
Rank #2
from PyQt4.QtCore import QUrl, QSize
from PyQt4.QtGui import QApplication, QImage, QPainter
from PyQt4.QtWebKit import QWebView
app = QApplication([])
view = QWebView()
view.resize(1366, 768)
def capture(ok):
if not ok:
app.quit()
return
image = QImage(view.size(), QImage.Format_ARGB32)
painter = QPainter(image)
view.page().mainFrame().render(painter)
painter.end()
image.save("viewport.png")
app.quit()
view.loadFinished.connect(capture)
view.load(QUrl("https://example.com/"))
app.exec_()
For a full document with QWebView, use the page’s main frame contents size, set the page viewport to that size, then allocate the image from page.viewportSize() as in the widget-less example. Do not assume the widget’s current size automatically means full-page capture.
Loading is not the same as visual readiness
The Boolean passed to loadFinished(bool) indicates whether loading succeeded. Qt’s QWebPage documentation explicitly notes that the signal is emitted when page contents are loaded independently of script execution or page rendering. A page can therefore continue changing after the signal: JavaScript may insert content, an image may be assigned later, or an application may fetch data asynchronously.
When the page is dynamic, add a readiness strategy appropriate to that site rather than treating the signal as proof that every pixel is settled. Typical approaches are a page-specific JavaScript flag, a known element whose presence indicates completion, or a controlled delay. These are application decisions; Qt’s basic signal does not provide a universal “network idle” guarantee. If the required content is not present when render() runs, the resulting image can be valid but incomplete.
Recommended Free Tools
Frames and content that may not appear
QWebFrame represents an individual frame. The main frame owns the document you normally load, and Qt documents child frames as well. Rendering the main frame with frame.render(painter) is the documented operation; Qt states that the contents and subframes are rendered into the painter.
That API description does not promise pixel-perfect output for every modern site, plugin, cross-origin resource, delayed asset, or browser-specific effect. Treat missing content as a page or engine compatibility issue, not as evidence that the image-save call failed. Confirm the URL, inspect the loaded document, and test the exact page in the exact Qt WebKit build you deploy.
Troubleshooting common failures
The callback never runs
Most often, the process has no running QApplication event loop, or the signal was connected after navigation began. Create the application first, connect loadFinished, then call load(), and enter app.exec_().
The callback receives False
The navigation did not complete successfully. Do not render the failed page as if it were a valid capture. Log the URL, check DNS and TLS support in the deployed Qt build, and surface a useful error to the caller before quitting or retrying.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
The image is blank or clipped
Check that the frame is the one you loaded, that the viewport was set before image allocation, and that the image dimensions are non-zero. A fixed viewport intentionally captures only that view; use contentsSize() when the requirement is full-frame content. Also verify that the painter was ended before saving.
Dynamic elements are missing
loadFinished can precede script-driven rendering. Add a page-specific readiness check or delay, and capture only after the required element or state exists. Do not solve this by blindly increasing a delay for every site; it makes batch jobs slow without guaranteeing completeness.
The layout differs from a normal browser
Qt WebKit is a legacy engine and its CSS, JavaScript, media, and security behavior may differ from current browsers. Check the viewport width first, because responsive layout changes are expected. Then determine whether the page relies on browser capabilities unavailable in your Qt build.
A very tall page consumes excessive memory
A full-page image stores every pixel in memory. Capture a deliberately sized viewport, split a long document into sections, or resize a separate copy after rendering. Avoid shrinking before the original render if you still need the full-resolution source.
Output, reliability, and batch-operation notes
Use a lossless format such as PNG when text fidelity matters; choose another format only when your application explicitly accepts its quality and size trade-offs. Check the Boolean result from image.save() and treat a false return as an output-path or format error. Use absolute paths in unattended jobs, ensure the destination is writable, and include the source URL and viewport in your job log.
For repeatable captures, fix the viewport, user environment, and readiness condition. Keep one page object alive for the entire asynchronous operation, and do not let local variables be garbage-collected before the callback runs. When processing many URLs, serialize or carefully limit concurrent pages according to the memory available; the Qt references do not establish a universal concurrency limit.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Qt WebKit is a legacy API: plan migration carefully
Qt’s current porting guidance distinguishes the old WebKit stack from Qt WebEngine. WebKit projects use QT += webkitwidgets, QWebPage, and QWebFrame; WebEngine projects use QT += webenginewidgets and QWebEnginePage. WebEngine merges frame handling into the page, so methods such as frame load() become page methods. This is not a safe mechanical class-name replacement: review the porting guide and redesign code around the WebEngine page API, asynchronous behavior, and rendering facilities available in your target Qt version.
The archived Qt 4.7 documentation describes support for HTML, XHTML, SVG, CSS, and JavaScript, but that historical capability list is not a compatibility guarantee for current websites. Pin the Qt and PyQt4 versions in your deployment and test representative pages before promising production coverage.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteBest Value
Or skip the browser setup:
ScreenshotNeo provides a single-call screenshot API when maintaining a PyQt4 browser is unnecessary. It accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and each response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
For a direct image response, see the ScreenshotNeo API documentation and run:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The same request in 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)
And in 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}`);
ScreenshotNeo also supports full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper and margin controls, custom CSS and JavaScript, clicks before capture, hidden selectors, waits for a selector, delay, or network idle, ad and tracker blocking, custom headers, cookies, user agents, Authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Common screenshot-API parameter names are accepted to ease migration.
| Plan | Included shots | Price |
|---|---|---|
| Free | 1,000 per month | No card required |
| Starter | 3,000 | $5 |
| Growth | 15,000 | $15 |
| Pro | 60,000 | $39 |
| Scale | 250,000 | $99 |
| Business | 1,000,000 | $249 |
Yearly billing gives two months free, and every feature is available on every plan. Start with 1,000 free screenshots a month without a card.
Free tools Windows power users keep installed
One-click scans. No signup required.
Frequently Asked Questions
Can I keep the original full-size render and make a thumbnail?
Yes. Render and save the original image first, then scale a separate copy so the source capture remains available.
What does the Boolean argument to loadFinished mean?
It reports whether loading succeeded; it does not certify that script-driven visual updates have finished.
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.




