# Agoda Scraper (`mlg14/agoda-scraper`) Actor

Extract public accommodation reviews, scores, stay details, and management replies from Agoda URLs. Filter by provider, rating, language, and date; export structured review records.

- **URL**: https://apify.com/mlg14/agoda-scraper.md
- **Developed by:** [MLG Data](https://apify.com/mlg14) (community)
- **Categories:** Travel, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Agoda Scraper

Scrape Agoda accommodation reviews from a hotel URL and export Agoda review data to JSON, CSV, or Excel. This Agoda API alternative collects public ratings, review text, stay details, reviewer labels, management replies, and the score breakdowns shown on the accommodation page.

Give the actor one accommodation URL, choose the review providers and optional filters, and set a result limit. Each dataset row represents one review. The fields are stable across runs, so you can compare new records with earlier exports by accommodation ID, provider ID, and review ID.

### What data can you extract from Agoda?

The output is one flat record per public review. Dates retain the timestamp and offset returned by the site. A value can be null when that review or accommodation does not expose it.

| Field | Description | Example |
| --- | --- | --- |
| hotelId | Numeric accommodation ID | 49487 |
| hotelName | Accommodation name | Ibis Budapest City |
| hotelUrl | Accommodation page URL | https://www.agoda.com/ibis-budapest-city/hotel/budapest-hu.html |
| reviewPageUrl | Public reviews page URL | https://www.agoda.com/ibis-budapest-city/reviews/budapest-hu.html |
| hotelReviewId | Review ID | 1064241007 |
| rating | Review rating on the displayed 0–10 scale | 8.8 |
| ratingText | Displayed rating label | Excellent |
| reviewTitle | Review headline | Excellent |
| reviewComments | Main comment section | One of the staff went out of the way... |
| reviewPositives | Positive feedback section | Comfortable room and helpful service |
| reviewNegatives | Negative feedback section | Slow check-in |
| reviewText | Nonempty comment sections joined into one text value | Overall, pleasant service! |
| reviewDate | Review publication timestamp | 2026-01-04T10:11:00+07:00 |
| checkInDate | Stay check-in timestamp | 2025-12-27T00:00:00+07:00 |
| checkOutDate | Stay check-out timestamp | 2025-12-31T00:00:00+07:00 |
| reviewerCountryName | Publicly displayed country | Singapore |
| reviewerName | Publicly displayed reviewer label | Amalina |
| reviewerGroupName | Traveler group | Group |
| reviewerRoomTypeName | Room type shown with the review | Room with 2 single beds |
| reviewerLengthOfStay | Stay length in nights | 4 |
| reviewProviderText | Displayed source of the review | Agoda |
| reviewProviderId | Numeric provider ID | 332 |
| reviewLanguage | Original review language code | en |
| responseDate | Management response timestamp | 2026-01-24T13:01:06+07:00 |
| responderName | Public responder label | Ibis Budapest City |
| responseText | Management reply | Thank you very much for your review... |
| originalTitle | Original headline when a translation is shown | null |
| originalComment | Original comment when a translation is shown | null |
| helpfulVotes | Helpful vote count | 0 |
| agodaAggregateReviewScore | Accommodation score from Agoda reviews | 8.4 |
| agodaAggregateDetailedReviewScore | Agoda category IDs and scores | cleanliness: 8.7 |
| bookingAggregateReviewScore | Accommodation score from Booking.com reviews | 8.7 |
| bookingAggregateDetailedReviewScore | Booking.com category IDs and scores | location: 9.0 |
| agodaReviewsCount | Agoda review total displayed on the page | 174 |
| bookingReviewsCount | Booking.com review total displayed on the page | 1758 |
| combinedReviewScore | Combined score displayed on the page | 8.6 |
| combinedReviewsCount | Combined review count displayed on the page | 2073 |

The two detailed score fields contain arrays of objects. Each object has an ID, such as cleanliness, facilities, location, roomComfort, staffPerformance, or valueForMoney, and a numeric score. Categories differ by property and provider. The count fields describe the accommodation page, not the number of rows in one run.

### How to scrape Agoda reviews

1. Open a public accommodation page and copy its URL. A normal URL contains /hotel/ and ends in .html. A public /reviews/ URL for the same property also works.
2. Paste that URL into **Accommodation URL**. Choose the maximum number of reviews. The default is 100; a smaller number is useful for a first export.
3. Keep both review providers selected, or select the provider you need. Add rating, language, and date filters if the downstream report needs a narrower set.
4. Run the actor. The dataset contains one row for each accepted review, up to the result limit and the records the public review service makes available.
5. Download the dataset in the format your workflow uses. Keep the review ID and provider ID when storing the records so repeated runs can be deduplicated.

The input accepts one property at a time. To monitor a portfolio, run it once per accommodation URL and combine the exported rows. Keeping each run tied to a single property also makes failures and unusually low result counts easier to investigate.

### Input

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| hotelUrl | String, required | None | Public Agoda accommodation or reviews URL. |
| maxReviewsPerHotel | Integer | 100 | Maximum rows to return. Zero continues until pagination ends, within a 100-page safety limit. |
| enabledProviders | Array | Agoda and Booking.com | Providers whose review rows may be returned. |
| rating | Array | Empty | Rating bands to keep: exceptional, excellent, very_good, good, below_expectation. Empty means all. |
| language | String | all | Original language code, such as en, de, or ko; all keeps every language. |
| cutoffDate | Date string | Empty | Keep reviews later than this YYYY-MM-DD date. The date itself is excluded. |
| sortBy | String | recent | recent, ratingHigh, ratingLow, or helpful. |
| aggregateRatings | Boolean | true | Include accommodation-level scores, breakdowns, and counts in every row. |
| proxyConfiguration | Object | Hosted proxy | Connection settings for the hosted run. |

A realistic request for feedback from both displayed providers is:

```
{
  "hotelUrl": "https://www.agoda.com/ibis-budapest-city/hotel/budapest-hu.html",
  "maxReviewsPerHotel": 80,
  "enabledProviders": ["Agoda", "Booking.com"],
  "sortBy": "recent",
  "language": "all",
  "aggregateRatings": true
}
```

Rating bands use the review's numeric score: exceptional is 9 or above; excellent is 8 to below 9; very_good is 7 to below 8; good is 6 to below 7; and below_expectation is below 6. Multiple selected bands are combined. Filters are applied to the returned records, so a request for a narrow band may yield fewer rows than the maximum even when the property has more reviews overall.

The language setting checks the original language reported with the review. A translated comment can therefore appear in English while its original language is another code. Use originalComment and originalTitle when the site supplies them and the original wording matters.

### Output example

This item is from a successful hosted run for the example accommodation. The management reply and category arrays are shortened here; the dataset row contains their complete values.

```
{
  "hotelId": 49487,
  "hotelName": "Ibis Budapest City",
  "hotelUrl": "https://www.agoda.com/ibis-budapest-city/hotel/budapest-hu.html",
  "reviewPageUrl": "https://www.agoda.com/ibis-budapest-city/reviews/budapest-hu.html",
  "hotelReviewId": 1064241007,
  "rating": 8.8,
  "ratingText": "Excellent",
  "reviewTitle": "Excellent",
  "reviewComments": "One of the staff went out of the way to attend to our queries. Overall, pleasant service! ",
  "reviewPositives": null,
  "reviewNegatives": null,
  "reviewText": "One of the staff went out of the way to attend to our queries. Overall, pleasant service!",
  "reviewDate": "2026-01-04T10:11:00+07:00",
  "checkInDate": "2025-12-27T00:00:00+07:00",
  "checkOutDate": "2025-12-31T00:00:00+07:00",
  "reviewerCountryName": "Singapore",
  "reviewerName": "Amalina",
  "reviewerGroupName": "Group",
  "reviewerRoomTypeName": "Room with 2 single beds equipped with the new b edding",
  "reviewerLengthOfStay": 4,
  "reviewProviderText": "Agoda",
  "reviewProviderId": 332,
  "reviewLanguage": "en",
  "responseDate": "2026-01-24T13:01:06+07:00",
  "responderName": "Ibis Budapest City",
  "responseText": "Thank you very much for your excellent review...",
  "originalTitle": null,
  "originalComment": null,
  "helpfulVotes": 0,
  "agodaAggregateReviewScore": 8.4,
  "agodaAggregateDetailedReviewScore": [{"id": "cleanliness", "score": 8.7}],
  "bookingAggregateReviewScore": 8.7,
  "bookingAggregateDetailedReviewScore": [{"id": "location", "score": 9.0}],
  "agodaReviewsCount": 174,
  "bookingReviewsCount": 1758,
  "combinedReviewScore": 8.6,
  "combinedReviewsCount": 2073
}
```

The hosted check returned 80 rows for this accommodation. All 80 had an accommodation ID, review ID, rating, publication date, displayed provider, reviewer label, accommodation name, and combined review text. These are observations from that run, not guarantees for every property or future date.

### Use cases

- A hotel operations team can review recent low scores and their associated comment text before deciding which service issue needs attention. The response fields show whether a public reply is already present.
- A travel researcher can compare public satisfaction across properties using the same numeric rating scale, while preserving provider IDs to avoid mixing sources without attribution.
- A reputation analyst can record daily review IDs, count newly observed comments, and separate new negative feedback from older records.
- A property manager can compare category scores for cleanliness, location, facilities, service, and value for money with the text of individual stays.
- A localization team can review original language codes and available original wording when translated comments are shown in a common display language.
- A data team can export tidy rows into a warehouse, join them to an accommodation table by hotelId, and use hotelReviewId with reviewProviderId as a repeat-run key.

Review data is best interpreted as customer feedback. A high score does not establish an objective measure of property quality, and the aggregate scores can change as new reviews are added or the site changes its display logic.

### How much does it cost to scrape Agoda?

The configured result price is **$1.00 per 1,000 delivered reviews**, or $0.001 per dataset row. The charge is based on rows delivered, so a limit of 100 is an upper bound of $0.10 for that run. A run that returns fewer rows has a lower result charge. Platform usage is included in the result price.

| Delivered reviews | Result charge |
| ---: | ---: |
| 80 | $0.08 |
| 500 | $0.50 |
| 1,000 | $1.00 |
| 5,000 across multiple runs | $5.00 |

These examples are arithmetic, not a promise that every property exposes that many records. In the hosted check, the 80-row request delivered 80 rows. If you are building a large collection, start with a smaller limit on a representative property and inspect both the row count and the provider mix before setting larger limits.

### Tips for best results

Use the property's public accommodation URL, including its city path and .html suffix. Search result pages and account pages are not accepted as accommodation URLs. The input removes query parameters, so referral codes and tracking fragments are unnecessary.

For regular monitoring, sort by most recent and save the last seen review IDs. The cutoffDate input can narrow results to reviews after a specific day, but the service may interleave review providers. Do not assume that one provider's first row is the newest record across every source. Compare dates in your stored data when building a single timeline.

If you only need one displayed provider, select it in enabledProviders. The actor still requests the public review feed and filters records before output, so a single-provider request may inspect pages containing records from other providers. A small result limit may therefore take more than one page to fill.

Use reviewText for full-text indexing because it includes the nonempty main, positive, and negative sections. Keep the separate sections if your analysis distinguishes praise from complaints. The site sometimes stores a Booking.com review mainly in positive and negative fields while leaving reviewComments empty.

Enable aggregateRatings when a report needs accommodation-level context next to each comment. Disable it when you do not need score values. The score fields become null in that case. Counts and scores refer to the page snapshot at run time and may change independently of individual review records.

### Limits

The actor reads public review data for one accommodation per run. It does not read booking accounts or private guest data. Some accommodations have no public comments, and some reviews omit a stay date, room name, country, response, or original-language text. Missing values are returned as null where possible.

The displayed total review count can be much larger than the rows exposed by the public paginated endpoint. On the tested accommodation, the page displayed more than two thousand combined reviews, while a limited request for 80 records succeeded. A probe of later pages returned empty responses, so a high maxReviewsPerHotel value must be treated as a cap, not an assurance that the cap is reached. The actor stops when the endpoint returns no comments or no new review IDs and has a 100-page safety boundary.

Rating, language, date, and provider filters are applied after pages are fetched. If the matching share is small, the result count can be below the requested maximum. Changing the sort order can expose a different subset, but it is not guaranteed to unlock all historical reviews.

A successful HTTP response can occasionally contain no comments even when the property has reviews. The actor retries an empty page with a fresh connection. If the first page is still empty, it checks a second public review endpoint and then rotates the proxy connection once before failing. A property whose page explicitly lists zero reviews can finish with an empty dataset. An empty response for a property that lists reviews is not silently reported as a completed scrape.

### Automated workflows

A scheduled run can write each new review to a review table keyed by hotelId, reviewProviderId, and hotelReviewId. A second process can compare reviewDate against the previous run, route low ratings for manual review, and retain the unmodified text fields for auditability. Use the dataset export endpoint when your workflow needs JSON or CSV, and use a webhook to react when a run finishes.

For a portfolio, keep a list of accommodation URLs in your own workflow and start one run for each URL. Record the input URL, start time, run status, and delivered row count. Those four values make it easier to detect a property whose public page changed or whose review feed temporarily returned fewer records.

### FAQ

#### Can I scrape Agoda reviews without a login?

Yes. This actor uses public accommodation and review pages. It does not ask for account credentials, booking references, or private booking information. The hotel URL must be publicly reachable.

#### Can I include both Agoda and Booking.com reviews?

Yes. Both displayed providers are selected by default. Each row includes reviewProviderText and reviewProviderId so you can separate them later. The provider totals in the aggregate fields are page-level counts, while the dataset contains only records returned during this run.

#### Why is reviewComments empty on some records?

Some reviews put their feedback in reviewPositives and reviewNegatives. reviewText combines available sections for a convenient searchable field. Keep the separate fields if the distinction matters to your analysis.

#### Why did I receive fewer reviews than maxReviewsPerHotel?

The maximum is a stop limit. Public pagination can end before it is reached, and optional filters discard nonmatching records. A property can also have fewer public comments than its displayed aggregate review count. Check the provider mix, try another sort order, or use a less restrictive filter when you need a broader sample.

#### What does language filter?

It checks the original language code attached to the review. A comment can be displayed in translation while reviewLanguage still records the source language. Original wording is included only when the site provides originalTitle or originalComment.

#### Can I schedule exports or open the results in a spreadsheet?

Yes. Schedule runs at the interval you need, then download the dataset as CSV or Excel. Retain the two review identifiers and the accommodation identifier so later exports can be merged without double-counting.

#### Is it legal to use the data?

The actor reads public information. You are responsible for checking the site's terms, applicable privacy rules, and the permitted use of review and reviewer data in your jurisdiction. Avoid using public reviewer labels to identify or contact individuals.

#### Do I need to configure a proxy?

The hosted run uses its configured connection automatically. Most users can keep the default. If a page or endpoint temporarily returns no records, the actor has a first-page recovery path and reports a failure when recovery does not produce records.

### Integrations

Export the default dataset to JSON, CSV, or Excel; retrieve it through the dataset API; or trigger a downstream workflow when a run finishes. Scheduling supports recurring monitoring. For spreadsheet reports, keep one sheet for raw rows and another for property-level summaries so repeated aggregate scores are not counted as separate accommodations.

### Support

Open an issue on the Issues tab; we reply within 24h and add fields on request.

# Actor input Schema

## `hotelUrl` (type: `string`):

Public Agoda accommodation URL ending in /hotel/...html or /reviews/...html.

## `maxReviewsPerHotel` (type: `integer`):

Maximum review records to return. Zero reads until public pagination ends, up to 100 pages.

## `enabledProviders` (type: `array`):

Include reviews from these displayed providers.

## `rating` (type: `array`):

Include only these rating bands on the 0–10 scale; leave empty for all ratings.

## `language` (type: `string`):

Original review language code, such as en, de, or ko. Use all for every language.

## `cutoffDate` (type: `string`):

Return only reviews after this date, in YYYY-MM-DD form. Leave blank for all dates.

## `sortBy` (type: `string`):

Order supplied by the review service.

## `aggregateRatings` (type: `boolean`):

Add provider scores, category breakdowns, and review counts to every record.

## `proxyConfiguration` (type: `object`):

Connection settings for requests made from the hosted run.

## Actor input object example

```json
{
  "hotelUrl": "https://www.agoda.com/ibis-budapest-city/hotel/budapest-hu.html",
  "maxReviewsPerHotel": 40,
  "enabledProviders": [
    "Agoda",
    "Booking.com"
  ],
  "rating": [],
  "language": "all",
  "sortBy": "recent",
  "aggregateRatings": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `reviews` (type: `string`):

All extracted review records in the default dataset.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "hotelUrl": "https://www.agoda.com/ibis-budapest-city/hotel/budapest-hu.html",
    "maxReviewsPerHotel": 40,
    "enabledProviders": [
        "Agoda",
        "Booking.com"
    ],
    "language": "all",
    "cutoffDate": "",
    "aggregateRatings": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("mlg14/agoda-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "hotelUrl": "https://www.agoda.com/ibis-budapest-city/hotel/budapest-hu.html",
    "maxReviewsPerHotel": 40,
    "enabledProviders": [
        "Agoda",
        "Booking.com",
    ],
    "language": "all",
    "cutoffDate": "",
    "aggregateRatings": True,
}

# Run the Actor and wait for it to finish
run = client.actor("mlg14/agoda-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "hotelUrl": "https://www.agoda.com/ibis-budapest-city/hotel/budapest-hu.html",
  "maxReviewsPerHotel": 40,
  "enabledProviders": [
    "Agoda",
    "Booking.com"
  ],
  "language": "all",
  "cutoffDate": "",
  "aggregateRatings": true
}' |
apify call mlg14/agoda-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mlg14/agoda-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/PpulNfWIcDy1dsfWF/builds/CpIzmowlMSoiK4ToG/openapi.json
