See Your Site Through Another Country
Screenshot the same website through proxies in two different countries.
Introduction
30 Second Summary
The price on a flight, the banner across a homepage, even which products show up at all can change depending on which country a visitor appears to be in. You only ever get to see your own version of the internet.
In this project, you will build a script that drives a browser running in the cloud through a proxy in one country and screenshots a page. Then you swap the proxy to another country and screenshot the same page again.
What You'll Build
You will finish holding two screenshots of the same page, taken minutes apart from two apparent countries, sitting side by side in a folder your own script wrote.
By the end of this project, you'll have:
- A runnable script that creates a proxy and launches a hosted browser through it, with no browser installed on your laptop.
- Proof the swap worked: an IP lookup page reporting two different countries for the same script on the same machine.
- A session recording you can replay to watch the browser load each page, plus a health check that flags a dead proxy before you rely on it.
- Secret Mission: An optional challenge to push your skills further.
Are there any prerequisites?
You need Node or Python on your machine and an API key for Kernel, the hosted browser service you'll drive. You do not need to install a browser, because the browser runs in the cloud.
Before We Start
Websites don't show everyone the same page. Prices, banners and cookie notices all change depending on the country the visitor appears to be in.
To see that difference for yourself, you need a browser that can appear somewhere else in the world. Kernel runs that browser in the cloud, so nothing gets installed on your laptop.
In this step, get ready to:
- Create a Kernel account and generate an API key.
- Install the Kernel Python SDK on your machine.
- Store your key in a .env file so your script can read it.
Create your Kernel account and API key
Every call your script makes has to prove it belongs to your account. An API key is the string that does the proving.
- Open the Kernel dashboard at dashboard.onkernel.com.
- Create your account using the sign-up option you prefer.
- Go to the API Keys page in the dashboard.
- Create a new key and name it country-view.
- Copy the key somewhere safe before you close the dialog.
Why you only get one look at your key
Kernel returns the plaintext key once, at the moment you create it. Every time you visit the page afterwards, the dashboard shows a masked version instead.
So if you lose it, you create a fresh key rather than recover the old one.
Can't find the API Keys page?
- Check that your sign-up finished, including any confirmation email. A half-finished account has no organisation to hang keys off.
- Check you are on the dashboard rather than the docs site, because the two look similar and only one of them issues keys.
- Still stuck? Walk me through creating a Kernel API key.
Install the Kernel Python SDK
The SDK is the Python library that turns a line of your code into a real browser running in a data centre. It needs Python 3.9 or newer.
- Check which Python you have by running this command:
python3 --version
On Windows, run python --version instead.
✔️ I see version 3.9 or higher
Your Python is new enough to run the Kernel SDK. Carry on with the install below.
ⓧ I see a version lower than 3.9
- Download the latest installer from python.org.
- Run the installer and accept the defaults.
- Close your terminal, then open a new one so it picks up the new install.
- Check the version again by running this command:
python3 --version
On macOS the built-in python3 often still answers with the old version. If that happens, call the new one by its versioned name, such as python3.12, and use that name wherever this project says python3.
ⓧ Command not found
- Download the installer for your operating system from python.org.
- Run the installer.
- Tick the option to add Python to your PATH if the installer offers it.
- Close your terminal, then open a new one.
- Check that Python answers now by running this command:
python3 --version
You should see a version number rather than an error.
- Install the Kernel SDK and the dotenv helper by running this command:
pip install kernel python-dotenv
What did that install?
- kernel is Kernel's official Python library. Your script calls it to create browsers, proxies and screenshots.
- python-dotenv reads a .env file, so your key never has to sit inside your code.
- Confirm the SDK landed by running this command:
pip show kernel
You should see a block of details starting with Name: kernel, followed by a Version: line.
Command not found, or nothing printed?
- If your terminal doesn't know pip, try pip3 instead, which is what many macOS setups use.
- If pip show kernel prints nothing, the install went to a different Python than the one you checked. Run it as python3 -m pip install kernel python-dotenv so both match.
- Still not working? Help me get the Kernel SDK installed.
Store your API key in a .env file
A key pasted into your code travels everywhere that code goes. A .env file keeps it beside your project instead of inside it.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir country-view
cd country-view
- Confirm you are standing in the new folder by running this command:
pwd
You should see a path ending in /Desktop/country-view.
- Create your .env file by running this command, swapping your-api-key-here for the key you copied from the dashboard:
echo "KERNEL_API_KEY=your-api-key-here" > .env
Why the name has to be KERNEL_API_KEY
When you create a Kernel client without handing it a key, the SDK goes looking for an environment variable called KERNEL_API_KEY. Match that name exactly and your script authenticates itself.
Keep this file out of anything you publish. If the country-view folder ever becomes a Git repository, add .env to your .gitignore first.
Before you run the next command, what do you think it prints if your key made it into the file, and what does it print if it didn't?
- Load the key the same way your script will by running this command:
python3 -c "from dotenv import load_dotenv; import os; load_dotenv(); print(os.environ['KERNEL_API_KEY'][:6])"
You should see the first six characters of your key and nothing else. Python found your file, read it, and handed the key over exactly as your script will.
Seeing a KeyError instead of your key?
- Check you ran the command from inside country-view, because the loader looks for the file in the folder you're standing in.
- Print the file to check your key really landed in it:
cat .env
- The line should read KERNEL_API_KEY= followed by your key, with no spaces around the equals sign and no quotes.
- Still no key? Why can't Python find my key.
Your account, your key and the SDK are all in place. Next up, you'll launch your first cloud browser and watch it load a page in a live view.
Launch Your First Hosted Browser Session
Your API key is generated and the SDK is installed, but nothing has used either of them yet. Right now your project folder holds a .env file and no script.
A hosted browser is a real Chromium running on someone else's machine, and you drive it over the network. In this step you write the script that starts one and hands you a link to watch it work.
In this step, get ready to:
- Open your project folder in an editor and create script.py.
- Write a script that loads your API key and launches one cloud browser.
- Watch that browser load a page through the live view URL your script prints.
Open your project folder in VS Code
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- Click File in the top menu bar.
- Click Open Folder.
- Select the project folder you created earlier, the one holding your .env file.
- Click Open.
The Explorer sidebar on the left now lists your .env file.
Sidebar looks empty?
Files starting with a dot are hidden in some file pickers, so an empty-looking folder in the picker can still be the right one.
If the sidebar is collapsed, click the two-page Explorer icon at the top of the far-left activity bar to expand it.
Why can't VS Code see the files in the folder I opened?
Write the script that launches a browser
- Press Ctrl+` to open VS Code's built-in terminal, which opens inside the folder you just opened.
- Install the library that reads your .env file by running this command:
pip install python-dotenv
- Check the output for a line confirming python-dotenv was installed.
Terminal can't find pip?
Try pip3 instead. Some systems only expose the version-suffixed name.
If you installed the SDK inside a virtual environment earlier, activate that environment first so both libraries land in the same place.
Why can't my terminal find pip?
- In the Explorer sidebar, click the New File icon.
- Type script.py as the file name.
- Press Enter to create it.
An empty script.py appears in the sidebar next to your .env file, and it opens in the editor.
- Load your API key and build the client by copying this code into the empty script.py:
import os
from dotenv import load_dotenv
from kernel import Kernel
# Read API_KEY out of the .env file sitting next to this script
load_dotenv()
# The client is your connection to the cloud browser API
client = Kernel(api_key=os.environ["API_KEY"])
print("Loaded API key, ready to launch a browser.")
What does this code do?
- load_dotenv() reads your .env file and loads what it finds into the environment for this run.
- os.environ["API_KEY"] pulls that value back out. A missing key stops the script right here rather than surfacing as a confusing failure later.
- Kernel(api_key=...) builds the client. Every request you make to Kernel travels through this one object.
- Save script.py.
- Prove the key loaded by running this command in the terminal:
python script.py
You should see Loaded API key, ready to launch a browser. printed in the terminal.
Seeing a KeyError instead?
A KeyError on API_KEY means your .env file was not found or does not hold that name.
Confirm both files sit side by side in the same folder in the Explorer sidebar.
If your terminal cannot find python, try python3 instead.
Why is python-dotenv not picking up my .env file?
- Launch the cloud browser by copying this code to the bottom of script.py, below the print line you just added:
# Ask for a cloud browser and tell it which page to open on boot
browser = client.browsers.create(
start_url="https://example.com",
timeout_seconds=300,
)
print("Session ID:", browser.session_id)
print("Live view:", browser.browser_live_view_url)
What does this code do?
- client.browsers.create() asks for a fresh browser on a machine in the cloud and waits for it to boot.
- start_url tells that browser which page to open the moment it starts.
- timeout_seconds=300 keeps the session alive for five idle minutes. The default of 60 seconds leaves you very little time to open the live view and look around.
- browser_live_view_url is a link you open in your own browser to watch the session as it runs.
Before you run this, predict what lands in your terminal. One line, three lines, or an error?
- Save script.py.
- Start the browser by running this command:
python script.py
Three lines come back. After the loaded-key message you get a Session ID: line and a long Live view: URL.
Script errored before printing a URL?
A 401 response means the key in your .env file is missing or not valid. Generate a fresh key and paste it in.
An import error on kernel means the SDK went into a different Python than the one running your script. Reinstall it from this same terminal.
Why is my cloud browser session failing to start?
✔️ Awesome, I've got everything!
Great. Double check you've saved script.py before you move on.
ⓧ I'd like to double check the full code
Here is the whole file. Compare it against yours line by line.
import os
from dotenv import load_dotenv
from kernel import Kernel
# Read API_KEY out of the .env file sitting next to this script
load_dotenv()
# The client is your connection to the cloud browser API
client = Kernel(api_key=os.environ["API_KEY"])
print("Loaded API key, ready to launch a browser.")
# Ask for a cloud browser and tell it which page to open on boot
browser = client.browsers.create(
start_url="https://example.com",
timeout_seconds=300,
)
print("Session ID:", browser.session_id)
print("Live view:", browser.browser_live_view_url)
Watch your browser work in the live view
Before you open that URL, predict what waits at the other end. A saved screenshot of the page, or a browser you can click on right now?
- Copy the URL printed after Live view: in your terminal.
- Paste it into a new tab in your own browser.
- Press Enter to load it.
You get a live browser window showing example.com already loaded. Click inside it and the cloud browser responds to your mouse.
Why the browser runs in the cloud
The browser you are watching never touched your laptop. It booted on a machine you do not own, in a location you can change later.
That detachment is the whole point of this project. Once the browser sits somewhere else, you can move where it appears to be and compare what a site shows each version of you.
Live view page says the session is gone?
The URL stops working once the session is deleted. Sessions shut down after the idle timeout you set, so a long pause between running the script and opening the link will lose it.
Run the script again and open the fresh URL straight away.
Why did my hosted browser session expire before I opened its live view?
You have a browser running somewhere else and a window onto it. Next up, you'll point that browser at a page that reports where in the world it thinks you are.
Screenshot Your IP and Location
Your browser session is live in the cloud, and you watched it load a page in the live view. That browser is running on a machine a long way from your desk.
So it does not browse from your country. Every website it visits sees that machine's location, not yours.
In this step you'll point that same session at a page that reports the visitor's IP address and location. Then you'll save what it says as a screenshot you can hold up against a second one later.
In this step, get ready to:
- Send your running browser session to a page that reports its IP and location.
- Read the country that page hands back.
- Save that page as ip_no_proxy.png inside a screenshots folder.
Ask the browser where it thinks it is
Some sites exist purely to tell a visitor what they look like from the outside: the IP address the request came from, and the city and country attached to it. Send your cloud browser to one, and it reports the cloud browser's location rather than yours.
- In script.py, scroll to the bottom, below the code that navigates the browser to the test page.
- Ask the running session where it appears to be by adding this code:
# Ask the browser to load a page that reports its own IP and location
ip_page = kernel.browsers.playwright.execute(
id=kernel_browser.session_id,
code="""
await page.goto('https://ipinfo.io/json');
await page.waitForLoadState('networkidle');
return await page.locator('body').innerText();
""",
)
# ip_page.result holds the text the page displayed
print(ip_page.result)
What does this code do?
- The code string is a Playwright snippet. The service runs it on the same machine as the browser, so you never install a browser locally.
- waitForLoadState holds the snippet until the page has finished fetching, so you never screenshot a half-loaded page.
- innerText grabs the visible text of the page and hands it back to your laptop as ip_page.result.
- Save script.py.
Before you run this, which country do you think the page will report? Remember the browser is not on your laptop.
- Find out by running this command:
python3 script.py
Under the live view URL, your terminal now prints a block of JSON with an ip field, plus city, region and country.
That country is the browser's, not yours. It is the baseline you'll change later.
Nothing printed, or an error instead?
- If your terminal says command not found, swap python3 for python at the start of the command.
- If the run stops on a timeout, the page needed longer than the default 60 seconds. Add timeout_sec=120 as another argument to the execute call.
Still stuck? Why is my Playwright execute call failing?
Save the answer as a screenshot
Terminal output scrolls away the moment you run something else. To compare two locations later, you need the page saved as a file you can open side by side.
- At the very top of script.py, above your existing imports, add the two tools you need for saving files:
import base64
import os
- Back at the bottom of script.py, below the print(ip_page.result) line, capture the page by adding this code:
# Take a full-page PNG screenshot and hand it back as base64 text
shot = kernel.browsers.playwright.execute(
id=kernel_browser.session_id,
code="""
const image = await page.screenshot({ type: 'png', fullPage: true });
return image.toString('base64');
""",
)
# Create the folder if it is missing, then decode base64 back into a real PNG
os.makedirs("screenshots", exist_ok=True)
with open("screenshots/ip_no_proxy.png", "wb") as f:
f.write(base64.b64decode(shot.result))
print("Saved screenshots/ip_no_proxy.png")
Why does the image arrive as text?
The screenshot is taken on the cloud machine, so it has to travel back to you over an API response. Images cannot ride inside JSON as raw bytes, so the snippet encodes the PNG as base64 text first.
b64decode turns that text back into the original bytes on your laptop. os.makedirs with exist_ok=True creates the folder on the first run and quietly does nothing on every run after.
- Save script.py.
Before you run it, what do you expect to find inside your project folder afterwards that was not there a minute ago?
- Capture the page by running this command:
python3 script.py
- Confirm the file landed by running this command:
ls screenshots
The terminal prints Saved screenshots/ip_no_proxy.png, and ls lists ip_no_proxy.png.
- Open the screenshots folder in Finder (macOS) or File Explorer (Windows).
- Double-click ip_no_proxy.png to view it.
You're looking at the same country your terminal reported, now frozen as an image. That is one half of the comparison this project is built around.
No image in the screenshots folder?
- If Python raises a NameError, check that import base64 and import os really are at the very top of the file.
- If the file exists but will not open, scroll up in your terminal. An error printed above the save line means the screenshot never came back.
Want a second opinion? Why is my saved PNG missing or unreadable?
✔️ Awesome, I've got everything!
Great, just double check you've saved script.py before moving on.
ⓧ I'd like to double check the full code
Scroll up through script.py and confirm all four pieces are present, in this order:
- import base64 and import os at the very top, above your existing imports.
- Everything from the last step: loading the API key, creating the client, creating the browser session, printing the live view URL, and navigating to the test page.
- The ip_page block that loads the IP page, followed by print(ip_page.result).
- The shot block that screenshots the page and writes screenshots/ip_no_proxy.png.
You now have proof of where your browser sits on the map. Next up, you'll route that same request through a proxy and watch a completely different country come back.
Launch a Browser Through a Proxy
Your first screenshot, ip_no_proxy.png, captured one point of view: wherever your hosted browser's traffic leaves Kernel's network by default. Every visitor sees a site through the country their traffic exits from.
To see a page the way another country sees it, the traffic has to leave from that country. That is the job of a proxy, and Kernel lets you create one and attach it to a browser session at launch.
In this step, get ready to:
- Create a proxy configuration that routes traffic through another country.
- Launch a second browser session through that proxy.
- Print the IP and country the proxied browser reports.
Create a proxy in another country
Kernel offers five proxy types: datacenter, ISP, residential, mobile and custom. Residential routes through real home internet connections, which is the type least likely to be blocked by the sites worth testing.
- Return to script.py and scroll to the bottom, below the line that saves your screenshot.
- Create your proxy by adding this to the bottom of the file:
# Register a proxy configuration that routes traffic through Germany
proxy = kernel.proxies.create(
type="residential",
name="nextwork-de",
config={
"country": "DE",
}
)
# proxy.id is what you hand to a browser session to route it through this proxy
print("Proxy ID:", proxy.id)
What does this code do?
- kernel.proxies.create registers a proxy configuration on Kernel and hands you back an object carrying an id.
- type="residential" routes through real home internet connections. Residential exits rotate, so the exact IP can change between requests while the country stays fixed.
- config={"country": "DE"} pins the exit to Germany.
- name is a label for you. Proxy names are not unique, so the id is the value you pass around.
- Save script.py.
- Run your script again by running this command:
python script.py
Your terminal now prints a Proxy ID: line followed by an ID string.
Proxy creation failing?
- Check the API key line in .env is unchanged. An invalid key fails at the client, before the proxy is ever created.
- Copy the country value exactly as written. Kernel's docs use two-letter codes like US and DE.
My proxies.create call is erroring, what should I check?
Launch a browser through the proxy
A proxy configuration on its own routes nothing. It only starts carrying traffic once a browser session launches through it.
- Launch a second browser session through the proxy by adding this to the bottom of script.py:
# Launch a second browser session, routed through the proxy you just created
proxied_browser = kernel.browsers.create(proxy_id=proxy.id)
# Print the session details so you can watch this one work too
print("Proxied session ID:", proxied_browser.session_id)
print("Proxied live view:", proxied_browser.browser_live_view_url)
What does this code do?
- proxy_id=proxy.id tells Kernel to route every request from this session through the proxy you just created.
- browsers.create returns a brand new session. Your first browser is untouched and still exits without a proxy.
- browser_live_view_url is the same kind of live view link you printed earlier, this time pointed at the proxied session.
- Save script.py.
- Run your script again by running this command:
python script.py
Your terminal now prints a second session ID and a second live view URL, both belonging to the proxied browser.
Hitting a limit on browser sessions?
- Every run of script.py opens new sessions. A session enters standby five seconds after nothing is driving it, then is deleted once its timeout elapses, so give it a minute before retrying.
- Look at the Proxy ID: line above the error. If it never printed, the failure is in the previous chunk rather than this one.
Why is my browser session creation being rejected?
Ask the proxied browser where it thinks it is
You now have a browser routed through Germany. The only way to know it actually worked is to ask a site what it sees.
- Send the proxied browser to the same IP and location page from the last step by adding this to the bottom of script.py:
# Ask the proxied browser to load the IP page and hand back what it shows
response = kernel.browsers.playwright.execute(
id=proxied_browser.session_id,
code="""
await page.goto('https://ipinfo.io/json')
return await page.textContent('body')
""",
)
# Print the IP and country the site recorded for the proxied session
print("Proxied location:", response.result)
What does this code do?
- kernel.browsers.playwright.execute runs Playwright code inside the same VM as your browser, so nothing extra needs installing on your machine.
- id=proxied_browser.session_id aims that code at the proxied session rather than your first one.
- page.goto loads the IP page, and page.textContent('body') hands the page's text back to your script.
- response.result holds whatever that code returned. The page answers in JSON, so the print shows the IP and country fields the site recorded.
- Save script.py.
Before you run this, which country do you expect the site to report back this time, and do you expect the IP to be one you have seen before?
- Find out by running this command:
python script.py
The Proxied location: line reports a German IP and country, where your first run reported wherever the unproxied browser exits. Same page, same script, different country.
Still seeing your original country?
- Check the execute call uses proxied_browser.session_id. Aiming it at your first session returns the unproxied country, and everything else still looks correct.
- If the country is right but the IP is not the one you expected, that is normal. Residential exits rotate between requests, so the address moves while the country holds.
My proxied browser is still reporting the wrong country, why?
✔️ Awesome, I've got everything!
Great, double check you've saved script.py.
ⓧ I'd like to double check the full code
Scroll up through script.py and confirm these three additions are present, in this order, below your screenshot line.
- The kernel.proxies.create block, ending with the Proxy ID: print.
- The proxied_browser line, followed by the two session prints.
- The kernel.browsers.playwright.execute block, ending with the Proxied location: print.
Everything above your screenshot line is unchanged from the previous steps.
Two browsers, two countries, one page. Next up, you'll point them at a real site that changes by region and save the difference as a pair of screenshots.
Compare a Region-Specific Page Across Proxies
Your last run printed two different countries back to the console, one from your plain session and one from your proxyed session.
An IP lookup page is a lab instrument. It tells you the proxy works, but nobody ever changed their mind about a product because a lookup page said Germany.
The interesting difference lives on real pages: prices, units, language, banners. In this step you point the proxied session at one of those pages, screenshot it, then swap the country underneath the same running browser and screenshot it again.
In this step, get ready to:
- Load a real page that changes by region through your proxied browser session.
- Swap that running session onto a second country without restarting it.
- Save one screenshot per country into your screenshots/ folder.
Capture what the first country sees
Weather sites decide what to show you from your IP address, which makes them an honest test of a proxy. Any site that localises works here, so swap the URL if you have a better one in mind.
- Return to script.py in your editor.
- Scroll to the bottom of the file, below the print that reports your proxied IP and country.
- Capture what your first proxy country sees by adding this code at the end of the file:
# Load a page whose content depends on where the visitor appears to be
kernel.browsers.playwright.execute(
id=proxy_browser.session_id,
code="""
await page.goto('https://weather.com')
await page.waitForLoadState('networkidle')
""",
)
# Save what the first proxy country sees
with open("screenshots/region_proxy_a.png", "wb") as f:
image_data = kernel.browsers.computer.capture_screenshot(id=proxy_browser.session_id)
f.write(image_data.read())
print("Saved screenshots/region_proxy_a.png")
What does this code do?
- kernel.browsers.playwright.execute runs Playwright code inside the browser's own machine, so there is nothing to install on your laptop.
- await page.waitForLoadState('networkidle') waits until the page stops fetching, so the screenshot catches a finished page instead of a half-drawn one.
- capture_screenshot hands back the raw image bytes, and f.write(image_data.read()) drops them into your screenshots folder.
- Save script.py.
- Run your script by entering this command in your terminal:
python3 script.py
- Confirm the new image landed by running:
ls screenshots
You should see ip_no_proxy.png and region_proxy_a.png listed.
Screenshot came out blank or half-loaded?
A heavy page can keep fetching after the first paint, so a screenshot taken too early shows an empty shell.
Check that the waitForLoadState line sits inside the same code string as the goto line, and that both are indented the same amount.
My screenshot is blank
Swap the proxy on the running session
You could launch a whole second browser for the second country. You do not have to, because a running session can be moved onto a different proxy while it stays open.
Datacenter proxies are the fastest option, but the sites worth region-testing are exactly the ones that block them. Residential proxies route through real home connections, so the page treats you like a local visitor.
- Create your second country and move the session onto it by adding this code at the end of the file:
# A second proxy in a different country, created the same way as the first
proxy_b = kernel.proxies.create(
type="residential",
name="proxy-b",
config={"country": "DE"},
)
# Point the running session at the new country without restarting it
kernel.browsers.update(
proxy_browser.session_id,
proxy_id=proxy_b.id,
)
print("Swapped session to proxy", proxy_b.id)
What is a proxy hot swap?
kernel.browsers.update changes the proxy on a browser that is already running. The session stays alive and the page stays open, so only the exit country underneath it moves.
The browser's network is momentarily disconnected during the swap, so any request already in flight may fail.
The call is synchronous. When it returns, every new request is already routing through the new country, which usually takes 2 to 3 seconds.
- Save script.py.
- Run your script again with this command:
python3 script.py
The console now ends with Swapped session to proxy followed by the new proxy's ID. That ID is the second country, already attached to the session that is still holding your page.
Swap call raising an error?
The first argument to kernel.browsers.update is the session ID, not the proxy. Check you passed proxy_browser.session_id and not the proxy object.
Every run of the script creates a fresh proxy configuration, so seeing a new ID each time is expected rather than a bug.
Why is my proxy swap failing?
Reload and capture the second country
The session is now exiting through a different country, but the page on screen is still the one fetched under the first proxy. A reload is what forces the site to make its regional decision again.
- Capture the second country by adding this code at the end of the file:
# Same page, same session, new exit country
kernel.browsers.playwright.execute(
id=proxy_browser.session_id,
code="""
await page.reload()
await page.waitForLoadState('networkidle')
""",
)
# Save what the second proxy country sees
with open("screenshots/region_proxy_b.png", "wb") as f:
image_data = kernel.browsers.computer.capture_screenshot(id=proxy_browser.session_id)
f.write(image_data.read())
print("Saved screenshots/region_proxy_b.png")
Why reload instead of navigating again?
await page.reload() asks for the exact same URL a second time, so the only variable that changed between your two screenshots is the country.
Navigating somewhere new would introduce a second difference and blunt the comparison.
- Save script.py.
Before you run this, what do you think the second screenshot will show? Same page in a different language, a different city, different units, or no visible change at all?
- Find out by running your script:
python3 script.py
- Confirm both region images exist by running:
ls screenshots
- Open region_proxy_a.png and region_proxy_b.png from the screenshots folder.
- Compare the two images for the location name, the temperature units, and the language of the labels.
The two files are the same page, taken seconds apart, by the same browser. Everything that differs between them is the site reacting to where it thinks you are.
Both screenshots look identical?
Some sites cache the region they picked on your first visit, so the reload serves you the old version.
Try a country further from your first one, or point the goto line at a different localising site and run the script again.
Why do both my screenshots look the same?
✔️ Awesome, I've got everything!
Great, double check you've saved script.py before you move on.
ⓧ I'd like to double check the full code
Scroll up through this step and confirm these three blocks are at the end of script.py, in this order:
- The playwright.execute call that loads the region page, followed by the block that writes region_proxy_a.png.
- The proxies.create call for proxy_b, followed by the browsers.update call that swaps the session.
- The page.reload() call, followed by the block that writes region_proxy_b.png.
Everything above these three blocks is the code you wrote in earlier steps and stays untouched.
You are holding the difference now, two images of one page from two countries. Next up, you'll organise those screenshots into a folder your script writes, and add a health check that tells you a proxy can reach the target before you spend a session on it.
Automate the Comparison and Add a Proxy Health Check
Your script now saves two screenshots of the same page, taken from two different countries. Comparing them still means opening two files and flicking between windows.
This step folds that comparison into a single image your script writes for you. It also adds a check that tells you whether a proxy can reach the page before a browser session is spent on it.
In this step, get ready to:
- Stitch your two region screenshots into one side-by-side image.
- Add a health check that tests each proxy against the target page.
- Run the finished script and confirm every screenshot lands in one folder.
Stitch the two screenshots into one image
Python cannot open and combine image files on its own. Pillow is the imaging library that does it, and it is one install away.
- Install Pillow by running this command:
python3 -m pip install pillow
- Confirm Pillow is installed by running this command:
python3 -c "import PIL; print(PIL.__version__)"
You should see a version number printed in your terminal.
Install did not go through?
- If your terminal reports that python3 is not recognised, use the same Python command you used to install the SDK in Step 1.
- If the install stops on a permissions error, add --user to the end of the install command.
- Why did my Pillow install fail?
- In script.py, add this import at the top of the file, under your existing imports:
from PIL import Image
- Build the comparison image by adding this function and its call at the very bottom of script.py, below the code that saves region_proxy_b.png:
def save_comparison(left_path, right_path, output_path):
# Open the two screenshots the script already saved to disk
left = Image.open(left_path)
right = Image.open(right_path)
# Build a canvas wide enough for both images and tall enough for the taller one
combined = Image.new("RGB", (left.width + right.width, max(left.height, right.height)), "white")
# Place the first country on the left and the second directly beside it
combined.paste(left, (0, 0))
combined.paste(right, (left.width, 0))
combined.save(output_path)
print(f"Saved comparison to {output_path}")
save_comparison(
"screenshots/region_proxy_a.png",
"screenshots/region_proxy_b.png",
"screenshots/comparison.png",
)
What does this code do?
- Image.open loads each screenshot your script saved in the previous step.
- Image.new creates one blank canvas as wide as both screenshots together.
- combined.paste drops the first country at x position 0 and the second one directly beside it.
- Save script.py.
- Produce the comparison image by running this command:
python3 script.py
The script opens its browser sessions again, so give it a moment. The last line of the output reads Saved comparison to screenshots/comparison.png.
- Open the screenshots folder in Finder (macOS) or File Explorer (Windows).
- Double-click comparison.png.
Both countries' versions of the same page now sit side by side in a single image.
Comparison image looking wrong?
- If the script stops because it cannot find screenshots/region_proxy_a.png, the earlier part of the script did not finish, so run it again from the top.
- If one half of the image is blank, that page had not finished loading when its screenshot was taken.
- Why is one half of my comparison image empty?
Check each proxy before a browser uses it
A proxy can be created successfully and still fail to reach the site you care about. Kernel has a check call that tests a proxy against a target URL before any browser session depends on it.
- In script.py, find the code from Step 3 that creates your first proxy with kernel.proxies.create(.
- Add the health check by pasting this code directly below that call:
TARGET_URL = '[[REGION_URL="the region page URL from Step 4"]]'
def check_proxy(proxy, label):
# Ask Kernel to reach the target page through this proxy before a browser uses it
try:
kernel.proxies.check(id=proxy.id, url=TARGET_URL)
print(f"{label}: PASS")
except Exception as error:
# A failure here saves you a wasted browser session and a useless screenshot
print(f"{label}: FAIL - {error}")
check_proxy(proxy_a, "proxy_a")
What does this code do?
- kernel.proxies.check asks Kernel to reach your target through that proxy and reports the result back.
- Passing url tests the page you actually care about instead of Kernel's default test URLs.
- The try block turns a failed check into a printed line rather than a crash.
Why a pass means more for some proxy types
For ISP and datacenter proxies the exit IP is stable. A successful check against your target reliably indicates that the next session reaches the same site from the same IP.
For residential and mobile proxies the exit node changes between requests. A pass confirms the proxy is configured and connected, without guaranteeing the same exit IP next time.
- Find the code from Step 4 that creates your second proxy with kernel.proxies.create(.
- Guard that proxy too by adding this line directly below the call:
check_proxy(proxy_b, "proxy_b")
- Save script.py.
- See both checks run by running this command:
python3 script.py
Two new lines appear in the output, one per proxy, each ending in PASS or FAIL.
One of your proxies reporting FAIL?
- Check that TARGET_URL matches the page you loaded in Step 4, including the https:// prefix.
- Some destination categories, including government, banking and payment domains, are blocked by Kernel's upstream network providers and fail at the proxy layer, so pick a different target page.
- Why is my proxy health check failing on this URL?
Run the finished script end to end
Before you run the script one last time, how many files do you expect to find in your screenshots folder?
- Run the finished script by running this command:
python3 script.py
- List everything the script saved by running this command:
ls screenshots
You should see four files: ip_no_proxy.png, region_proxy_a.png, region_proxy_b.png and comparison.png.
✔️ Awesome, I've got everything!
Great. Double check you have saved script.py before you move on.
ⓧ I'd like to double check the full code
Your script is now too long to compare in one block. Scroll up and confirm each piece from this step is present, in this order:
- The from PIL import Image line at the top of the file, with your other imports.
- The TARGET_URL value, the check_proxy function, and the check_proxy(proxy_a, "proxy_a") call, directly below where you create your first proxy.
- The check_proxy(proxy_b, "proxy_b") call, directly below where you create your second proxy.
- The save_comparison function and its call, at the very bottom of the file.
Secret mission
Sweep a Whole List of Countries
You have two screenshots from two countries. The moment someone asks what the page looks like from Japan, you are back editing your script by hand. This mission turns your two hardcoded countries into a list your script sweeps in a single run.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Your screenshots sit on your own machine, but the browser sessions your script launched run on Kernel's side.
Cost warning
Kernel provided proxies are unmetered and not billed, subject to fair use. A hosted browser session is a separate thing, and it keeps running until something stops it.
Check the usage and pricing on your own account before you walk away from an open session. A session you forget about is a session still open.
Resources you used:
- The hosted browser sessions launched by script.py.
- Your script.py file and the .env file holding your API key.
- The screenshots/ folder holding ip_no_proxy.png, region_proxy_a.png, region_proxy_b.png and comparison.png.
Keep everything running
No action needed. Choose this if you're still actively building on the script and want to run it again soon.
- Leave script.py and your screenshots/ folder where they are.
- Open the live view URL your script printed to see whether a browser session is still live.
Your API key stays valid on your account, and the proxy configurations you created stay there too. Neither one is billed while it sits idle.
Pause - I'll come back to this later
Shut down anything still running, but keep your files. Choose this if you want the screenshots and the script, without a browser session ticking along in the background.
- Press Ctrl+C in the terminal running script.py to stop the script.
- Open the live view URL your script printed to check whether the session is still live.
- Sign in to your Kernel account and stop any browser session still listed as running.
Reload the live view URL once more. A stopped session no longer shows a live browser.
Delete - I don't want to use this again
Remove the project files and close out anything still running. Choose this if you're done with the comparison and want a clean machine.
- Press Ctrl+C in the terminal running script.py to stop the script.
- Sign in to your Kernel account and stop any browser session still listed as running.
- In your terminal, move into the folder that holds script.py.
- Delete your screenshots folder by running this command (macOS/Linux):
rm -rf screenshots
- Delete the same folder on Windows by running:
rmdir /s /q screenshots
- Delete the script and the file holding your API key by running (macOS/Linux):
rm script.py .env
- Delete the same two files on Windows by running:
del script.py .env
- Confirm nothing is left by running:
ls -a
The output no longer lists script.py, .env or screenshots.
Nice Work!
Nice Work!
You just built a script that shows you the same web page the way two different countries see it. Everything you looked at came out of a browser running in the cloud, driven entirely from your own code.
You've learned how to:
- Launch a hosted browser session from a script and watch it load a page through the live view URL.
- Create a proxy configuration and route a browser through it, so an IP lookup reports a different country.
- Swap the proxy on a running session and save a side-by-side comparison of one page seen from two locations, with a health check that proves each proxy can reach the target first.
- Secret Mission: If you took on the optional challenge, you pushed your script further than the main path required.
Ready to quiz yourself?