Hello! Welcome to the third lesson in our course.
In the last session, we learned how to extract data from sources that provide a public API. That's the ideal scenario, as APIs give us structured, clean data. But as you'll often find, the data you need isn't always available through a convenient API.
This lesson directly addresses that challenge and fulfills the learning outcome: Scrape movie viewing data from web pages when APIs are not available. We will explore web scraping, the process of programmatically extracting information directly from the HTML content of websites. This is a powerful, and sometimes necessary, addition to your data acquisition toolkit.
We'll cover three main strategies:
- Scraping traditional, static websites.
- A clever trick to find "hidden" APIs on dynamic sites.
- Handling modern, JavaScript-heavy websites using browser automation.
1. The Classic Approach: Scraping Static Sites with requests and BeautifulSoup
The most common method for web scraping involves two key Python libraries that work in tandem:
requests: The same library we used in the last lesson. Here, its job is to send aGETrequest to a URL and download the raw HTML of the page as a string.BeautifulSoup: A powerful parsing library that takes the raw HTML string fromrequestsand turns it into a structured, searchable object. This allows us to navigate the HTML tree and pull out the specific pieces of information we want.
The video below provides a complete, step-by-step guide to scraping the IMDB website. It perfectly illustrates the fundamental workflow you'll use for many scraping tasks.
Web Scraping in Python using Beautiful Soup | Writing a Python program to Scrape IMDB website
This video, 'Web Scraping in Python using Beautiful Soup', demonstrates the entire process of scraping a static website from start to finish.
Watch the full video. It covers everything from installing the necessary libraries to inspecting a web page, finding the right HTML tags, and writing the Python code to extract specific data like movie titles, ranks, and ratings.

The Core Scraping Workflow
As the video demonstrates, the process is systematic and relies heavily on your browser's Developer Tools ("Inspect Element"):
- Fetch the Page: Use
requests.get(url)to retrieve the page's HTML. It's good practice to wrap this in atry...exceptblock to handle network errors or bad URLs. - Inspect the HTML: Right-click on the data you want to extract (e.g., a movie title) and select "Inspect". This opens the Developer Tools and highlights the exact HTML element containing your data. Pay close attention to the tag (like
<td>,<h4>,<a>) and its attributes (especiallyclassorid). - Parse with BeautifulSoup: Create a "soup" object from the response text:
from bs4 import BeautifulSoup import requests response = requests.get(URL) soup = BeautifulSoup(response.text, 'html.parser') - Find Elements: Use
soup.find()(for the first match) orsoup.find_all()(for all matches) to locate your target elements. You'll typically search by tag and class.# Find all table rows ('tr') that represent a movie movie_rows = soup.find_all('tr') - Extract Data: Loop through the elements you found and extract the text content using
.textor specific attributes using.get('attribute_name').
For example, to find all movie titles which are in an <h2> tag with the class movie-title:
# Assuming 'soup' is your parsed BeautifulSoup object
all_title_elements = soup.find_all('h2', class_='movie-title')
for title_element in all_title_elements:
print(title_element.text)
Notice the class_ keyword (with an underscore). This is because class is a reserved keyword in Python.
Test your understanding!
Given the following HTML snippet, which represents a list of movies:
<div class="movie-list">
<div class="movie-item">
<h3>The Christmas Chronicle</h3>
<span>(2018)</span>
</div>
<div class="movie-item">
<h3>Klaus</h3>
<span>(2019)</span>
</div>
<div class="movie-item">
<h3>A Boy Called Christmas</h3>
<span>(2021)</span>
</div>
</div>
How would you write a short Python script using BeautifulSoup to find and print only the movie titles? Assume the HTML is stored in a variable called html_content.
Show answer
from bs4 import BeautifulSoup
html_content = """
<div class="movie-list">
<div class="movie-item">
<h3>The Christmas Chronicle</h3>
<span>(2018)</span>
</div>
<div class="movie-item">
<h3>Klaus</h3>
<span>(2019)</span>
</div>
<div class="movie-item">
<h3>A Boy Called Christmas</h3>
<span>(2021)</span>
</div>
</div>
"""
soup = BeautifulSoup(html_content, 'html.parser')
# Find all 'div' elements with the class 'movie-item'
movie_items = soup.find_all('div', class_='movie-item')
# Loop through each item and find the 'h3' tag inside it
for item in movie_items:
title_tag = item.find('h3')
if title_tag:
print(title_tag.text)
# A more direct alternative:
# for title_tag in soup.select('div.movie-item > h3'):
# print(title_tag.text)
This demonstrates locating a parent container and then extracting specific child elements, a very common pattern.
2. A Smarter Approach: Checking for Hidden APIs
HTML scraping is effective but can be brittle; if the website changes its layout, your scraper will break. Before you start writing parsing code, there's a more elegant approach you should always try first, especially on modern, interactive websites.
Many websites load their content dynamically using the same kind of APIs we discussed in the last lesson. They just aren't publicly documented. By using your browser's developer tools, you can often find these "hidden" APIs and get clean, structured JSON data, saving you the trouble of parsing messy HTML.
Always Check for the Hidden API when Web Scraping
The video 'Always Check for the Hidden API when Web Scraping' demonstrates this powerful and efficient technique.
Watch from 0:26 to 6:52. Pay close attention to the use of the 'Network' tab and the 'XHR' filter in the browser's developer tools to spot API calls.
The workflow is straightforward:
- Open your browser's Developer Tools and navigate to the Network tab.
- Click the XHR (or sometimes
Fetch/XHR) filter. This filters the network log to show only the data requests made by the website's JavaScript code. - Interact with the website (e.g., scroll down, click "Load More," apply a filter).
- Watch for new requests appearing in the Network log. Click on them and check the Response or Preview tab. If you see well-structured JSON data, you've found a hidden API!
- You can then use the URL and headers from that request in your Python script with the
requestslibrary, just as we did in the previous lesson.
This method is almost always preferable to HTML scraping because the data is already structured, and API endpoints tend to be more stable than website layouts.
3. Handling Dynamic Sites with Browser Automation
What if there's no hidden API, and the content you want is loaded with JavaScript after the initial page load? When you use requests.get(), you only get the initial HTML, which might be an empty shell. The actual content (like a list of movies) is rendered by JavaScript running in the browser.
To scrape these sites, you need a tool that can act like a browser: executing JavaScript and rendering the full page. This is called browser automation. The two main libraries for this in Python are Selenium and Playwright.

