I recently wrote about creating a no-code flight price tracker using our Google Flights API and Make.com. I thought I would continue that theme with another no-code platform; n8n. This time we'll be searching for hotels for a nice, lengthy stay in the Eternal City of Rome.

n8n is similar in many ways to Make in that you can create automated workflows without writing any code. n8n positions itself as a no-code platform for technical people where they make it possible to add code when the user wishes for additional customization.

In this tutorial, we'll stick to a no-code workflow that less technical users should be able to follow as we did in the Make tutorial.

Initial Setup

Before we get started, we'll need accounts with SerpApi and n8n. Follow the links below to sign up for these two services:

  1. https://serpapi.com/users/sign_up
  2. https://app.n8n.cloud/register

Make a note of your SerpApi API Key. After you register, you'll be able to find it here: https://serpapi.com/manage-api-key

You'll also need a Google account, but I trust you have one of those already.

Scraping with the Google Hotels API

My preferred starting point for working with any SerpApi API is the API Playground. Here we'll construct our query, find the data we need, and then port it over to n8n.

Our quest is to find hotels in Rome with availability from December 1, 2024 until December 15, 2024. We'll limit our search to only hotels part of Hilton, IHG, and Marriott. We'll also sort the results by lowest price.

Here's what the SerpApi params look like:

engine=google_hotels
q=rome
check_in_date=2024-12-01
check_out_date=2024-12-15
brands=28,17,46 # 28 for Hilton, 17 for IHG, 46 for Marriott
sort_by=3 # 3 to sort by lowest price

Learn more about our Google Hotels API in our documentation. Or check out this pre-filled query in the API Playground.

The results should look like this:

Notice in the JSON body there is an array called properties. This is where all the data we're interested in lives.

