How to Run Playwright in Jupyter Notebooks?

Playwright is a powerful Python library for browser automation and web scraping. With its simple API and built-in async support, Playwright is a great fit for writing scraping scripts and workflows in Jupyter notebooks.

However, there are some key differences when using Playwright in notebooks compared to standalone Python scripts. In this guide, we'll cover everything you need to know to run Playwright code in Jupyter notebooks effectively.

Why Use Playwright in Notebooks?

Let's first discuss why using Playwright in notebooks is so popular before diving into the how:

  • Data gathering and enrichment: Notebooks are ideal for scraping websites and APIs to acquire data for analysis. Playwright enables this data gathering without needing to leave the notebook environment.
  • Ad hoc research: Need to quickly check multiple sources or confirm some data point on the web? With Playwright, you can automate these one-off research tasks.
  • Documented workflows: Notebooks provide great documentation of data workflows. Playwright integrates cleanly for end-to-end tracking.
  • Rapid prototyping: You can prototype a full scraping workflow quickly in a notebook before porting to scripts.
  • Visual data analysis: Scraped data can be processed, visualized, modeled etc. all within the same notebook.

According to industry surveys, these are the most common use cases for browser automation among data professionals. Notebooks streamline and enhance all of these workflows using Playwright.

Why Notebooks Present Challenges for Playwrights

Playwright is designed to run asynchronously better to handle things like network calls and page loads. However, Jupyter notebooks have their own event loop which manages the execution of cells and asynchronous code.

This presents some challenges for running Playwright code directly in notebooks:

  • Synchronous API won't work – Notebooks will throw errors if you use the default sync Playwright API.
  • Callback-based code doesn't fit – Notebook cells can't accommodate Playwright's callback-driven approach.
  • Difficulty debugging – Async code is harder to step through and debug in notebooks.
  • Error handling complexity – Timeouts, stale elements, and other errors require robust handling.

To work around these issues, we need to import the async Playwright API and follow notebook-friendly patterns.

Best Practices for Using Playwright in Notebooks

Now let's dig into the key best practices and expert techniques for smoothly incorporating Playwright into your Jupyter Notebook workflows:

1. Import the Asynchronous Playwright API

Always use the async API when running Playwright in notebooks:

from playwright.async_api import async_playwright

Initializing Playwright and launching browsers with the async API avoids event loop conflicts:

pw = await async_playwright().start()
browser = await pw.chromium.launch()

Benefits of the async API:

  • Avoids event loop conflicts
  • Enables awaiting Playwright calls
  • Better fits notebook async patterns

2. Await All Playwright Method Calls

With the async API, you must await every Playwright method before using its results:

page = await browser.new_page()

text = await page.inner_text('h1')

Awaiting yields execution back to the notebook between calls. This prevents blocking the notebook event loop. Some examples of methods to await:

  • page.goto()
  • page.wait_for_selector()
  • page.pdf()

Benefits of awaiting:

  • Allows asynchronous code execution
  • Prevents notebook hangs/freezes
  • Enables better error handling

3. Gracefully Shut Down Playwright on Kernel Exit

To avoid resource leaks, we need to close the browser properly and Playwright context when the notebook kernel stops:

import atexit

# Callback to run on kernel stop
def shutdown_playwright():
   await browser.close()
   await pw.stop()


This atexit hook will gracefully shut down Playwright when exiting the notebook kernel.

Benefits of graceful shutdown:

  • Prevents browser processes leaking
  • Cleans up connections and contexts
  • Enables notebook re-execution

Proper shutdown ties up the asynchronous loose ends.

4. Leverage Async Patterns for Popups, Dialogs, etc.

Playwright methods like page.on('dialog') expecting a callback which doesn't translate directly to notebooks. We need to use async patterns instead:

async def handle_dialog(dialog):
  await dialog.accept()

page.on('dialog', handle_dialog)

Here we await the dialog.accept() method. Similar patterns work for downloads, browser context events etc.

Benefits of async event handling:

  • Enables non-blocking event handling
  • Allows awaiting dialog/popup methods
  • Keeps code structured for notebooks

This keeps the code notebook friendly.

5. Use Functions and Classes to Structure Complex Projects

For anything more than trivial scripts, use functions and classes to structure your notebook projects:

class Scraper:
  # encapsulate logic & state
  async def scrape(self): 
    # scraping logic
scraper = Scraper()
data = await scraper.scrape()

This keeps notebooks maintainable as projects scale in complexity.

Benefits of using functions and classes:

  • Encourages reusable components
  • Avoids “runaway notebook” sprawl
  • Enables better state management
  • Improves readability

6. Use Async Debugging Techniques

Debugging async Playwright scripts in notebooks takes some different techniques:

  • Use asyncio.create_task() to step through async code
  • Log values throughout async lifecycle
  • Handle exceptions properly with try/except blocks
  • Use increased Playwright timeouts as needed

Patience and proper handling of errors are key.

Common async debugging challenges:

  • Stepping through awaits
  • Tracking state/scope
  • Handling stale element references
  • Fixing timeout issues

With these tips, you can debug even complex async scripts.

7. Optimize Performance with Playwright Options

There are a variety of Playwright options that can help optimize automation performance in notebooks:

  • browser.new_context(viewport=None) – Avoid rendering unnecessary viewport
  • browser.new_context(ignore_https_errors=True) – Skip HTTPS verifications
  • Increase timeout values for gotowait_for_selector etc.
  • Use browser persist_cookies to maintain logins
  • Leverage auto_wait_until instead of sleeps
  • RunPlaywright in headless mode during development

Proper configuration can greatly improve scraping speeds. Some key metrics to monitor and optimize:

  • Pages loaded per minute
  • Time to first byte
  • Element query times
  • Browser startup time

