Arcadium Data Scrapers
Home / Guides / Hotel Reviews Scraper

How to use Hotel Reviews Scraper

Step by step: in the Apify Store, over the API, from JavaScript or Python, in n8n, and from AI agents through MCP or LangChain.

$4.50 per 1,000 reviews on the Free plan, down to $0.80 on higher plans (live Apify price on 2026-10-31).

1. In the Apify Store (no code)

  1. Open arcadium/hotel-reputation and sign in to Apify (a free account works).
  2. Fill in the input form. The fields below are the ones the example uses.
  3. Set a maximum cost per run in the run options if you want a hard cap.
  4. Start the run. Rows appear in the Output tab as they arrive.
  5. Export the dataset as JSON, CSV or Excel, or connect an integration.
Input fieldTypeDescription
hotelsarrayHotel names, or objects that tie one hotel's pages together. A plain name is looked up on Tripadvisor (and Google Hotels with 'Also search Google by name'); Booking, Agoda and Trip.com have no name search, so for them or Google use the object form, for example {"name": "The Savoy", "bookingUrl": "https://www.booking.com/hotel/gb/the-savoy.html"}. Object fields: name, tripadvisorUrl, bookingUrl, bookingId, googleUrl, agodaUrl, agodaId, tripcomUrl, tripcomId.
maxReviewsPerSourceintegerMaximum number of reviews to collect from each site for each hotel (up to 50,000). Reviews are always read newest first. Booking shows about the last three years. Also accepted as maxItemsPerQuery, maxReviewsPerHotel or maxReviews (other hotel review actors). Default 100.

Example input:

{
  "hotels": [
    {
      "name": "The Savoy London",
      "bookingUrl": "https://www.booking.com/hotel/gb/the-savoy.html"
    }
  ],
  "maxReviewsPerSource": 20
}

2. Over the Apify API

The synchronous endpoint runs the actor and returns its rows in one call (for runs up to about five minutes; use the asynchronous run endpoint for longer ones).

curl -X POST "https://api.apify.com/v2/acts/arcadium~hotel-reputation/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"hotels":[{"name":"The Savoy London","bookingUrl":"https://www.booking.com/hotel/gb/the-savoy.html"}],"maxReviewsPerSource":20}'

3. From JavaScript

// npm install apify-client
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('arcadium/hotel-reputation').call({
  "hotels": [
    {
      "name": "The Savoy London",
      "bookingUrl": "https://www.booking.com/hotel/gb/the-savoy.html"
    }
  ],
  "maxReviewsPerSource": 20
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);

4. From Python

# pip install apify-client
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("arcadium/hotel-reputation").call(run_input={
    "hotels": [{
        "name": "The Savoy London",
        "bookingUrl": "https://www.booking.com/hotel/gb/the-savoy.html",
    }],
    "maxReviewsPerSource": 20,
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)

5. In n8n

Paste this node into a workflow (it is a plain HTTP Request node). Create a Header Auth credential with name Authorization and value Bearer <APIFY_TOKEN>; each returned row becomes one item for the next node.

{
  "nodes": [
    {
      "name": "Run arcadium/hotel-reputation",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        0,
        0
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.apify.com/v2/acts/arcadium~hotel-reputation/run-sync-get-dataset-items",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "{\"hotels\":[{\"name\":\"The Savoy London\",\"bookingUrl\":\"https://www.booking.com/hotel/gb/the-savoy.html\"}],\"maxReviewsPerSource\":20}",
        "options": {
          "timeout": 300000
        }
      }
    }
  ],
  "connections": {}
}

6. From an AI assistant (MCP)

Add Apify’s MCP server to Claude Desktop, Cursor, VS Code or any MCP client, limited to this actor:

{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com/?tools=arcadium/hotel-reputation",
      "headers": {
        "Authorization": "Bearer <APIFY_TOKEN>"
      }
    }
  }
}

More on the MCP page.

7. From LangChain

# pip install langchain-apify   (reads APIFY_API_TOKEN from the environment)
from langchain_apify import ApifyActorsTool

tool = ApifyActorsTool("arcadium/hotel-reputation")
rows = tool.invoke({"run_input": {
    "hotels": [{
        "name": "The Savoy London",
        "bookingUrl": "https://www.booking.com/hotel/gb/the-savoy.html",
    }],
    "maxReviewsPerSource": 20,
}})
# or give `tool` to an agent: create_react_agent(llm, [tool])

Sample output

One row, from the example in the actor’s README. Long text is shortened and lists are cut to two items.

