How to Scrape Expedia: Step-by-Step Guide

23 August 2025 | 11 min read

Expedia scraping is a great strategy for tracking of hotel prices, travel trends, and comparison of deals with real-time data. It’s especially useful for building tools that rely on dynamic hotel details like location, rating, and pricing strategies, but accessing these platforms is a lot harder with automated tools.

The main challenge is that Expedia loads its content using JavaScript, so simple scrapers can’t see the hotel listings without rendering the page. On top of that, the site often changes its layout and uses anti-bot measures like IP blocking.

To meet the needs of scraping enthusiasts, our HTML API takes care of JavaScript rendering, user simulation, and proxy rotation automatically. This means you can extract structured hotel data with a single API call—no headless browser or complex setup needed.

In this tutorial, we will teach beginner coders how to define your query, extract hotel listings, and export the data in a clean and understandable format. Let's not waste any time and cover all the necessary steps in extracting information with our dynamic tools!

Quick Answer (TL;DR)

Scraping Expedia requires JavaScript rendering and careful extraction of dynamic hotel listings. Our API client does the heavy lifting for you, fetching hotel names, prices, ratings, and other details – all within the parameters of the connection request.

We handle JavaScript rendering and header assignments by default, so all you need to worry about is assigning appropriate CSS selectors in the "extract_rules" section. To learn more about hotel data extractions, take a look at our Expedia Scraping API.

Scrape Expedia with ScrapingBee

Targeting Expedia data with scraping is a great way to automate hotel search, targeting multiple pages and their HTML source code. Below is an example Python script we created to target Expedia URLs, helping you access all relevant points in hotel information.

Note: Platforms that store constantly updated data often change their HTML structure to throw off automated data extraction efforts. Make sure to check the platform's CSS selectors manually via browser if your scraper stops extracting hotel data.

from scrapingbee import ScrapingBeeClient
import pandas as pd

client = ScrapingBeeClient(api_key='YOUR_API_KEY')

def expedia_search(destination, start_date, end_date):

    extract_rules = {
        "properties": {
            "selector": ".uitk-layout-grid.uitk-layout-grid-has-auto-columns.uitk-layout-grid-has-columns-by-medium.uitk-layout-grid-display-grid",
            "type": "list",
            "output": {
                "name": "h3.uitk-heading.uitk-heading-5",
                "location": ".uitk-text.uitk-text-spacing-half.truncate-lines-3",
                "price_per_day": ".uitk-text.uitk-type-end.uitk-type-400.uitk-type-regular.uitk-text-default-theme",
                "rating": "span.uitk-badge.uitk-badge-base-large.uitk-badge-base-has-text > span.is-visually-hidden",
                "reviews": "span.uitk-text.uitk-type-200.uitk-type-regular.uitk-text-default-theme"
            }
        }
    }

    js_scenario = {
        "instructions":[
            {"evaluate": "document.querySelector('.scrollable-result-section')?.scrollTo(0, document.querySelector('.scrollable-result-section').scrollHeight);"},
            {"wait": 5000}
        ]
    }

    q = destination.replace(' ', '+')

    response = client.get(
        f'https://www.expedia.com/Hotel-Search?destination={q}&endDate={end_date}&startDate={start_date}',
        params={ 
            "wait_for": ".uitk-layout-grid.uitk-layout-grid-has-auto-columns.uitk-layout-grid-has-columns-by-medium.uitk-layout-grid-display-grid",
            "premium_proxy": "true",
            "extract_rules": extract_rules,
            "js_scenario": js_scenario, 
            "country_code": "us" 
        },
    )

    if response.text.startswith('{"message":"Invalid api key:'):
        return (
            "Oops! It seems you may have missed adding your API KEY or "
            "you are using an incorrect key.\n"
            "You can obtain your API KEY by visiting this page: "
            "https://app.scrapingbee.com/account/manage/api_key"
        )
    else:
        def get_info():
            if len(response.json()['properties']) == 0:
                return "FAILED TO RETRIEVE PROPERTIES"
            else:
                return "SUCCESS"

        props = response.json().get('properties', [])
        df = pd.DataFrame(props)
        df.to_csv("result.csv", index=False)
        print(props)
        return {
            'count': len(response.json()['properties']),
            'info': f"{response.status_code} {get_info()}",
        }
    
    
