Puppeteer Web Scraping Tutorial: How to Extract Data from Websites

If you want to learn Puppeteer web scraping, you’ve probably already discovered that controlling a browser is only half the job.

Opening a page is easy. The interesting part begins when you need to find the right elements, extract their contents, handle dynamically generated pages, follow links, and turn everything into structured data.

In this tutorial, we’ll build a practical Puppeteer scraper and look at the techniques you can use to extract information from modern websites.

We’ll also look at how Apify Puppeteer Scraper can simplify the process when you want to run your scraper without building the entire scraping environment yourself.


What is Puppeteer web scraping?

Puppeteer is a Node.js library for controlling a browser programmatically.

Instead of manually opening Chrome, clicking buttons, scrolling through pages, and copying information, your JavaScript program can control the browser for you.

For example, Puppeteer can:

  • Open web pages
  • Click buttons
  • Fill out forms
  • Scroll through pages
  • Read HTML elements
  • Extract text and attributes
  • Interact with JavaScript-rendered content
  • Monitor browser activity
  • Navigate between pages

This makes Puppeteer particularly useful for websites where the information you need isn’t available in the initial HTML response.

The basic idea is simple:

Your JavaScript code → controls Chrome → loads the website → extracts the information you need.


A simple Puppeteer scraper

Let’s start with a very small example.

First, install Puppeteer in a Node.js project:

npm install puppeteer

Then create a JavaScript file:

const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch({
        headless: true
    });

    const page = await browser.newPage();

    await page.goto('https://example.com');

    const title = await page.title();

    console.log(title);

    await browser.close();
})();

This script launches a browser, opens a page, retrieves its title, prints it, and closes the browser.

That’s enough to prove that the browser automation works.

But web scraping becomes much more interesting when we start extracting specific elements.


Finding elements with CSS selectors

The first skill you need when learning Puppeteer scraping is understanding selectors.

Suppose a page contains:

<h1 class="product-title">Wireless Headphones</h1>

You can target the element using:

.product-title

Puppeteer can then extract its text.

For example:

const title = await page.$eval(
    '.product-title',
    element => element.textContent.trim()
);

console.log(title);

The result would be:

Wireless Headphones

This technique is extremely useful because you don’t need to extract the entire page.

You can tell Puppeteer exactly which element contains the information you want.


Using page.$eval() to extract information

One of the most useful Puppeteer methods for scraping is $eval().

It finds the first element matching your selector and runs a function against it.

For example:

const description = await page.$eval(
    '.product-description',
    element => element.textContent.trim()
);

You can use the same technique for headings, prices, descriptions, dates, links, images, and many other elements.

For example:

const price = await page.$eval(
    '.price',
    element => element.textContent.trim()
);

The important concept is:

CSS selector → element → extracted value

Once you understand that pattern, many scraping tasks become considerably easier.


Extracting attributes

Text isn’t the only information you can collect.

HTML elements often contain useful attributes.

Consider this link:

<a class="product-link" href="/products/headphones">
    View product
</a>

You can extract the URL like this:

const url = await page.$eval(
    '.product-link',
    element => element.href
);

You can also extract attributes explicitly:

const value = await page.$eval(
    '.product-link',
    element => element.getAttribute('href')
);

This becomes particularly useful when building crawlers because you can extract links and then use them to discover additional pages.


Extracting multiple elements with $$eval()

What if a page contains dozens of products?

Using $eval() would only give you one matching element.

This is where $$eval() becomes useful.

Suppose the page contains:

<div class="product">Product A</div>
<div class="product">Product B</div>
<div class="product">Product C</div>

You can extract all three with:

const products = await page.$$eval(
    '.product',
    elements => elements.map(element => element.textContent.trim())
);

console.log(products);

The result is an array:

[
    'Product A',
    'Product B',
    'Product C'
]

This pattern is one of the foundations of practical web scraping.

Instead of thinking:

“How do I scrape this page?”

think:

“Which elements contain the data I need, and how can I transform those elements into structured objects?”


Turning scraped elements into structured data

Let’s make the example more useful.

Imagine a product listing contains:

<div class="product">
    <h2 class="name">Laptop Pro</h2>
    <span class="price">$899</span>
    <a class="link" href="/laptop-pro">View</a>
</div>

We can extract all three fields:

const products = await page.$$eval('.product', elements => {
    return elements.map(product => ({
        name: product.querySelector('.name')?.textContent.trim(),
        price: product.querySelector('.price')?.textContent.trim(),
        url: product.querySelector('.link')?.href
    }));
});

console.log(products);

Now the result looks like:

[
    {
        name: 'Laptop Pro',
        price: '$899',
        url: 'https://example.com/laptop-pro'
    }
]

This is much closer to the kind of structured dataset you actually want from a scraper.


Why browser-based scraping is useful

Traditional HTTP scraping works extremely well when the information you need is already present in the HTML returned by the server.

But modern websites often depend heavily on JavaScript.

A page might initially load a basic HTML structure and then use JavaScript to:

  • Load products
  • Display search results
  • Open menus
  • Load additional content
  • Navigate without a traditional page reload
  • Render information after the browser starts

This is where browser automation becomes valuable.

Puppeteer can control a real browser environment and interact with the page after JavaScript has executed.

That gives you considerably more control than simply downloading the raw HTML.


Waiting for dynamic content

One common scraping problem is timing.

You open a page, but the element you want hasn’t appeared yet.

Instead of immediately trying to extract it, you can wait for a selector:

await page.waitForSelector('.product');

const products = await page.$$eval(
    '.product',
    elements => elements.map(element => element.textContent.trim())
);

This tells Puppeteer to wait until the target element is available.

For more complex applications, you may also need to wait for navigation, network activity, or a specific state of the page.

The exact approach depends on how the website works.