{
  "platform": "booking",
  "reviewId": "Booking___c6397d1e2a854aa3",
  "productName": "The Savoy",
  "date": "2026-09-30",
  "title": "Wonderful stay",
  "pros": "Staff and location.",
  "cons": "Breakfast queue.",
  "ratingOverall": 4.5,
  "language": "en",
  "country": "US",
  "extras": {
    "rawRating": 9,
    "rawScale": 10,
    "score10": 9,
    "travelerType": "COUPLES"
  }
}
FieldWhat it holds
rowTypeKind of row. Filter on this first. Values: 'review': one review (charged). 'hotel-summary': one row per hotel with its overall ratings (free). 'coverage': stated total vs delivered and why reading stopped (free). 'error': an input could not be read; see code and message (free).
platformSource site, lower case, e.g. 'tripadvisor', 'capterra', 'googleplay', 'appstore'.
productNameName of the reviewed hotel, business, product or app.
productUrlPage of the reviewed hotel, business, product or app on the source site.
entityIdThe site's own id of the reviewed entity (place id, product id, app id).
reviewIdStable review id in the form '<Platform>___<native id>'. Use it to deduplicate between runs.
reviewIdSource'native' when the id comes from the site, 'derived' when it is a hash of the content (less stable).
reviewUrlLink to the single review, null when the site has none.
titleReview headline, null when the site has none.
textReview text in the language it was written in. Null when the review has no text.
prosWhat the reviewer liked, for sites that ask separately (Capterra, TrustRadius, Glassdoor, Booking). Null otherwise.
consWhat the reviewer disliked, for sites that ask separately. Null otherwise.
ratingOverallOverall rating on a 0 to 5 scale (Booking: its 1-10 score divided by 2, original in extras.score10). Null when missing.
ratingsSub-ratings as an object, e.g. { value: 5, cleanliness: 4 }. Keys depend on the site; empty object when there are none.
dateDate the review was written, ISO format yyyy-mm-dd. Null if unknown.
reviewerRoleJob title or role the reviewer states. No names. Null if not given.
reviewerIndustryIndustry the reviewer states. Null if not given.
reviewerCompanySizeCompany size bracket of the reviewer, e.g. '51-200 employees'. Null if not given.
reviewerTimeUsedHow long the reviewer used the product, as the site words it. Null if not given.
switchedFromProduct the reviewer switched from, null if not given.
alternativesConsideredAlternatives the reviewer considered, comma-separated, null if not given.
reasonForChoosingWhy the reviewer chose this product, free text, null if not given.
reasonForSwitchingWhy the reviewer left the previous product, free text, null if not given.
adviceToOthersAdvice the reviewer gives others (Glassdoor: advice to management), null if not given.
incentivizedTrue when the site marks the review as incentivized (for example a gift card). Null when the site does not say.
incentiveThe site's raw incentive label, null if none.
reviewerVerifiedTrue when the site marks the review as verified. Null when the site does not say.
vendorResponseReply of the owner, vendor, developer or employer, null when there is none.
helpfulVotesNumber of helpful or useful votes, null when the site shows none.
languageLanguage of the review text as a two-letter code, null if unknown.
countryCountry of the reviewer or the storefront where it applies (lower case code), otherwise null. No personal data.
productSummary of the reviewed entity repeated on each row: { platform, entityId, name, url, rating, reviewCount, category, vendor, website }. rating and reviewCount are the site's overall figures.
extrasSite-specific extras, e.g. trip type, visit date, translated text, photo URLs, owner reply date. Keys are listed in the README.
scrapedAtISO timestamp of when the row was collected.
statedCoverage rows: total the site states for the entity, null when it does not say.
deliveredCoverage rows: number of rows delivered for the entity in this run.
skippedSeenCoverage rows: items skipped because an earlier run already delivered them (only-new mode).
duplicatesCoverage rows: repeats the site returned that were dropped.
stopReasonCoverage rows: why reading stopped. limit (your maximum reached), end (no more pages), dateFrom (older than your date), onlyNew (reached already delivered items), budget (run charge limit), cap (site-side depth limit), error (ended by a failure), filter (ended with a filter active).
capSourceCoverage rows: who limited the result, 'user' (your maximum or budget) or 'site' (a limit of the website). Null if nobody.
shortfallCoverage rows: true when fewer rows were delivered than the site states although no maximum or filter was set.
inputError and coverage rows: the input (URL, id or search text) the row is about. Example: 'https://www.example.com/page'.
codeError rows: machine-readable reason. One of invalid_input (fix the input), not_found (page does not exist), blocked (site refused, retry later), unavailable (temporary error, retry later), filter_unsupported (the filter cannot be applied on this site), no_match (name search found nothing).
messageError rows: short human-readable explanation. Do not parse it, use code.
hotelName'hotel-summary' rows: the hotel name.
tripadvisor'hotel-summary' rows: Tripadvisor rating 1-5, review count, reviews per star and how the hotel was matched.
booking'hotel-summary' rows: Booking score10, rating (score / 2), review count, stars, city and categoryScores with the city range and vsCity (above, within, below).
google'hotel-summary' rows: Google Hotels rating 1-5, number of Google reviews, reviews per star, topic breakdown (mentions, positive, negative per topic) and how the hotel was matched.
agoda'hotel-summary' rows: Agoda's own guests: score10, rating (score / 2), number of ratings, stars, city, categoryScores with the city average, and the combined score of every provider Agoda shows.
tripcom'hotel-summary' rows: Trip.com score10, rating (score / 2), review count, category scores (location, facilities, service, cleanliness), positive and negative counts and the advertised from-price.
combined'hotel-summary' rows: the ratings of all requested sites weighted by review count, when two or more sites were read.

Schedule it

In Apify Console, create a Task from your input and add a Schedule (for example daily). Each run writes a new dataset; integrations can push the rows to Google Sheets, Slack, a webhook or your database.