# Date format: YYYY-MM-DD
print(expedia_search(destination='Rome', start_date='2025-10-05', end_date='2025-10-10'))

What the Script Does

Before we break down the script, here are a few essentials to take care of :

  • Python – biggest coding language in 2025, available for all major operating systems.

  • Python libraries – A ScrapingBee library to import our tools, and Pandas for document parsing.

  • Your API key – Available from your ScrapingBee dashboard.

API key

Once you finish your Python installation library, go to your Terminal (or Command Prompt for Windows users) to install Python libraries:

pip install scrapingbee pandas

Note: "pip" is the standard Python package installer used to manage libraries. It is included by default, no extra installation required.

Now, choose a directory on your device and name your Python script, for example, "expedia.py". Then, use a text editor of your choice to write the code. Visual Studio Code works great thanks to its linting and IntelliSense tools, available via extensions.

Begin by importing the installed "pip" packages with the following commands. Lines beginning with a hash sign are comments to help you through the tutorial.

# Importing installed libraries for HTML API calls and code parsing
from scrapingbee import ScrapingBeeClient
import pandas as pd

Importing these libraries allows us to use their tools, which simplify data extraction by taking care of all parameters within the API call. The following line of code begins defining a function "expedia_search" that will require 3 variables:

  • "destination" – pick a location for your hotel search

  • "start_data" – the date of your intended arrival to the hotel.

  • "end_data" – checkout date, which closes the range of your stay

# function definition with 3 variables
def expedia_search(destination, start_date, end_date):

The indented lines below will be a part of the "expedia_search" function, as required by Python’s syntax.

Defining parsing rules

At the start of our function, we begin constructing the scraper by defining its parsing rules in the "extract_rules" dictionary variable, which will be attached to the GET API call parameters.

    extract_rules = {
    "properties": {
        # the chosen selector will iterate over loaded hotel listing cards
        "selector": ".uitk-layout-grid.uitk-layout-grid-has-auto-columns.uitk-layout-grid-has-columns-by-medium.uitk-layout-grid-display-grid",
        "type": "list",
        "output": {
            # within the listing cards, these CSS selectors contain the most relevant information
            "name": "h3.uitk-heading.uitk-heading-5",
            "location": ".uitk-text.uitk-text-spacing-half.truncate-lines-2",
            "price_per_day": "[data-test-id=price-summary] > div > [data-test-id=price-summary-message-line]:first-child > div > span",
            "rating": "span.uitk-badge.uitk-badge-base-large.uitk-badge-base-has-text > span.is-visually-hidden",
            "reviews": "span.uitk-text.uitk-type-200.uitk-type-regular.uitk-text-default-theme"
        }
    }
}

Let's take a step back – this section may feel overwhelming, and there's no easy way around it, especially because defined CSS selectors can change over time. By manually inspecting the platform via a browser, we found that each property listing card is defined within a selector of this particular HTML class:

.uitk-layout-grid.uitk-layout-grid-has-auto-columns.uitk-layout-grid-has-columns-by-medium.uitk-layout-grid-display-grid"

To reach the same result, go to Expedia, choose random variables, right-click a hotel listing card your browser window, and press "inspect element" to enter developer mode. This is the fastest way to find the correct <div> element, and the one containing hotel data should look something like this:

Expedia

Rules for JavaScript rendering

