ScreenshotNeo

BlogHow-to

Create Thumbnails for Indian Real Estate Listings from URLs with PHP Panther

Capture a rendered listing page with PHP Panther, then turn the screenshot into a consistent thumbnail while accounting for browser setup, consent overlays, and failures.

By the ScreenshotNeo team4 October 20269 min read

Short answer: use Panther’s native Chrome or Firefox browser client to load the listing URL and save a screenshot, then use PHP’s GD extension to resize and crop that screenshot into your thumbnail dimensions. Panther captures the rendered page; it does not identify the listing’s primary property photo or extract it as a separate image. The code below creates a thumbnail from the visible browser viewport, so use a stable selector and page-specific logic if the desired output is the listing’s photo rather than a page screenshot.

Panther drives real browsers through WebDriver and supports JavaScript, waiting for asynchronously rendered content, and screenshots. Its faster BrowserKit-based alternatives do not support JavaScript or screenshot capture, so they are not suitable for this job. See the Panther project documentation for capabilities and setup details.

1. Install PHP dependencies and a browser

Use PHP with Composer, Chrome or Firefox, and the matching browser driver. The Panther documentation shows installation through Composer and describes driver-installer and operating-system package options. Browser and driver compatibility can vary by environment, so follow the current setup instructions in the project README.

composer require symfony/panther

The thumbnail step below uses PHP GD as well. Enable GD in the PHP runtime that will execute the script. For example, check whether it is available with:

php -m | grep -i '^gd$'

If your deployment uses a container, install the browser, driver, and GD extension in that image. A browser available on your laptop is not automatically available to PHP running in a server container.

2. Capture a listing page and make a thumbnail

Save this as thumbnail.php. It accepts one URL, opens it in Panther’s Chrome client, waits briefly for client-side rendering, saves the viewport screenshot, and uses GD to create a 320-by-200 JPEG with a center crop. The dimensions and crop are implementation choices; adjust them to fit your listing cards.

<?php

require __DIR__ . '/vendor/autoload.php';

use Symfony\Component\Panther\Panther;

if ($argc < 2) {
    fwrite(STDERR, "Usage: php thumbnail.php <listing-url> [output.jpg]\n");
    exit(2);
}

$url = $argv[1];
$output = $argv[2] ?? __DIR__ . '/listing-thumbnail.jpg';

// Limit accepted schemes. In a service, also allowlist permitted hosts to
// prevent requests to internal network addresses (SSRF).
$parts = parse_url($url);
if (!$parts || !isset($parts['scheme'], $parts['host']) ||
    !in_array(strtolower($parts['scheme']), ['http', 'https'], true)) {
    fwrite(STDERR, "Provide a valid http or https URL.\n");
    exit(2);
}

if (!extension_loaded('gd')) {
    fwrite(STDERR, "PHP GD is required to resize and crop the screenshot.\n");
    exit(2);
}

$client = Panther::createChromeClient();

try {
    $client->request('GET', $url);

    // This is a simple settling delay, not proof that every image or API call
    // has finished. Replace it with a page-specific selector wait when possible.
    usleep(1500000);

    $screenPath = sys_get_temp_dir() . '/panther-' . bin2hex(random_bytes(8)) . '.png';
    $client->takeScreenshot($screenPath);

    $source = @imagecreatefrompng($screenPath);
    if ($source === false) {
        throw new RuntimeException('Could not read the browser screenshot as PNG.');
    }

    $sourceWidth = imagesx($source);
    $sourceHeight = imagesy($source);
    $targetWidth = 320;
    $targetHeight = 200;

    // Scale to cover the target rectangle, then crop excess from the center.
    $scale = max($targetWidth / $sourceWidth, $targetHeight / $sourceHeight);
    $scaledWidth = (int) ceil($sourceWidth * $scale);
    $scaledHeight = (int) ceil($sourceHeight * $scale);
    $scaled = imagecreatetruecolor($scaledWidth, $scaledHeight);
    imagecopyresampled(
        $scaled, $source,
        0, 0, 0, 0,
        $scaledWidth, $scaledHeight,
        $sourceWidth, $sourceHeight
    );

    $thumbnail = imagecreatetruecolor($targetWidth, $targetHeight);
    $cropX = (int) floor(($scaledWidth - $targetWidth) / 2);
    $cropY = (int) floor(($scaledHeight - $targetHeight) / 2);
    imagecopy(
        $thumbnail, $scaled,
        0, 0, $cropX, $cropY,
        $targetWidth, $targetHeight
    );

    $directory = dirname($output);
    if (!is_dir($directory) || !is_writable($directory)) {
        throw new RuntimeException('Output directory does not exist or is not writable.');
    }
    if (!imagejpeg($thumbnail, $output, 85)) {
        throw new RuntimeException('Could not write thumbnail to ' . $output);
    }

    imagedestroy($source);
    imagedestroy($scaled);
    imagedestroy($thumbnail);
    @unlink($screenPath);
    echo "Saved thumbnail: {$output}\n";
} finally {
    $client->quit();
}

