DataLens

Navigating pages and taking screenshots

Go to URLs and check the response status, follow links, move back and forward, and save screenshots of pages and elements to see what your scraper saw.

Level: Foundations · 7 min read

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

The idea

A scraper is a browser on autopilot: it goes to pages, follows links, and occasionally needs to show you what it saw. This module covers moving around a site reliably (including noticing when a page failed to load) and screenshots, your best debugging tool.

goto returns a response

When a scraper visits hundreds of product pages, some of them won't exist anymore. A person would notice the "Not found" message straight away, but a script won't unless you check. goto hands you the server's response, so you can:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    response = page.goto("https://nusantara-gadget.test/product/3")
    print(response.status, response.ok)
    print(page.url)
    print(page.title())

    missing = page.goto("https://nusantara-gadget.test/product/999")
    print(missing.status, missing.ok)
    print(page.title())
    browser.close()
200 True
https://nusantara-gadget.test/product/3
iPhone 15 — Nusantara Gadget
404 False
Not found

page.goto returns the main document's response. The browser happily shows a "Not found" page, so without checking the status your scraper would carry on and scrape an error page as if it were a product. Check response.ok (status below 400) or response.status after every important navigation.

Real users click links; so can your scraper. Clicking a link navigates, and page.url tells you where you ended up:

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://nusantara-gadget.test/")
    page.get_by_role("link", name="Laptops").click()
    print(page.url)
    print(page.title())

    page.go_back()
    print(page.url)
    page.reload()
    print(page.title())
    browser.close()
https://nusantara-gadget.test/products?category=laptops
Products — page 1 — Nusantara Gadget
https://nusantara-gadget.test/
Nusantara Gadget — Official Gadget Store

(get_by_role finds elements the way a person describes them, "the link called Laptops". Module 4 covers it properly.)

Links in HTML are often relative (/product/3, ?page=2). page.goto resolves them against the current page, just like a browser:

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://nusantara-gadget.test/products")
    page.goto("/products?page=2")
    print(page.url)
    browser.close()
https://nusantara-gadget.test/products?page=2

When you build URLs yourself in Python, urllib.parse.urljoin(page.url, href) does the same.

Screenshots

When a scraper misbehaves, the fastest question to answer is "what did the page actually look like?":

import os

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://nusantara-gadget.test/products")
    page.screenshot(path="products.png")                       # the visible window
    page.screenshot(path="products_full.png", full_page=True)  # the whole scrollable page
    page.locator(".product-card").first.screenshot(path="first_card.png")   # one element
    browser.close()

print(sorted(f for f in os.listdir(".") if f.endswith(".png")))
['first_card.png', 'products.png', 'products_full.png']

A useful habit: in your scraper's error handling, save a screenshot named after the failing URL. When something breaks at 3 a.m., the picture usually explains it in seconds.

Key idea

Check response.ok after goto; navigate by clicking links or with goto (relative URLs work); use page.url to confirm where you are; take screenshots whenever you need to see what the scraper saw.

wait_until: when is a page "loaded"?

goto waits for the load event by default: the HTML, CSS, scripts and images are in. It does not wait for data that JavaScript fetches afterwards (the catalog from module 1). Options:

wait_until=Waits for
"load" (default)HTML and its resources
"domcontentloaded"just the HTML parsed (faster)
"networkidle"no network requests for 500 ms (slow, use sparingly)

For JavaScript-loaded content, it's better to wait for the specific element you need, which module 6 covers.

Why this matters for clean code

Silent failures are the worst kind: a scraper that collects 500 "Product not found" pages reports success and corrupts your dataset. Checking statuses and failing loudly (or logging and skipping) keeps bad data out. Screenshots on failure make the problems you can't prevent quick to diagnose.

Recap

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

Next: Selecting elements (selectors, get_by_role / get_by_text) →