JavaScript Hotels Parser

Parser for website hotels, all from the single package.

About

hotels-scraper-js is a Node.js package designed to parse popular website hotels from a single module.

The package is available on npm and can be installed and used in your Node.js project. It is used to scrape hotels list and hotel info from Airbnb, Booking, and Hotels.com websites. Soon we planning to add Google Hotels or other websites.

The long-term plan of this project is to combine API and non-API solutions as SerpApi started developing support for Google Hotels so later users can choose the desired backend to work with (free with limitations or paid without limitations). SerpApi's backend could be also useful for people who don't know how to use it, for example, SerpApi pagination which could be enabled with a simple function argument pagination=True.

Development is sponsored by SerpApi.

Limitations and problems

  • The main problem is that sites that parse often change their structure or selectors, so from time to time one of the scraping fields can't be gotten (sometimes the parser can crash).

  • Hotels.com sometimes does not load the place input field (which happens in the usual browser too) and the place autocomplete doesn't work, so the parser can crash.

  • If your IP address is blocked, or there is no access from your country, and if one of the sites from this parser doesn't work in your browser you can't get results from this parser.

What you can get with hotels-scraper-js?

Airbnb:

List of recommended hotels by categories

image

List of hotels by selected location and dates

image

Selected hotel info

image

Booking:

List of hotels by selected location and dates

image

Selected hotel info

image

Hotels.com:

List of hotels by selected location and dates

image

Selected hotel info

image

Installation & using

You can get the installation guide and all available options from the documentation

Basic Examples

In these basic examples, we'll get links to each hotel from the 3 parsers. And then those links will be used to get hotel info.

Airbnb hotels result from the selected category and with selected currency.

First, we need to get all available categories and currencies:

console.log(airbnb.getFilters());

Output:

{
   "currencies":[
      {
         "name":"Australian dollar",
         "value":"AUD"
      },
      {
         "name":"Brazilian real",
         "value":"BRL"
      },
    ... and other currencies
   ],
   "categories":[
      {
         "name":"A-frames",
         "value":"8148"
      },
      {
         "name":"Design",
         "value":"8528"
      },
     ... and other categories
   ]
}

Next, using got before category and currency get hotels list with 50 results:

📌Note: you can use both name or value.

airbnb.getHotels("Design", "BRL", 50).then(console.log);

Output:

[
   {
      "thumbnail":"https://a0.muscache.com/im/pictures/74102172/9815f41f_original.jpg?im_w=720",
      "title":"Bolzano, Italy",
      "subtitles":[
         "Designed by Peter Pichler",
         "Apr 15 – 20"
      ],
      "price":{
         "currency":"R$",
         "value":1417,
         "period":"night"
      },
      "rating":4.8,
      "link":"https://www.airbnb.com/rooms/4594196"
   },
   {
      "thumbnail":"https://a0.muscache.com/im/pictures/397ed026-16a9-4418-af19-1d2b31fe0aa8.jpg?im_w=720",
      "title":"Korčula, Croatia",
      "subtitles":[
         "Luxury Lifestyle Awards: Best Luxury Villa Croatia",
         "Apr 1 – 6"
      ],
      "price":{
         "currency":"R$",
         "value":11290,
         "period":"night"
      },
      "rating":"No rating",
      "link":"https://www.airbnb.com/luxury/listing/34594127"
   },
   ... and other hotels
]

And then using one of the hotel links get hotel info:

airbnb.getHotelInfo("https://www.airbnb.com/rooms/4594196").then((data) => {
  console.dir(data, { depth: null });
});

Output:

{
   "name":"Mirror House Sud",
   "shortDescription":"Entire home hosted by Sabina Angela",
   "shortOverview":[
      "4 guests",
      "1 bedroom",
      "1 bed",
      "1 bath"
   ],
   "highlights":[
      {
         "title":"Designed by",
         "subtitle":"Peter Pichler"
      },
      {
         "title":"Featured in",
         "subtitle":"Designboom, December 2014ArchDaily, December 2014"
      },
      {
         "title":"Sabina Angela is a Superhost",
         "subtitle":"Superhosts are experienced, highly rated hosts who are committed to providing great stays for guests."
      }
   ],
   "price":{
      "currency":"₴",
      "amount":8828,
      "period":"night"
   },
   "description":"Mirror Houses are two small houses immersed in a beautiful scenery of apple orchards just outside, in the wonderful surroundings of the South Tyrolean Dolomites.The Mirror Houses offer a unique opportunity to spend a wonderful holiday surrounded by contemporary architecture of the highest standards in close contact with one of the most evocative landscapes that nature can offer.The spaceThe Mirror House Sud (45sqm) consists of 1 bedroom with double bed, 1 bathroom with spacious shower and 1 living room / kitchen (single space) with a double sofa bed. The location is ideal for 2 people, but can accommodate up to a maximum of 4 people.If you are 4 adults we recommend booking both Mirror House North and South.In addition there is a spacious terrace that + a private garden and
in the summer months a shared garden with a common pool and a self-service pool bar.Under the houses there is also a common storage room for sports equipment or other.",
   "sleepOptions":[

   ],
   "host":{
      "name":"Sabina Angela",
      "joined":"Joined in March 2013",
      "overview":[
         "127 Reviews",
         "Identity verified",
         "Superhost"
      ],
      "additionalInfo":[
         "Response rate: 87%",
         "Response time: within an hour"
      ]
   },
   "link":"https://www.airbnb.com/rooms/4594196",
   "placeOffers":[
      "Hair dryer",
      "Shampoo",
      "Hot water",
   ... and other place offers
   ],
   "houseRules":[
      "4 guests maximum",
      "No pets",
      "Check-in: 2:00 PM - 9:00 PM",
      "Checkout before 11:00 AM",
      "No commercial photography",
      "No parties or events",
      "No smoking"
   ],
   "safetyAndProperty":[
      "Carbon monoxide alarm not reported",
      "Smoke alarm not reported",
      "Some spaces are shared"
   ],
   "photos":[
      "https://a0.muscache.com/im/pictures/74102421/46d207f2_original.jpg?aki_policy=large",
      "https://a0.muscache.com/im/pictures/74102172/9815f41f_original.jpg?aki_policy=large",
      "https://a0.muscache.com/im/pictures/309bee53-311d-4f07-a2e7-14daadbbfb77.jpg?aki_policy=large",
   ... and other photos
   ],
   "reviewsInfo":{
      "totalReviews":53,
      "rating":{
         "total":4.81,
         "cleanliness":4.9,
         "accuracy":4.9,
         "communication":4.8,
         "location":4.7,
         "checkIn":4.8,
         "value":4.6
      },
      "reviews":[
         {
            "name":"Nadav",
            "avatar":"https://a0.muscache.com/im/pictures/user/cbecae28-d8cc-46b9-9294-c1b0fcb512e9.jpg?im_w=240",
            "userPage":"https://www.airbnb.com/users/show/10949335",
            "date":"March 2023",
            "review":"Had an amazing time at the Mirror House. Not only was the place very beautifully designed, but the location also made it very special. A must-stay for a romantic vacation in Bolzano.
In addition to this, Sabine was probably the best host I've had while using AirBnb. She was very helpful in helping us get reservations to local restaurants and actually made us a cake (this was an engagement trip, so she offered to make us one instead of actually getting one at a bakery). Overall, we had an amazing vacation. Sabine and the Mirror House definitely made it very special for us!"
         },
         {
            "name":"Khaled",
            "avatar":"https://a0.muscache.com/im/pictures/user/f7804757-f867-46d5-8557-33d9be786657.jpg?im_w=240",
            "userPage":"https://www.airbnb.com/users/show/179920339",
            "date":"March 2023",
            "review":"Beautiful place and private surroundingIt was a good experienceHighly recommended"
         },
      ... and other reviews
      ]
   }
}

Booking hotels result with selected filters, currency, location, dates, amount of guests, and set for business purposes.

First, we need to get all available categories and currencies:

📌Note: you need to use console.dir method to see the depth of inner keys of more than two in the result object.

console.dir(booking.getFilters(), { depth: null });

Output:

{
   "currencies":[
      {
         "name":"Property's Currency",
         "value":"hotel_currency"
      },
      {
         "name":"Argentine Peso",
         "value":"ARS"
      },
    ... and other currencies
   ],
   "filters":{
      "budget":[
         {
            "name":"US$0 – US$50",
            "value":"pri=1"
         },
         {
            "name":"US$50 – US$100",
            "value":"pri=2"
         },
        ... and other budget options
      ],
      "propertyRating":[
         {
            "name":"1 star",
            "value":"class=1"
         },
         {
            "name":"2 stars",
            "value":"class=2"
         },
         {
            "name":"3 stars",
            "value":"class=3"
         },
        ... and other rating options
      ],
      ... and other filters
   }
}

Next, using got before an array of filters (["pri=2", "3 stars"]), currency, set location, dates, amount of guests, and set for business purposes:

📌Note: you can use both name or value for filters and currencies.

booking
  .getHotels(["pri=2", "3 stars"], "Argentine Peso", undefined, "Miami", "22/05/2023", "27/05/2023", 3, 1, undefined, "business")
  .then(console.log);
  • ["pri=2", "3 stars"] - we choose a budget filter with a price limit of "US$50 – US$100" and a property rating filter with a "3 stars" limit;

  • "Argentine Peso" - selected currency;

  • undefined - didn't set the resultsLimit argument. That means we leave the default results limit (35 for booking);

  • "Miami" - selected location;

  • "22/05/2023" - check in date;

  • "27/05/2023" - check out date;

  • 3 - adults amount;

  • 1 - children amount;

  • undefined - didn't set the rooms argument. That means we leave the default needed rooms amount (1 for booking);

  • "business" - we choose "traveling for work" filter;

Output:

[
   {
      "thumbnail":"https://cf.bstatic.com/xdata/images/hotel/square200/277583459.webp?k=d90d11e2d824655526e06df6a5ddc759b715e9a27cb6ed1d8351a1a3a4e67b03&o=&s=1",
      "title":"Holiday Inn Express Doral Miami, an IHG Hotel",
      "stars":3,
      "preferredBadge":true,
      "promotedBadge":false,
      "location":"Doral",
      "subwayAccess":false,
      "distanceFromCenter":17.7,
      "highlights":[

      ],
      "rating":{
         "score":9,
         "scoreDescription":"Wonderful",
         "reviews":1396
      },
      "link":"https://www.booking.com/hotel/us/holiday-inn-express-doral-miami.html?lang=en-us"
   },
   {
      "thumbnail":"https://cf.bstatic.com/xdata/images/hotel/square200/379622649.webp?k=ed10fc85de5b55f776d6d5f2337bd823566039afd770a5f8a395d24e5308399f&o=&s=1",
      "title":"Best Western Plus Windsor Inn",
      "stars":3,
      "preferredBadge":true,
      "promotedBadge":false,
      "location":"North Miami",
      "subwayAccess":false,
      "distanceFromCenter":13.3,
      "highlights":[

      ],
      "rating":{
         "score":7.7,
         "scoreDescription":"Good",
         "reviews":644
      },
      "link":"https://www.booking.com/hotel/us/best-western-windsor-inn.html?lang=en-us"
   },
   ... and other hotels
]

And then using one of the hotel links get hotel info:

booking.getHotelInfo("https://www.booking.com/hotel/us/holiday-inn-express-doral-miami.html?lang=en-us").then((data) => {
  console.dir(data, { depth: null });
});

Output:

{
   "title":"Holiday Inn Express Doral Miami, an IHG Hotel",
   "type":"Hotel",
   "stars":3,
   "preferredBadge":true,
   "subwayAccess":false,
   "sustainability":"",
   "address":"1691 NW 107th Ave, Doral, FL 33172, United States of America",
   "highlights":[
      "Free WiFi",
      "Free parking",
      "Air conditioning",
      "Private Bathroom",
      "24-hour front desk",
      "Key card access",
      "Daily housekeeping",
      "Non-smoking rooms",
      "Safe",
      "Baggage storage"
   ],
   "description":"Located a 13-minute walk from Miami International Mall, Holiday Inn Express Doral Miami, an IHG Hotel offers 3-star accommodations in Doral and has a shared lounge. Featuring a fitness center, the 3-star hotel has air-conditioned rooms with free WiFi, each with a private bathroom. Guests can have a drink at the snack bar.\n""+""All rooms at the hotel come with a seating area, a flat-screen TV with satellite channels and a safety deposit box. At Holiday Inn Express Doral Miami, an IHG Hotel all rooms include bed linen and towels.\n""+""An American breakfast is available every morning at the accommodation.\n""+""Guests at Holiday Inn Express Doral Miami, an IHG Hotel will be able to enjoy activities in and around Doral, like cycling.\n""+""Free private parking and a business center are available, as well as a 24-hour front desk.\n""+""Dolphin Mall is a 17-minute walk from the hotel, while University of Miami is 17.7 km from the property. The nearest airport is Miami International Airport, 6 km from Holiday Inn Express Doral Miami, an IHG Hotel.",
   "descriptionHighlight":"Couples in particular like the location – they rated it 9.2 for a two-person trip.",
   "descriptionSummary":"Holiday Inn Express Doral Miami, an IHG Hotel has been welcoming Booking.com guests since Oct 19, 2020\n""+""\n""+""Hotel chain/brand:\n""+""Holiday Inn Express",
   "facilities":[
      "Free parking",
      "Free WiFi",
      "Fitness center",
      "Facilities for disabled guests",
      "Non-smoking rooms",
      "Tea/Coffee Maker in All Rooms",
      "Free parking",
      "Free WiFi",
      "Fitness center",
      "Facilities for disabled guests",
      "Non-smoking rooms",
      "Tea/Coffee Maker in All Rooms"
   ],
   "areaInfo":[
      {
         "What's nearby":[
            {
               "place":"Veteran's Park",
               "distance":"1.8 km"
            },
            {
               "place":"The Women's Park",
               "distance":"2.3 km"
            },
         ... and other nearby places
         ]
      },
      ... and other area info
   ],
   "link":"https://www.booking.com/hotel/us/holiday-inn-express-doral-miami.html?lang=en-us",
   "photos":[
      "https://cf.bstatic.com/xdata/images/hotel/max1024x768/277583459.jpg?k=da2b83dc681cb5c13878eb05d0bb838298092038e7d61b6df72a86631dcce9aa&o=&hp=1",
      "https://cf.bstatic.com/xdata/images/hotel/max1024x768/294863203.jpg?k=8ad830f1aa24e541616b02550d32d9da8018a5889730b509db4ad801981b6588&o=&hp=1",
      "https://cf.bstatic.com/xdata/images/hotel/max1024x768/277583540.jpg?k=ef279b7064a7b7c538f9e8fb437ef593fa4451f7186894af4ce9f4fd0602ecb0&o=&hp=1",
   ... and other photos
   ],
   "reviewsInfo":{
      "score":9,
      "scoreDescription":"Rated wonderful",
      "totalReviews":1419,
      "categoriesRating":[
         {
            "Staff":9.2
         },
         {
            "Facilities":9.2
         },
         {
            "Cleanliness":9.3
         },
         {
            "Comfort":9.3
         },
         {
            "Value for money":8.8
         },
         {
            "Location":9.2
         },
         {
            "Free WiFi":8.8
         }
      ],
      "reviews":[
         {
            "name":"Barbara",
            "avatar":"https://cf.bstatic.com/static/img/review/avatars/ava-b/8103dfb0481c4cedc201d849f5666a270512f538.png",
            "country":"Bermuda",
            "date":"April 3, 2023",
            "reting":"10",
            "review":[
               {
                  "liked":"Breakfast was very good.\nThe food was hot\nCoffee was good"
               },
               {
                  "didNotLike":"Nothing"
               }
            ],
            "hotelResponse":"Hello, thank you for your feedback! We are glad you enjoyed your stay."
         },
      ... and other reviews
      ]
   }
}

Hotels.com selected hotel info results.

First, we need to get hotels links:

hotelsCom.getHotels().then(console.log);

Output:

[
   {
      "title":"Hotel 10 Opera",
      "isAd":true,
      "location":"Paris",
      "snippet":{
         "title":"3* hotel located near the Opera",
         "text":"In the heart of the 9th district: customized offers according to the length of stay, flexibility & reinforced sanitary measures."
      },
      "paymentOptions":[
         "Fully refundable",
         "Reserve now, pay later"
      ],
      "highlightedAmenities":[

      ],
      "price":{
         "currency":"$",
         "value":274,
         "withTaxesAndCharges":305
      },
      "rating":{
         "score":8.8,
         "reviews":36
      },
      "link":"https://www.hotels.com/ho282954/hotel-10-opera-paris-france/"
   },
   ... and other hotels
]

And then we use the hotel link and get the hotel info result:

hotelsCom.getHotelInfo("https://www.hotels.com/ho282954/hotel-10-opera-paris-france/").then((result) => console.dir(result, { depth: null }));

Output:

{
   "title":"Hotel 10 Opera",
   "stars":3,
   "shortDescription":"City-center hotel within walking distance of Palais Garnier",
   "address":"10, Rue du Helder, Paris, Paris, 75009",
   "description":"Hotel 10 Opera offers a great location, putting you within a 15-minute walk of Palais Garnier and Louvre Museum. Also, Champs-Élysées and La Machine du Moulin Rouge are just a 5-minute drive away. Fellow travelers say great things about the local sightseeing and quiet guestrooms. The property is just a short walk to public transportation: Chaussee d'Antin - La Fayette Station is 3
minutes and Opéra Station is 3 minutes.",
   "languages":"English, French, Portuguese, Spanish",
   "roomOptions":[
      "Room",
      "Twin Room",
      "Double Room Single Use",
      "Family Room"
   ],
   "areaInfo":[
      {
         "What's nearby":[
            {
               "place":"In Paris City Center"
            },
            {
               "place":"Galeries Lafayette",
               "distance":"3 min walk"
            },
            ... and other nearby places
         ]
      },
      ... and other aria info
   ],
   "CleaningAndSafety":[
      {
         "Enhanced cleanliness measures":[
            "Disinfectant is used to clean the property",
            "High-touch surfaces are cleaned and disinfected",
            "Sheets and towels are washed at 60°C/140°F or hotter"
         ]
      },
    ... and other cleaning and safety options
   ],
   "atAGlance":[
      {
         "Hotel size":[
            "34 rooms",
            "Arranged over 6 floors"
         ]
      },
    ... and other at a glance options
   ],
   "propertyAmenities":[
      {
         "Food and drink":[
            "Buffet breakfast (surcharge) each morning 7 AM–10:30 AM",
            "Coffee/tea in a common area",
            "Room service (limited hours)"
         ]
      },
     ... and other property amenities
   ],
   "roomAmenities":[
      {
         "Be entertained":[
            "Television",
            "Satellite TV channels"
         ]
      },
     ... and other room amenities
   ],
   "specialFeatures":[],
   "feesAndPolicies":[
      {
         "Mandatory fees":[
            "A tax is imposed by the city: EUR 1.88 per person, per night. This tax does not apply to children under 18 years of age."
         ]
      },
    ... and other fees and policies
   ],
   "link":"https://www.hotels.com/ho282954/hotel-10-opera-paris-france/",
   "photos":[
      "https://images.trvl-media.com/lodging/2000000/1550000/1542800/1542774/b08e7322.jpg?impolicy=resizecrop&rw=1200&ra=fit",
      "https://images.trvl-media.com/lodging/2000000/1550000/1542800/1542774/8975206c.jpg?impolicy=resizecrop&rw=1200&ra=fit",
     ... and other photo
   ],
   "reviewsInfo":{
      "score":8.8,
      "scoreDescription":"Fabulous",
      "totalReviews":36,
      "categoriesRating":[
         {
            "Cleanliness":9.4
         },
        ... and more category rating
      ],
      "reviews":[
         {
            "date":"Mar 21, 2023",
            "reting":8,
            "reviewTitle":"Will stay here again!!",
            "review":"The hotel is located around around the corner of the Opera House and a 5 minute walk to Galerie Lafayette. It is 15 minutes walk to the Louvre. The location is perfect.  The hotel is
clean, safe and the staff is friendly. Free water, coffee and tea in reception. Two things to be aware of: 1. small, simple room and tiny bathroom (you get what you pay for) and 2. Across the street from hotel is a popular restaurant with people on street. The restaurant closes at midnight so noise is not a problem if you are in the front.  I plan to stay there again given the price, location, and service.",
            "hotelResponse":"Dear Karen,Thank you for your feedback, we are delighted to see that you enjoyed your stay! Please feel free to tell us what we could improve to deserve a 10!Looking forward to welcoming you again"
         },
        ... and other reviews
      ]
   }
}