Now we continue defining the function by introducing another nested dictionary variable – "js_scenario". Here we will define rules for JavaScript rendering through provided instructions for our built-in headless browser.

    js_scenario = {
    "instructions": [
 # Pause for 2 seconds to let the page load
        {"wait": 2000},
 # scroll down to the bottom of the page
        {"evaluate": "window.scrollTo(0, document.body.scrollHeight);"},
        {"wait": 2000}
 # Pause for 2 seconds to let the page load
    ]
    }

Let's take a closer look at these instruction parameters:

  • wait – pauses execution for the given milliseconds so the page has time to load/render.

  • evaluate – runs custom JavaScript in the page’s browser context (e.g., scrolling, clicking).

The defined JavaScript rendering instructions will ensure that all hotel search data is loaded before the API call extracts the HTML code.

Initiate the GET API call

Before we reach the Expedia pages, let's create a variable "q" that will reformat your provided destination by replacing spaces with plus signs (turns phrases like "New York" to "New+York") so it can be safely embedded in the search URL:

q = destination.replace(' ', '+')

Finally, we can get to the core of our scraper. Here we invoke the GET API call and instantly assign its results to the "response" variable:

# assigns the GET API call to the response variable 
   response =  client.get(
        f'https://www.expedia.com/Hotel-Search?destination={q}&endDate={end_date}&startDate={start_date}',
# parameters that control how the request is processed and how data is extracted from the loaded page
        params={ 
            "wait_for": ".uitk-layout-grid.uitk-layout-grid-has-auto-columns.uitk-layout-grid-has-columns-by-medium.uitk-layout-grid-display-grid",
            "extract_rules": extract_rules,
            "js_scenario": js_scenario, 
            'country_code':'us'
        },
        retries=2
    )

The addition of a webpage URL is the easy part. The real trick is to nail down the extraction parameters, so let's take a closer look at how they affect your request:

  • wait_for – tells the request to wait until a specific element (CSS selector) appears before continuing.

  • extract_rules – provides the previously defined nested dictionary as an extraction schema (which fields to return).

  • js_scenario – specifies JavaScript steps like waits or scroll actions to simulate user behavior.

  • country_code – sets the request’s exit country, which can affect localization of search results.

Note: To learn more about parameters defining the GET API call, check out our documentation page that explains all ways to control the extracted hotel data.

After that, let's add a section for catching errors, like an incorrect API key, and return of an HTTP error code. By wrapping this in an if statement, we let the user know whether the GET call succeeded or failed, and return the results if everything runs smoothly.

    if response.text.startswith('{"message":"Invalid api key:'):
        return (
            "Oops! It seems you may have missed adding your API KEY or "
            "you are using an incorrect key.\n"
            "You can obtain your API KEY by visiting this page: "
            "https://app.scrapingbee.com/account/manage/api_key"
        )
    else:
        def get_info():
            if len(response.json()['properties']) == 0:
                return "FAILED TO RETRIEVE PROPERTIES"
            else:
                return "SUCCESS"

        props = response.json().get('properties', [])
 #prints the results 
        print(props)
        return {
            'count': len(response.json()['properties']),
            'info': f"{response.status_code} {get_info()}",
        }

And that's it! After finally finishing the "expedia_search" function definition, the printed result should be your list of hotel search results.

CMD

Depending on your goals, you can read extracted data on a Terminal, or export it into a .csv file with the help of Pandas by adding these lines instead of "print(props)":

        df = pd.DataFrame(props)
 df.to_csv("result.csv", index=False)

Note: Expedia is not an easy platform to scrape, therefore you might need a few additional retries to avoid the 500 HTTP error code.

Table

What You Can Extract from Expedia

When scraping Expedia, the core data you’ll want to capture includes the hotel name and location, which identify each property in your search results. These are paired with the price, rating, and review count, giving users a quick snapshot of value and guest experience.

Beyond these essentials, you can also extend your scraper to extract details like price per day, photos, or even promotional tags. These additions add extra layers to your extracted data set making it more useful for all cases.