Here's sample data for a single property element:

    {
        "type": "hotel",
        "name": "Crowne Plaza Rome - St. Peter's, an IHG Hotel",
        "description": "Modern quarters in an elegant hotel featuring a restaurant, 2 bars & a gym, plus a spa with a pool.",
        "link": "https://www.ihg.com/crowneplaza/hotels/gb/en/rome/romsp/hoteldetail?cm_mmc=GoogleMaps-_-CP-_-IT-_-ROMSP",
        "gps_coordinates":
        {
            "latitude": 41.8888363,
            "longitude": 12.4260736
        }
        ,
        "check_in_time": "3:00 PM",
        "check_out_time": "12:00 PM",
        "rate_per_night":
        {
            "lowest": "$171",
            "extracted_lowest": 171,
            "before_taxes_fees": "$145",
            "extracted_before_taxes_fees": 145
        }
        ,
        "total_rate":
        {
            "lowest": "$2,391",
            "extracted_lowest": 2391,
            "before_taxes_fees": "$2,026",
            "extracted_before_taxes_fees": 2026
        }
        ,
        "nearby_places":
        [
            {
                "name": "Vatican Museums",
                "transportations":
                [
                    {
                        "type": "Taxi",
                        "duration": "10 min"
                    }
                ]
            }
            ,
            {
                "name": "Roma Aurelia",
                "transportations":
                [
                    {
                        "type": "Taxi",
                        "duration": "8 min"
                    }
                ]
            }
            ,
            {
                "name": "Leonardo da Vinci–Fiumicino Airport",
                "transportations":
                [
                    {
                        "type": "Taxi",
                        "duration": "36 min"
                    }
                    ,
                    {
                        "type": "Public transport",
                        "duration": "1 hr 9 min"
                    }
                ]
            }
            ,
            {
                "name": "Ristorante Arturo",
                "transportations":
                [
                    {
                        "type": "Walking",
                        "duration": "2 min"
                    }
                ]
            }
        ]
        ,
        "hotel_class": "4-star hotel",
        "extracted_hotel_class": 4,
        "images":
        [
            {
                "thumbnail": "https://lh5.googleusercontent.com/p/AF1QipNiD1ahcrLS9iAlJ0mvm-GRGrCAho0IN2ReacSz=s287-w287-h192-n-k-no-v1",
                "original_image": "https://lh5.googleusercontent.com/p/AF1QipNiD1ahcrLS9iAlJ0mvm-GRGrCAho0IN2ReacSz=s10000"
            }
            ,
            {
                "thumbnail": "https://lh5.googleusercontent.com/p/AF1QipP7p0wH6HUkVBbv3A1KwxR3oieKAnH-AxFbyLlb=s287-w287-h192-n-k-no-v1",
                "original_image": "https://lh5.googleusercontent.com/p/AF1QipP7p0wH6HUkVBbv3A1KwxR3oieKAnH-AxFbyLlb=s10000"
            }
            ,
            {
                "thumbnail": "https://lh5.googleusercontent.com/p/AF1QipOdwtsEyGSKj-XOZ5XmrBPk2vZirl8rTbXI9XE0=s287-w287-h192-n-k-no-v1",
                "original_image": "https://lh5.googleusercontent.com/p/AF1QipOdwtsEyGSKj-XOZ5XmrBPk2vZirl8rTbXI9XE0=s10000"
            }
            ,
            {
                "thumbnail": "https://lh5.googleusercontent.com/p/AF1QipPnD55e1jg8UGkWUpCi70RPWPZd1HBmjXYRt-qG=s287-w287-h192-n-k-no-v1",
                "original_image": "https://lh5.googleusercontent.com/p/AF1QipPnD55e1jg8UGkWUpCi70RPWPZd1HBmjXYRt-qG=s10000"
            }
            ,
            {
                "thumbnail": "https://lh5.googleusercontent.com/p/AF1QipPlCrLUwTh7IfDanci9grJY_PT4deGdGt6xYthF=s287-w287-h192-n-k-no-v1",
                "original_image": "https://lh5.googleusercontent.com/p/AF1QipPlCrLUwTh7IfDanci9grJY_PT4deGdGt6xYthF=s10000"
            }
            ,
            {
                "thumbnail": "https://lh5.googleusercontent.com/p/AF1QipMi6goY5eP7yit_2RjGxDDok1jVt4ps3_h2okKa=s287-w287-h192-n-k-no-v1",
                "original_image": "https://lh5.googleusercontent.com/p/AF1QipMi6goY5eP7yit_2RjGxDDok1jVt4ps3_h2okKa=s10000"
            }
            ,
            {
                "thumbnail": "https://lh5.googleusercontent.com/p/AF1QipO1L9cxWptOfS2rtLA0mXsoKGdsam-9Daq-v74n=s287-w287-h192-n-k-no-v1",
                "original_image": "https://lh5.googleusercontent.com/p/AF1QipO1L9cxWptOfS2rtLA0mXsoKGdsam-9Daq-v74n=s10000"
            }
            ,
            {
                "thumbnail": "https://lh5.googleusercontent.com/p/AF1QipNe3zbREjnkTnDaXTCn_LIZ6sf5oKXDxK5y95fJ=s287-w287-h192-n-k-no-v1",
                "original_image": "https://lh5.googleusercontent.com/p/AF1QipNe3zbREjnkTnDaXTCn_LIZ6sf5oKXDxK5y95fJ=s10000"
            }
            ,
            {
                "thumbnail": "https://lh5.googleusercontent.com/p/AF1QipNhRbh3Dk5vBQDE2Lqe7SkSubQJNMCAYm2rtMWT=s287-w287-h192-n-k-no-v1",
                "original_image": "https://lh5.googleusercontent.com/p/AF1QipNhRbh3Dk5vBQDE2Lqe7SkSubQJNMCAYm2rtMWT=s10000"
            }
        ]
        ,
        "overall_rating": 4.3,
        "reviews": 3684,
        "ratings":
        [
            {
                "stars": 5,
                "count": 2044
            }
            ,
            {
                "stars": 4,
                "count": 1093
            }
            ,
            {
                "stars": 3,
                "count": 354
            }
            ,
            {
                "stars": 2,
                "count": 89
            }
            ,
            {
                "stars": 1,
                "count": 104
            }
        ]
        ,
        "location_rating": 2.7,
        "reviews_breakdown":
        [
            {
                "name": "Fitness",
                "description": "Fitness",
                "total_mentioned": 308,
                "positive": 229,
                "negative": 51,
                "neutral": 28
            }
            ,
            {
                "name": "Wellness",
                "description": "Wellness",
                "total_mentioned": 220,
                "positive": 176,
                "negative": 27,
                "neutral": 17
            }
            ,
            {
                "name": "Spa",
                "description": "Spa",
                "total_mentioned": 190,
                "positive": 153,
                "negative": 20,
                "neutral": 17
            }
            ,
            {
                "name": "Pool",
                "description": "Pool",
                "total_mentioned": 203,
                "positive": 163,
                "negative": 26,
                "neutral": 14
            }
            ,
            {
                "name": "Business",
                "description": "Business",
                "total_mentioned": 91,
                "positive": 63,
                "negative": 14,
                "neutral": 14
            }
            ,
            {
                "name": "Dining",
                "description": "Food and Beverage",
                "total_mentioned": 203,
                "positive": 148,
                "negative": 38,
                "neutral": 17
            }
        ]
        ,
        "amenities":
        [
            "Breakfast ($)",
            "Free Wi-Fi",
            "Free parking",
            "Outdoor pool",
            "Hot tub",
            "Air conditioning",
            "Pet-friendly",
            "Fitness centre",
            "Spa",
            "Bar",
            "Restaurant",
            "Room service",
            "Full-service laundry",
            "Accessible",
            "Business centre",
            "Child-friendly"
        ]
        ,
        "eco_certified": true,
        "property_token": "ChgI2pHwwsKssrZYGgwvZy8xMTl2MTZuN2cQAQ",
        "serpapi_property_details_link": "https://serpapi.com/search.json?adults=2&brands=28%2C17%2C46&check_in_date=2024-12-01&check_out_date=2024-12-15&children=0&currency=USD&engine=google_hotels&gl=us&hl=en&property_token=ChgI2pHwwsKssrZYGgwvZy8xMTl2MTZuN2cQAQ&q=rome&sort_by=3"
    }