Advanced Example

Find the cheapest hotels from all sites, get top 10 hotel info, and save the results.

Full code:

import { airbnb, booking, hotelsCom, saveToJSON } from "./index.js";

const getHotels = async (location, checkIn, checkOut) => {
  const hotels = [
    ...(await airbnb.getHotels(undefined, "USD", 50, location, checkIn, checkOut)),
    ...(await booking.getHotels(undefined, "USD", 50, location, checkIn, checkOut)),
    ...(await hotelsCom.getHotels(undefined, undefined, undefined, "United States", undefined, 50, location, checkIn, checkOut)),
  ];

  return hotels
    .sort((a, b) => {
      let aValue = a.price?.value;
      let bValue = b.price?.value;
      if (a.price?.taxesAndCharges || (a.price?.period && a.price?.period !== "night")) {
        aValue = a.price?.value / 10;
      }
      if (b.price?.taxesAndCharges || (b.price?.period && b.price?.period !== "night")) {
        bValue = b.price?.value / 10;
      }
      return aValue - bValue;
    })
    .slice(0, 10);
};

const getInfo = async (hotels) => {
  const results = [];
  for (const hotel of hotels) {
    const { link, price } = hotel;
    if (link.includes("airbnb.com")) {
      results.push({ ...(await airbnb.getHotelInfo(link)), price });
    } else if (link.includes("booking.com")) {
      results.push({ ...(await booking.getHotelInfo(link)), price });
    } else results.push({ ...(await hotelsCom.getHotelInfo(link)), price });
  }
  return results;
};

getHotels("Miami", "07/18/2023", "07/28/2023").then(getInfo).then(saveToJSON);

Explanation:

First, we get hotels from each website located in "Miami" and free from 07/18/2023 to 07/28/2023:

getHotels("Miami", "07/18/2023", "07/28/2023");

In getHotels function we get 50 hotels from each website, then sort them by price ascendent, and leave the 10 most cheap:

const getHotels = async (location, checkIn, checkOut) => {
  const hotels = [
    ...(await airbnb.getHotels(undefined, "USD", 50, location, checkIn, checkOut)),
    ...(await booking.getHotels(undefined, "USD", 50, location, checkIn, checkOut)),
    ...(await hotelsCom.getHotels(undefined, undefined, undefined, "United States", undefined, 50, location, checkIn, checkOut)),
  ];

  return hotels
    .sort((a, b) => {
      let aValue = a.price?.value;
      let bValue = b.price?.value;
      if (a.price?.taxesAndCharges || (a.price?.period && a.price?.period !== "night")) {
        aValue = a.price?.value / 10;
      }
      if (b.price?.taxesAndCharges || (b.price?.period && b.price?.period !== "night")) {
        bValue = b.price?.value / 10;
      }
      return aValue - bValue;
    })
    .filter((el, i) => i < 10);
};

After hotels are received and filtered, we call then function and give received hotels into getInfo function:

.then(getInfo)

In getInfo we iterate over all hotels, receive each hotel info, push them in results array and return it:

const getInfo = async (hotels) => {
  const results = [];
  for (const hotel of hotels) {
    const { link, price } = hotel;
    if (link.includes("airbnb.com")) {
      results.push({ ...(await airbnb.getHotelInfo(link)), price });
    } else if (link.includes("booking.com")) {
      results.push({ ...(await booking.getHotelInfo(link)), price });
    } else results.push({ ...(await hotelsCom.getHotelInfo(link)), price });
  }
  return results;
};

And the last one, we call then function and give received hotels into saveToJSON function:

.then(saveToJSON)

The result is the saved parsed_results.json file in your app directory with top-10 cheapest hotels with full description.

If you want other functionality added to this demo project or if you want to see some other projects made with SerpApi, write me a message.


Join us on Twitter | YouTube

Add a Feature Request💫 or a Bug🐞