Run it with an authorized public listing URL:

php thumbnail.php 'https://example.com/listing/123' './output/listing-123.jpg'

The example is a generic implementation, not a guarantee that any particular Indian property portal will render or permit automated access. It does not bypass logins, bot defenses, or consent prompts. Check the portal’s terms and access rules, and use URLs you are allowed to process.

3. Choose what the thumbnail should show

A screenshot thumbnail and an extracted listing photo are different deliverables. The code above captures the browser viewport and crops its center; depending on the page layout, that may include navigation, text, a cookie dialog, or an image that is not the primary listing photo.

Goal Approach Trade-off
Preview of the page as rendered Capture a viewport screenshot with Panther. Page chrome and overlays may appear in the result.
One specific property photo Inspect the page’s markup and select the photo element or its image URL; use page-specific extraction rules. Selectors and image markup can change between portals or listings.
Consistent card image Resize and crop the resulting image to fixed dimensions, as the example does. A center crop can cut off the property; choose crop positioning based on the source content.

Panther’s documentation establishes browser screenshots and page interaction, not portal-specific rules for locating the primary image, dismissing consent overlays, choosing crop coordinates, or producing a particular image format. Treat those as application decisions and inspect output from each portal before using it in a production feed.

Wait for a page-specific condition

A fixed delay can be too short on a slow page and unnecessarily long on a fast one. When you know a stable selector that appears when the main content is ready, wait for that element using Panther’s browser client API. The exact selector is portal-specific; confirm that it identifies the listing content you need. If the page lazy-loads photos only after scrolling, scroll the relevant element or page before capturing and check that the image actually loaded.

Viewport, full page, and crop choices

  • Viewport size: pick dimensions that match the layout you want to render. A narrow viewport can trigger a mobile layout; a wide one can show more desktop navigation.
  • Viewport screenshot: gives a predictable visible region and works with the GD code above.
  • Full-page capture: may produce a very tall image that still needs a deliberate crop to become a card thumbnail. Confirm Panther’s current screenshot options for your installed version.
  • Crop position: a center crop is neutral but may remove the building or room. If the target is a property photo, detect its bounds or extract that image separately rather than assuming the page center is useful.
  • Output format: this example writes JPEG at quality 85. GD can also write PNG or WebP when the PHP build includes the relevant support; select a format based on your destination and transparency needs.

4. Handle URL and browser failures

Listing pages are remote, variable inputs. A production job should record the requested URL, capture status, and error category; use bounded retries for transient failures and avoid retrying permanent errors indefinitely.

Symptom Likely cause What to do
Chrome or driver fails to start Browser/driver missing, incompatible, or unavailable in the runtime. Install the browser and driver in the same environment as PHP; verify the current Panther setup instructions and container dependencies.
Screenshot is blank or shows a loading state Navigation or JavaScript content has not completed, or the site rejected the request. Wait for a meaningful page selector, inspect the final browser state, and distinguish a genuinely empty page from a slow load.
Thumbnail shows a consent overlay or popup The page presented an overlay before capture. Handle it only through an allowed interaction or a page-specific strategy; do not assume Panther removes consent UI automatically.
The wrong image is centered or the property is cut off The screenshot is of the whole viewport, and the center crop is content-agnostic. Change viewport/crop positioning or locate and capture the intended image element with a portal-specific selector.
imagecreatefrompng fails The screenshot file is absent, invalid, or not a PNG. Check the preceding Panther error and screenshot path; Panther’s documented example saves a screenshot, but verify the output format used by your installed version.
GD function is undefined GD is not installed or enabled in the PHP runtime running the script. Enable/install GD for that PHP environment and confirm with php -m.
Cannot write the output Directory is missing or the PHP process lacks write access. Create the directory and grant the process the minimum required write permission.
Request hangs or times out Slow navigation, stalled third-party resources, or an unavailable page. Set an operational timeout in your job runner, capture diagnostics, and retry selectively with a limit.