As you can see, there is quite a lot of data returned for each hotel property. We'll select only a few fields relevant to us in a later step. But first, we need to get this same API call working in n8n.

Connecting SerpApi with n8n

We'll start our integration by creating an n8n workflow. This will be our space for linking up multiple nodes to create our hotel price tracker.

Upon creating a workflow, you'll be presented with a nearly blank editor. The only node you'll have is "When clicking 'Test workflow'" which is a trigger that will run all the linked nodes whenever you press the "Test workflow" button near the bottom of the editor.

To add a linked node, hit the "+" button.

We want to make an API call to SerpApi's Google Hotels API so we'll search for "http" and select "HTTP Request."

Next, we'll add the base URL of https://serpapi.com/search.json.

"Authentication" should be set to "Generic Credential Type."

"Generic Auth Type" should be set to "Query Auth."

We'll create a new credential through the "Query Auth" form field.

Here you'll set the "Name" as api_key and the "Value" as your API key. You can always locate your SerpApi API key here. Then click "Save."

Enable "Send Query Parameters." We'll need this enabled to include all the various query params of our Google Hotels search.

With query params enabled, we'll "Specify Query Parameters" by "Using Fields Below" or "Using JSON".

JSON is the easiest way because you can simply copy the JSON below to set all the params.

{
  "engine": "google_hotels",
  "q": "rome",
  "check_in_date": "2024-12-01",
  "check_out_date": "2024-12-15",
  "brands": "28,17,46",
  "sort_by": 3
}

It should look like this in n8n:

If you want to do it the hard way by inputting each param manually, add the "Name" and "Value" while hitting "Add Parameter" for each additional param in our list.

The final product should look like this in n8n:

Now, we ought to give our API call a test by hitting "Test step."

Your output should look something like the following:

Splitting Out Hotel Properties

The response from SerpApi's Google Hotels API will include a properties array as we saw in the Playground. Before we can add these to a Google Sheet, we need to iterate over each one that needs to be processed. This will use n8n's "Split Out" node.

Hit the "+" button to add another linked node.

Search for "split out" and select "Split Out."

From the Split Out menu, find the properties array displayed under "Input." "Input" here means the "Output" from the previous node in the link of nodes. That was our HTTP Request to the Google Hotels API.

Drag properties to the "Fields to Split Out" form field.

Once "properties" is set in the form, hit "Test step."

The output should list each item along with the individual fields of each item. When I searched for this, I received 18 items representing 18 properties in Rome that matched my query and had availability for my dates.