Working with JavaScript interactions

One of Puppeteer’s biggest advantages is that you aren’t limited to reading pages.

You can interact with them.

For example:

await page.click('.load-more');

You can type into an input:

await page.type('#search', 'web scraping');

You can then wait for new content:

await page.waitForSelector('.search-results');

And scrape the results:

const results = await page.$$eval(
    '.search-result',
    elements => elements.map(element => element.textContent.trim())
);

This opens the door to scraping workflows that would be difficult or impossible with a simple HTTP request.


Handling infinite scrolling

Some websites don’t use traditional pagination.

Instead, additional content appears as you scroll.

A simple browser automation approach might repeatedly scroll the page:

await page.evaluate(async () => {
    await new Promise(resolve => {
        let totalHeight = 0;
        const distance = 500;

        const timer = setInterval(() => {
            window.scrollBy(0, distance);
            totalHeight += distance;

            if (totalHeight >= document.body.scrollHeight) {
                clearInterval(timer);
                resolve();
            }
        }, 100);
    });
});

After the content has loaded, you can extract the elements.

However, production scraping introduces additional challenges such as retries, resource management, pagination, request handling, storage, and scaling.

That’s where using a scraping platform can become much more convenient.


Puppeteer vs. a ready-made scraping solution

Writing Puppeteer code yourself gives you a lot of control.

But there is another question:

Do you actually want to build and maintain all the infrastructure around the scraper?

A complete scraping workflow may eventually involve:

  • Browser configuration
  • Proxies
  • Retries
  • Request queues
  • Dataset storage
  • Scheduling
  • Monitoring
  • Scaling
  • Handling failed requests
  • Running multiple scraping tasks

For a small experiment, building everything yourself can be perfectly reasonable.

For repeated or larger scraping projects, it can become a project of its own.


Using Apify Puppeteer Scraper

This is where Apify Puppeteer Scraper becomes interesting.

Apify provides a ready-made Puppeteer-based scraper that lets you work with browser automation while handling much of the surrounding infrastructure for you.

The Apify Academy tutorial demonstrates using Puppeteer Scraper to extract structured information from web pages, including titles, descriptions, dates, and other page data.

One particularly useful distinction is between Apify’s simpler Web Scraper and Puppeteer Scraper.

Web Scraper is designed to make common scraping tasks easier, while Puppeteer Scraper provides more control through a Node.js execution environment.

That extra control becomes useful when you need browser interactions or more complicated scraping logic.


When should you use Puppeteer Scraper?

Puppeteer is a good choice when your target website requires browser-level interaction.

For example, you may need to:

  • Execute JavaScript
  • Click elements
  • Interact with forms
  • Navigate dynamically generated pages
  • Extract information after the page renders
  • Handle websites where simple HTML scraping isn’t enough
  • Control browser behavior more precisely

Apify also documents techniques such as infinite scrolling, blocking unnecessary requests, working with datasets, and making browser-like HTTP requests.

The important idea is not that Puppeteer should replace every other scraping technique.

It shouldn’t.

If a simple HTTP request and HTML parser can do the job, that approach can be faster and simpler.

But when you need a browser, Puppeteer gives you the control you need.


A practical Puppeteer scraping workflow

A useful way to think about a scraping project is:

1. Identify the target

Choose the page or website you want to collect information from.

2. Inspect the HTML

Open your browser’s Developer Tools and identify the elements containing the data.

3. Create selectors

Find reliable CSS selectors for those elements.

4. Load the page

Use Puppeteer to navigate to the URL.

5. Wait for the required content

Make sure JavaScript-rendered content has loaded before extracting it.

6. Extract the data

Use $eval() for individual elements and $$eval() for collections.

7. Transform the results

Convert the scraped information into structured objects.

8. Store the data

Save your results as JSON, CSV, or another format suitable for your project.

9. Scale the workflow

If the scraper needs to run repeatedly or across many pages, introduce task management, retries, proxies, storage, monitoring, and scheduling.

This is the point where a scraping platform can save considerable development time.


What you should learn next

If you’re learning Puppeteer web scraping, don’t stop at extracting a title from a page.

The real power comes from combining browser automation with a structured scraping workflow.

Focus on these skills:

  • CSS selectors
  • $eval() and $$eval()
  • Page navigation
  • Waiting for elements
  • Browser interactions
  • Pagination
  • Infinite scrolling
  • JavaScript-rendered content
  • Link extraction
  • Data transformation
  • Error handling
  • Dataset storage

Once you understand these concepts, you can build increasingly sophisticated scrapers.

And if you don’t want to spend your time building all the infrastructure around those scrapers, there’s a practical shortcut.

Try Apify Puppeteer Scraper

If you’re ready to experiment with browser-based web scraping, you can start with Apify’s ready-made Puppeteer Scraper.

It gives you a practical environment for running Puppeteer-based scraping tasks without having to build every part of the infrastructure from scratch.

👉 Try Apify Puppeteer Scraper

Start with a small website, identify a few useful fields, build your selectors, run the scraper, and then gradually increase the complexity of your project.

That’s the best way to move from “I know what Puppeteer is” to “I can actually scrape websites with Puppeteer.”


Final thoughts

Puppeteer web scraping becomes much easier once you stop thinking about scraping as simply “getting HTML.”

Modern websites behave like applications.

You may need to wait for content, click buttons, interact with the page, follow dynamically generated links, and extract information after JavaScript has executed.

Puppeteer gives you the browser-level control needed for these situations.

And when you’re ready to move from experiments to repeatable scraping workflows, Apify Puppeteer Scraper is worth trying.

Learn the browser. Understand the selectors. Build the scraper. Then let the infrastructure do the heavy lifting.

Leave a Reply

Your email address will not be published. Required fields are marked *