comparisons, market analysis, and even a potential foundation for an aggregator platform.

Best Use Cases

When we scrape Expedia, the extracted data opens doors for several practical applications. It can help market analysts monitor hotel prices, ratings, and reviews over time to reveal trends and opportunities. Other platforms like travel startups can benefit from web scraping by using this data to build comparison tools, giving users a clearer view of value and guest experience.

At a larger scale, our script can be expanded to feed accurate results into aggregator platforms, combining listings from multiple sources into one interface. By extending our scraper to capture extras like photos or promotions, we create a dataset ready for benchmarking, competitor tracking, and customer-facing travel apps.


Why Use ScrapingBee for Expedia

When scraping Expedia, the biggest challenge is handling a site that depends heavily on JavaScript and constantly shifts its HTML layout. Normally, you’d need to set up a headless browser like Selenium or Playwright, install dependencies, and maintain a local environment just to render the page.

With our API, none of that is necessary. We automatically handle JavaScript execution, so you can request hotel listings without worrying about whether dynamic content has fully loaded. On top of that, you don’t need to combine libraries like requests and BeautifulSoup (BS4) to parse results.

Another key issue is IP blocking, but our built-in proxy rotation solves this by automatically resolving the issue of access to targeted URLs. Combined with JavaScript rendering and smart retries, our API is more reliable and lets you focus on data analysis instead of managing scraping infrastructure.


Important Tips for Scraping Expedia

Scraping Expedia successfully can require quite a bit of tweaking to make sure scraper avoids detection. One of the most effective strategies is setting custom headers such as User-Agent and Accept-Language, which make your requests appear closer to real browser traffic. By mimicking normal browsing behavior, you reduce the risk of being flagged as a bot.

Timing is equally important. Instead of sending bursts of requests, implement irregular JavaScript rendering steps. This prevents Expedia’s servers from treating your scraper as suspicious automated traffic. You’ll also need to handle pagination carefully—search results often span multiple pages, so loop through them with updated query parameters to capture the full dataset.

Retries are another key safeguard. Even well-structured scrapers can occasionally fail due to server hiccups or network issues. Adding retry logic ensures your script can gracefully attempt the request again instead of failing outright.

Still not sure how to apply these solutions? Check out our blog on scraping without getting blocked.

Need Expedia Data? Start with ScrapingBee

Collecting data from Expedia manually takes way too much time. However, thanks to our HTML API, you can get hotel prices, reviews, ratings, and any other hotel data with just one click! We handle JavaScript-heavy content automatically, so you don’t need to run a headless browser and worry about connection restrictions.

Just plug in your search destination, set your travel dates, and your script deliver clean, structured data from Expedia! Sign up today and use your free credits and utilize our versatile HTML API for hotel data in a matter of minutes.

How can I scrape Expedia hotel listings?

Scraping Expedia requires handling JavaScript-rendered content and targeting the right CSS selectors for hotel details like names, prices, and ratings. Our HTML API automates this by rendering JavaScript and simulating scrolling, so you get complete data with minimal setup.

Does Expedia use JavaScript for content?

Yes, Expedia dynamically loads hotel listings using JavaScript, which makes static scraping tools miss key information. We automatically handle full JavaScript execution in the background, ensuring you can reach the data on a targeted platform.

What headers should I use when scraping Expedia?

To avoid detection, you should send headers like User-Agent and Accept-Language that mimic real browser traffic. In this tutorial, we did not use any header because our smart defaults already reduce the chance of encountering HTTP 500 errors.

How does ScrapingBee avoid blocks on Expedia?

We avoid connection restrictions by rotating IPs, managing retries, and mimicking human behavior. Our tools automate proxy rotation, and allow the optimization of mimicked user actions with no extra libraries.

image description
Kevin Sahin

Kevin worked in the web scraping industry for 10 years before co-founding ScrapingBee. He is also the author of the Java Web Scraping Handbook.