Adding Hotels to Google Sheets

Now, we can begin working on getting our hotel price data into Google Sheets.

First, head over to Google Sheets and create a new sheet called something like "Hotel Price Tracker." Add the same column headers as my screenshot below:

Coming back to n8n, we'll hit the "+" button to add a new linked node.

Search for "google sheets" and click on the "Google Sheets" result.

The Google Sheets node in n8n has multiple "Action" options. We want the "Append row in sheet" option.

n8n will require connection to your Google Sheets account. You can link this by selecting "Create New Credential" under "Credential to connect with."

Follow the "Sign in with Google" flow to link your accounts. You can always unlink this connection whenever you wish.

Ensure "Resource" is set to "Sheet Within Document."

Click on the form box under "Document" and find the "Hotel Price Tracker" sheet we created earlier.

Select "Sheet1" under "Sheet" unless you renamed the sheet in your Google Sheet.

We will be mapping each column manually as you can see in the screenshot. Fortunately, n8n will automatically pull in the column headers into the form to help us out with mapping.

The "Input" here will contain the fields for a single property item from our properties array. We only need to map the fields for one of them to enable all of the properties to be synced to our Google Sheets.

We must first set one field that isn't easily draggable, and that is "Search Time." Just add {{ $now }} to the "Search Time" field. This runs a baked-in n8n method to grab the current time and date. Technically, the search execution time can be retrieved from the API response of the Google Hotels API, but running $now is much simpler to implement in n8n and serves our purposes just fine.

The rest of our fields can be dragged as follows:

Drag the name field to "Name."

Drag the link field to "Link."

Under rate_per_night, drag extracted_lowest to "Rate per Night."

Under total_rate, drag extracted_lowest to "Total Rate."

Scroll down a bit within the "Input" to find hotel_class and overall_rating. These ought to be fairly straightforward to know where to drag.

Here's the final form:

Hit our trusty "Test step" button again.

We should see an "Output" tab with the same number of hotel properties from past steps. This time our data should be organized within our column structure used in the Google Sheet.

Assuming the output above looks good, let's try a full test by hitting "Test workflow."

Pop over to our Google Sheet and we should see a list of all 18 hotels we've been tracking.

Our core workflow is done.

If you followed along with my post making a flight price tracker with Make.com, you'll remember we added a step to send an email through a Gmail connection notifying us of any flights within budget. Unfortunately, n8n requires more steps to do this including configurations in Google Cloud. That would be a bit too much of a tangent for this post so we'll skip that part.

However if that part is important to you, you can follow n8n's instructions here.

Scheduling Searches

We'll run our search once per day as we did our flight tracker. We'll do that by adding a Schedule Trigger node.

We'll need to add a new node for that. However this time, we don't want to add an already linked node. Instead, we'll add an unlinked node by hitting the "+" button in the upper right.

Search for "schedule trigger" and select "Schedule Trigger."

Set the "Trigger Interval" to "Days" and "1" "Days Between Triggers."

We'll set the "Trigger at Hour" to "9am."

Then head back to the editor canvas.

Move the unlinked "Schedule Trigger" node to sit close to the beginning of our workflow. It doesn't matter exactly where it is.

Then delete the link between "When clicking 'Test workflow'" and the "HTTP Request" nodes by pressing the trash can button between those nodes.

Select and drag the "+" button attached to "Schedule Trigger" over to the "HTTP Request" node to link them.

The workflow should look like something like this. Remember it doesn't matter exactly where the nodes are as long as the links are all in the right place. Organize the nodes as you see fit.

Your last step is to set the workflow as "Active" using the tab in the upper right.

Done!

Going forward, the Hotel Price Tracker sheet will get an update every day with the latest hotel prices and availability.

Want to Import This?

If you want to skip all this setup work, you can import my workflow exactly as I've built it in this tutorial. You can then jump in and modify it as you wish. Of course, you'll need to configure your SerpApi and Google credentials in order to run it yourself.

Here's the JSON import file:

To import my workflow, create a new workflow and press the "..." button in the upper right. Then select "Import from File."

Select the JSON file you downloaded from above and n8n will take care of the rest.

Once you've set your SerpApi and Google credentials, you'll be off to the races.