Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 7

Web Scraping: Server-Side Parsing & Headless Browsers

Recommended resources

Hello! Welcome back to our course on building an automated content creation system.

In the last lesson, we successfully built modules to collect data from structured sources: RSS feeds and JSON APIs. You now have a solid foundation for ingesting clean, machine-readable data.

However, many valuable data sources, like GitHub's trending repositories page or specific tech news articles, don't offer a convenient API or RSS feed. To access this information, we need to extract it directly from the webpage itself. This lesson focuses on that exact skill: web scraping.

We will cover two essential techniques to handle different types of websites:

  1. Server-side HTML Parsing: For simple, static websites where the content is delivered in the initial HTML document.
  2. Headless Browser Automation: For modern, dynamic websites where JavaScript renders the content after the page loads.

Mastering these methods will significantly expand the reach of your data collection pipeline, allowing you to source insights from virtually any public webpage. Your experience with frontend development and the DOM will be particularly useful here, as we'll be interacting with web pages programmatically.

1. Server-Side Parsing for Static Content

The simplest form of scraping is for static websites. When you send an HTTP request to a static site, the server responds with an HTML file that contains all the visible content. Our task is to parse this HTML and extract the data we need. This is very similar to how we fetched JSON from an API, but instead of parsing JSON, we'll be parsing HTML.

For this, we'll use a powerful combination:

  • fetch: The built-in Node.js function you've already used to make HTTP requests and get the raw HTML.
  • Cheerio: A fast, lightweight library that provides a jQuery-like API for navigating and manipulating the HTML document on the server. Given your frontend experience, you should find its selector-based syntax very familiar.

Let's see how this works in practice.

Web scraping with JavaScript and Node.js - ScrapingBee

The following article, 'Web scraping with JavaScript and Node.js,' provides a fantastic, direct introduction to scraping with fetch and Cheerio. The first example is a perfect starting point.

Please read these two sections to get a practical overview: 'Quick answer (TL;DR)': This section provides a complete, minimal script for scraping quotes.toscrape.com. Pay close attention to the flow: fetch HTML, load into Cheerio, iterate over elements with selectors, and extract data. 'Example: Scraping a real website with Node.js': This section expands on the first example with more detailed explanations and better code structure. It reinforces the core pattern of fetch -> parse -> extract -> output.

Implementing a Static Scraper

Let's formalize this pattern into a reusable TypeScript module for our project. We will scrape the website quotes.toscrape.com as demonstrated in the resource.

  1. Installation: Add cheerio to your project.

    npm install cheerio
    
  2. Create the Module: In your src/data-collection directory, create a new file named static-scraper.ts.

  3. Write the Scraper Function:

    // src/data-collection/static-scraper.ts
    
    import * as cheerio from 'cheerio';
    
    // Define the structure of the data we want to extract
    export interface Quote {
      text: string;
      author: string;
      tags: string[];
    }
    
    /**
     * Scrapes quotes from quotes.toscrape.com.
     * @returns {Promise<Quote[]>} A promise that resolves to an array of quotes.
     */
    export const scrapeQuotes = async (): Promise<Quote[]> => {
      const url = 'https://quotes.toscrape.com/';
    
      try {
        console.log(`Fetching HTML from: ${url}`);
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`Request failed with status ${response.status}`);
        }
        const html = await response.text();
    
        // Load the HTML into Cheerio
        const $ = cheerio.load(html);
    
        const quotes: Quote[] = [];
    
        // Use CSS selectors to find the quote elements
        $('.quote').each((_index, element) => {
          const quoteElement = $(element);
          
          const text = quoteElement.find('.text').text().trim();
          const author = quoteElement.find('.author').text().trim();
          
          const tags: string[] = [];
          quoteElement.find('.tags .tag').each((_i, tagEl) => {
            tags.push($(tagEl).text().trim());
          });
    
          if (text && author) {
            quotes.push({ text, author, tags });
          }
        });
    
        console.log(`Successfully scraped ${quotes.length} quotes.`);
        return quotes;
    
      } catch (error) {
        console.error(`Error scraping ${url}:`, error);
        return [];
      }
    };
    

This module provides a clean, self-contained function for scraping a static site. It's efficient because it doesn't need to render the page or execute any JavaScript. However, this approach fails when the content we want isn't in the initial HTML.


2. Headless Browser Automation for Dynamic Content

Many modern websites are built as Single-Page Applications (SPAs) using frameworks like React or Vue. They load a minimal HTML shell and then use JavaScript to fetch data and render the content in the browser. If you use fetch on these sites, you'll get the shell, not the final content.

To scrape these dynamic sites, we need to automate a real web browser that can execute JavaScript. A "headless" browser is a browser that runs without a graphical user interface, which is perfect for automation on a server.

The main tools for this are Puppeteer (from Google) and Playwright (from Microsoft). They allow you to programmatically control a browser (like Chrome, Firefox, or WebKit) to perform actions like navigating, clicking, scrolling, and extracting content from the fully rendered page.