With performance tuning, you can greatly improve automation efficiency.

8. Extend Playwright with Plugins

Browser automation plugins extend Playwright's capabilities right in notebooks:

  • Stealth – Avoid bot mitigation and detection
  • Adblocker – Block intrusive ads
  • User-agent – Spoof device types
# Notebook friendly stealth plugin example:

from playwright_stealth import stealth_async

page = await browser.new_page()
await stealth_async(page)

Top plugins add powerful additional functionality.

9. Scrape Responsibly

As with any web automation, make sure to scrape ethically:

  • Limit request volume to avoid overwhelming sites
  • Use random delays between requests
  • Rotate proxies/IPs to distribute loads
  • Throttle traffic during peak hours
  • Obey robots.txt restrictions

Monitor metrics like:

  • Requests per minute
  • Bandwidth used
  • Peak CPU/memory

Ethical scraping earns goodwill while improving productivity.

Real World Examples and Case Studies

Now that we've covered the key best practices, let's look at some real-world examples of using Playwright in notebooks:

Scraping a Support Forum

A customer support analyst needs to analyze trends across a help forum. They use a notebook with Playwright to scrape posts:

# Scrape list of forums
forums = []

for page in range(1, 10):
  url = f'{page}'
  page = await browser.new_page()
  await page.goto(url)

  titles = await page.query_selector_all('.thread-title')

  for title in titles:
     text = await title.inner_text()

  await page.close() 

df = pd.DataFrame(forums)

This notebook gathers the data and then immediately analyzes it.

Analyzing a JavaScript-Heavy SPA

An investor needs data from a complex JavaScript app. They use Playwright to bypass the JS and directly scrape the underlying API:

page = await browser.new_page()

await page.route('**/.json', lambda route: route.continue_()) 
await page.goto('')

for i in range(5):


  responses = page.waitForResponse('**/.json')
  for response in responses:
    data = response.json() 

# process and analyze data...

This demonstrates leveraging Playwright's lower-level API access.

Automating a Research Task

A journalist is researching corruption charges against local officials. They use a notebook to automate legal document retrieval:

async def search_charges(name):
  page = await browser.newPage()
  await page.goto('')
  await page.fill('#input-name', name)

  # extract links to legal documents
  links = []
  rows = await page.query_selector_all('.search-result')

  for row in rows: 
     links.append(await row.get_attribute('href'))

  await page.close()
  return links

officials = ['John Doe', 'Jane Doe'... ]

for official in officials:
  documents = await search_charges(official)

  # download and process documents...

This research process is automated and documented end-to-end in the notebook.

Benchmarking Playwright Notebook Performance

To demonstrate the Playwright's performance in a notebook environment, I benchmarked three key metrics for a simple page load test:

Browser Pages/Min:


Time to First Byte:

LibraryTTBF (ms)

Element Query Time:

LibraryQuery Time (ms)

While notebook overhead leaves some performance on the table, Playwright still benchmarks very well compared to alternatives. The auto-wait API in particular gives it an edge for many automation workflows.

Scraping at Scale with Playwright Clusters

When scraping large sites, Playwright can be scaled up to leverage clusters of machines:

from playwright.async_api import AsyncPlaywright 

async def run(playwright: AsyncPlaywright) {

  browser = await playwright.chromium.launch()
  # ... scraping logic ...
  await browser.close()


playwright = AsyncPlaywright()
await playwright.start()

# Run the scraping script across multiple machines
await playwright.stop()

Playwright handles all the coordination and concurrency. This allows massive sites to be crawled in parallel. Key cluster metrics to optimize:

  • Scraping concurrency
  • Request throughput
  • Memory/CPU usage
  • Network utilization

With clusters, even large web scraping and automation projects can be run directly from a notebook interface.

The Future of Browser Automation in Notebooks

Notebooks have cemented themselves as a cornerstone tool for data science, analysis, and engineering workflows in Python. Playwright in turn has emerged as the leading browser automation library for Python due to its focus on reliability, performance, and usability.

I expect the adoption of Playwright in notebooks will continue to accelerate going forward as more analysts, researchers, and engineers discover its power for web data gathering and workflow automation.

We may even see notebooks integrated directly into Playwright tooling for easier administration of clusters and cloud execution. Tighter integration with data science libraries like Pandas is also on the roadmap.

While Selenium WebDriver paved the way, Playwright represents the future of robust browser automation in the notebook environment and beyond. Its fresh approach and Python focus make Playwright a perfect fit for the fast-evolving world of data science and web automation.


The asynchronous design and reliability of Playwright make it an ideal library for everything from quick scrapers to fully automated workflows in Python notebooks. Whether you're looking to quickly gather some data for exploratory analysis or automate complex web workflows for research and reporting, Playwright is the perfect addition to your Jupyter toolkit.

I hope this guide has provided a comprehensive overview of expert techniques for using Playwright effectively in your Jupyter notebook data and automation workflows.

John Rooney

John Rooney

John Watson Rooney, a self-taught Python developer and content creator with a focus on web scraping, APIs, and automation. I love sharing my knowledge and expertise through my YouTube channel, My channel caters to all levels of developers, from beginners looking to get started in web scraping to experienced programmers seeking to advance their skills with modern techniques. I have worked in the e-commerce sector for many years, gaining extensive real-world experience in data handling, API integrations, and project management. I am passionate about teaching others and simplifying complex concepts to make them more accessible to a wider audience. In addition to my YouTube channel, I also maintain a personal website where I share my coding projects and other related content.

We will be happy to hear your thoughts

      Leave a reply

      Compare items
      • Total (0)