5. Make batch capture safer and more reliable

  • Validate URLs: accept only HTTP(S), and for a public service restrict hosts to approved domains. Checking the scheme alone does not prevent server-side request forgery to private or internal addresses.
  • Bound resources: run browser jobs with limits on time, memory, and concurrency. Each browser session consumes more resources than a plain HTTP request.
  • Use isolated output paths: generate unique temporary filenames and ensure cleanup happens on both success and failure.
  • Retry carefully: retry transient network or browser startup errors with a small bounded policy. A CAPTCHA, access denial, or changed page structure is not fixed by repeated retries.
  • Keep evidence for debugging: retain a temporary screenshot or error log when a capture fails, subject to your data-retention rules.
  • Respect source access: do not treat browser automation as permission to evade a portal’s restrictions or access controls.

6. Performance and cost considerations

Panther starts and drives a real browser, so captures have browser startup, page navigation, JavaScript, and image-download costs. The exact time and resource use depend on the browser, page, and runtime; the cited project documentation does not provide a benchmark. Reuse a browser session where your job design safely allows it, cap parallel sessions, and avoid waiting on a long arbitrary sleep when a specific ready condition is available.

The Panther documentation does not establish hosted service pricing or per-capture costs. For a self-hosted workflow, plan for the infrastructure and operations needed to run PHP, a browser, its driver, and image processing. For batches, track failures and elapsed time per URL so slow sites do not silently dominate the queue.

Or skip the browser setup

If you want a rendered screenshot without installing and maintaining a browser and driver, ScreenshotNeo is a website screenshot API and MCP server. Its API accepts a URL and returns an image or PDF; see the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/listing/123 -o listing.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com/listing/123"},
    timeout=90,
)
r.raise_for_status()
open("listing.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com/listing/123'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('listing.webp', res);

For PHP, the same endpoint can be called with cURL:

<?php
$query = http_build_query([
    'access_key' => 'YOUR_API_KEY',
    'url' => 'https://example.com/listing/123',
]);
$ch = curl_init('https://api.screenshotneo.com/v1/shot?' . $query);
$fp = fopen('listing.webp', 'wb');
curl_setopt_array($ch, [
    CURLOPT_FILE => $fp,
    CURLOPT_TIMEOUT => 90,
]);
$ok = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$error = curl_error($ch);
curl_close($ch);
fclose($fp);
if (!$ok || $status < 200 || $status >= 300) {
    @unlink('listing.webp');
    throw new RuntimeException($error ?: "Screenshot request returned HTTP {$status}");
}

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; those cleanup steps can be turned off. Bot checks, blank pages, and failed loads are never billed, and the response identifies page verdict and billing status in headers. Its MCP server gives AI agents screenshot, page-info, and PDF-capture tools. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000 screenshots. These captures are screenshots of rendered pages; they do not by themselves identify or extract a listing’s primary property photo.

Sign up for ScreenshotNeo and get 1,000 screenshots a month free, with no card.

FAQ

Can Panther capture a JavaScript-rendered listing page?

Yes. Use its native browser client, which drives Chrome or Firefox and supports JavaScript. The faster BrowserKit alternatives are documented as lacking JavaScript support and screenshot capture.

Does a screenshot automatically become the listing’s main property photo?

No. A page screenshot captures what is rendered in the browser. Finding the preferred property image requires portal-specific extraction or a deliberate element capture and crop.

Will this work for every Indian property portal?

The available Panther documentation does not promise access to every portal or address login walls, bot defenses, or consent flows. Check the behavior and access rules for each source you use.

Can I use the faster Panther clients for screenshots?

No. The project documentation says its faster BrowserKit-based alternatives do not support screenshot capture; use the native browser client for this workflow.