Playwright is a more modern tool with a very clean API. Let's see how it works.
Web Scraping with Playwright and Python
The article 'Web Scraping with Playwright and Python' from Scrapfly provides an excellent introduction to this modern browser automation tool.
Please read the following sections: 'What is Playwright?', 'The Basics' (including 'Navigation and Waiting' and 'Parsing Data'), and 'Scrolling and Infinite Pagination'. This will give you a solid understanding of how to automate a browser to get fully-rendered HTML.
The key insight from the article is the combined-power approach:
- Use Playwright to control a browser, navigate to the page, and wait for the dynamic content to load.
- Once the content is fully rendered, grab the final HTML using
page.content(). - Pass that complete HTML to BeautifulSoup for parsing, using the same techniques we learned in the first section.
Here is what the basic pattern looks like:
from playwright.sync_api import sync_playwright
from bs4 import BeautifulSoup
def get_dynamic_soup(url):
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto(url)
# Wait for a specific element that appears after JS loads
page.wait_for_selector('div.movie-list-container')
html_content = page.content()
browser.close()
return BeautifulSoup(html_content, 'html.parser')
# Now you can use the 'soup' object as before
soup = get_dynamic_soup("http://example-dynamic-movie-site.com")
# ... find elements and extract data
This pattern gives you the best of both worlds: Playwright's ability to handle JavaScript and BeautifulSoup's powerful and familiar parsing API.
4. Ethical Scraping and Best Practices
With great power comes great responsibility. Web scraping can place a heavy load on a website's servers and has legal and ethical dimensions. As a professional, it's crucial to scrape responsibly.
Python Web Scraping Tutorial: Step-By-Step (2025)
The article 'Python Web Scraping Tutorial' contains a vital section on common pitfalls and best practices.
Please read the 'Common errors' section. It provides a concise summary of the key ethical and technical considerations for building robust and respectful scrapers.
Here are the most important rules to follow:
- Check
robots.txt: Most websites have a file atwww.example.com/robots.txtthat outlines which parts of the site they permit or disallow bots to access. Always respect this file. - Check Terms of Service: Review the website's terms of service for their policy on scraping.
- Be Gentle: Don't hit the server with rapid-fire requests. Add delays between your requests (e.g.,
time.sleep(2)) to mimic human browsing speed and avoid overwhelming their server. - Identify Yourself: Set a custom
User-Agentin your request headers. This tells the website who you are. A descriptive user agent (e.g.,'MyChristmasMovieProject/1.0') is better than a generic browser string. Many sites block the defaultpython-requestsuser agent. - Cache Data: Don't re-scrape the same page repeatedly if the data doesn't change often. Save the HTML locally and work from the cached version.
Conclusion
In this lesson, you've added a vital skill to your data-sourcing arsenal. When a clean API isn't available, you now have a toolkit for extracting data directly from web pages.
Key Takeaways:
- For static websites, use
requeststo fetch HTML andBeautifulSoupto parse and extract data. - Before scraping, always inspect the Network Tab (XHR) in your browser's developer tools to check for a "hidden" API, which provides much cleaner data.
- For dynamic, JavaScript-heavy websites, use a browser automation tool like Playwright to render the page fully before passing the final HTML to BeautifulSoup.
- Always scrape responsibly: check
robots.txt, be gentle with your request rate, and identify your scraper with aUser-Agent.
We've now covered the two primary methods for acquiring data from the web. Our next challenge is that data from different sources (or even from a single messy source) rarely comes in a perfectly consistent format.
In the next lesson, we will focus on the essential task of cleaning and standardizing our collected data to prepare it for consolidation and analysis.