These diagrams visually explain how headless browsers work.

How a Headless Browser Automates the Web
This diagram shows an automation script sending commands to a headless browser engine (like Chromium). The engine renders the page, executes JavaScript, and allows the script to extract data or take screenshots from the final rendered output.
Headless Browser Stack: Behind the Scenes
This diagram illustrates the software stack. Our TypeScript code uses a controller API (like Playwright) to command the browser core, which runs on the server's operating system. It has full browser capabilities but no GUI.

For our project, we will use Crawlee, a powerful web scraping and crawling library that uses Playwright under the hood. It simplifies many common scraping tasks, such as managing request queues and avoiding getting blocked.

This video provides an excellent end-to-end tutorial on building a scraper with Crawlee and Playwright.

Build a Web Scraper from Scratch | JavaScript | Playwright | Crawlee

The video 'Build a Web Scraper from Scratch' gives a complete, hands-on demonstration of using Crawlee and Playwright to scrape a dynamic e-commerce site. It's a perfect practical guide for our needs.

Watch the following segments to learn the core mechanics of building a dynamic scraper: Project Setup (00:34 - 02:06): See how to initialize a new Crawlee project with TypeScript and install Playwright. Basic Extraction (04:18 - 08:20): This part is crucial. It shows how to define a PlaywrightCrawler, create a request handler, and use page methods with selectors to extract data from a page. Crawling and Labeling (08:20 - 11:43): Learn how Crawlee uses enqueueLinks to discover and queue up new pages to scrape (e.g., following links from a category page to product detail pages). The concept of 'labels' is key to managing different stages of scraping. Extracting Detailed Data (14:22 - 20:02): This shows how to target specific data points on a detail page (like title, price, SKU) using Playwright's locators and how to clean the extracted data. Saving Data (20:02 - 22:10): Finally, see how to use Crawlee's built-in Dataset to push the scraped data and export it to a structured format like JSON.

Key Concepts in Headless Browser Automation

As you saw in the video, scraping dynamic sites involves a few more steps than static scraping:

  • Launching a Browser: You start by launching a browser instance.
  • Navigating: You instruct the browser to go to a specific URL.
  • Waiting: This is critical. You must wait for the necessary JavaScript to execute and for the target elements to appear in the DOM. Playwright has powerful auto-waiting capabilities, but sometimes you need to explicitly wait for a selector, a timeout, or a network event.
  • Interacting: You can simulate user actions like clicking buttons (e.g., to load more items) or scrolling (for infinite-scroll pages).
  • Extracting: Once the content is rendered, you use selectors—similar to Cheerio—to grab the data. Playwright's locators and methods like page.evaluate() are used for this.
  • Closing the Browser: You must close the browser instance to free up resources.

This process is more resource-intensive than static scraping but is essential for modern web applications. The logic of using proxies to avoid IP blocking, as mentioned in the "NestJS Web Scraping With Puppeteer" video (resource LINK), is an advanced consideration for when you scale your scraping operations to avoid being detected and blocked by target websites.


Activity: Put Your Scraping Skills to the Test

It's time to add these new capabilities to your project.

  1. Static Scraping Task:

    • Integrate the static-scraper.ts module we designed into your project.
    • In your main script (src/main.ts), import and call the scrapeQuotes function.
    • Log the resulting array of Quote objects to the console to verify it's working.
  2. Dynamic Scraping Task (Choose One):

    • Option A (Recommended): Follow the Crawlee video (LINK) and set up a new Crawlee project. Your goal is to scrape the first page of product titles and prices from the Shopify demo store shown in the video (https://shopify.webscrapingapi.com/collections/all). This directly applies what you learned.
    • Option B (Advanced): If you feel confident, try to scrape the titles and URLs of the repositories on the GitHub Trending page (https://github.com/trending). This is a dynamic page and will require a headless browser. You'll need to install Playwright (npm install playwright) and adapt the patterns from the video.

Completing this activity will equip your project with the tools to gather data from a much wider and more diverse set of sources.


Conclusion

You've now explored the two primary methods of web scraping. You can differentiate between static and dynamic websites and choose the right tool for the job.

Key Takeaways:

  • Static Sites: Use fetch to get the HTML and Cheerio to parse it. This method is fast and resource-efficient.
  • Dynamic Sites: Use a headless browser library like Playwright (or a framework like Crawlee) to render the page and execute its JavaScript before extracting data. This is more powerful but also more complex and resource-intensive.
  • Inspect First: Always start by using your browser's DevTools to inspect the target page. Check the Network tab to see if the data is in the initial HTML document or if it's loaded via subsequent XHR/Fetch requests. This will tell you which scraping method to use.

Preview of the next lesson:
Our data collection toolkit is now incredibly powerful. We can pull data from APIs, RSS feeds, static sites, and dynamic applications. The final step in this module is to make this pipeline run on its own. In the next lesson, we will implement a scheduling mechanism to automate the entire data collection process, ensuring your system is always fueled with the latest information without any manual intervention.