# Hrs Hotels Reviews Scraper (`reviewly/hrs-hotels-reviews-scraper`) Actor

Scrape guest reviews from any HRS hotel to JSON, CSV or Excel. Exports review text, 1-10 ratings, per-category scores, traveller type, dates and hotel replies — no login or API key needed. Sweeps all seven HRS locales so you get every published review, not just the ones in one language.

- **URL**: https://apify.com/reviewly/hrs-hotels-reviews-scraper.md
- **Developed by:** [Reviewly](https://apify.com/reviewly) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 reviews

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## HRS Hotel Reviews Scraper — Export Guest Reviews to JSON, CSV & Excel

**Scrape every published guest review from any hotel on HRS.com and HRS.de — review text, 1–10 ratings, 12 category scores, traveller type, dates and hotel replies — in seconds. No login, no API key, no cookies.**

- ✅ **Gets reviews other scrapers miss** — HRS splits reviews across 7 language sites. This Actor sweeps all of them and merges the results.
- ✅ **Rich data, not just stars** — per-category scores (cleanliness, breakfast, beds…), traveller segments, and management responses.
- ✅ **Fast and cheap** — about 3.5 seconds per hotel, no browser, no CAPTCHA solving.
- ✅ **Honest counts** — clear reporting of what HRS actually publishes vs. the headline rating number.
- ✅ **Export anywhere** — JSON, CSV, Excel, XML, or straight into your app via API.

***

### 📌 What This Actor Does

[HRS](https://www.hrs.com) is one of Europe's largest hotel booking platforms, especially dominant in business travel across Germany, Austria and Switzerland. Its guest reviews are detailed — every review carries a 1–10 score plus up to 12 individual category ratings — but HRS offers no public API and no way to export them.

This **Apify Actor** does that for you. Give it a hotel URL or hotel ID, and it returns a clean, structured dataset of that hotel's reviews and profile.

#### Who it's for

| You are… | You use it to… |
| --- | --- |
| **Hotel & revenue managers** | Track your property's reputation and benchmark against competitors |
| **Hospitality groups & chains** | Monitor reviews across an entire portfolio in one run |
| **Market researchers & analysts** | Build datasets on European hotel quality and guest sentiment |
| **Data scientists & AI teams** | Feed real multilingual review text into sentiment models and LLMs |
| **Travel tech & OTA developers** | Enrich listings with review data HRS doesn't expose via API |
| **Agencies & consultants** | Produce reputation audits and competitor reports for clients |

***

### ✨ Key Features

- **Complete review coverage.** Sweeps all 7 HRS language sites (German, English, Spanish, French, Italian, Dutch, Polish) and merges them into one deduplicated set.
- **Full review detail.** Positive comment, negative comment, 1–10 score, recommendation flag, traveller type, and exact timestamp.
- **Category-level ratings.** Up to 12 scores per review: cleanliness, value for money, breakfast, bed quality, room size, bathroom, reception friendliness, staff service, atmosphere, room layout, restaurant and wellness.
- **Hotel management replies.** Captures the hotel's public response where one exists.
- **Complete hotel profile.** Name, star rating, full address, GPS coordinates, image, average score, recommendation rate, and rating breakdowns by traveller segment.
- **Bulk scraping.** Process one hotel or hundreds in a single run.
- **Incremental runs.** `targetDate` collects only reviews newer than your last run.
- **Stable review IDs.** Every review gets a deterministic ID so you can deduplicate across runs.
- **Analysis-ready output.** Split *and* combined review text, so it works with CSV tools and AI pipelines alike.
- **No blocking.** No CAPTCHAs, no login walls, no anti-bot workarounds needed.

***

### 🧠 Why This Actor Is Different

#### It solves the problem most HRS scrapers get wrong

**An HRS hotel page only shows the reviews written on that language's website.**

Take hotel `391864`. The English page shows **2 reviews**. If your scraper reads that page — which is the obvious thing to do — that's all you get. The German page for the same hotel has **32**. The real total is **34**.

Scraping one page returns **6% of the available reviews** for that hotel. This Actor fetches every language site and merges them, so you get all 34.

#### It tells you the truth about review counts

HRS displays "67 ratings" for that hotel, but only 34 are published anywhere, in any language. The other 33 are score-only submissions with no written comment, plus ratings older than HRS's ~24-month display window. **HRS never serves them — to anyone.**

Rather than quietly returning 34 and letting you wonder what broke, this Actor reports both numbers (`totalRatings` and `reviewsScraped`) and explains the gap in the run log. No guesswork.

#### Built on the data layer, not fragile HTML

It reads HRS's own structured server data rather than scraping rendered HTML with CSS selectors. That means cleaner values, no formatting artifacts, and far less breakage when HRS redesigns its pages.

#### Fast and lightweight

No headless browser, so no browser overhead and no CAPTCHA-solving costs. A 5-hotel run with 67 reviews completes in under 18 seconds.

***

### ⚙️ Input Configuration

| Field | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `startUrls` | Array | One of these | – | HRS hotel page URLs. Any language version works. |
| `hotelIds` | Array | One of these | – | Numeric HRS hotel IDs. Use instead of, or alongside, `startUrls`. |
| `languages` | Array | No | *all 7* | Restrict to specific language sites. **Leave empty for complete results.** |
| `maxReviews` | Integer | No | `0` | Max reviews per hotel, newest first. `0` = unlimited. |
| `targetDate` | String | No | – | Only reviews on or after this date (`YYYY-MM-DD`). |
| `proxyConfiguration` | Object | No | – | Optional. HRS does not block requests, so this is rarely needed. |

#### Basic example — one hotel, all reviews

```json
{
  "startUrls": [{ "url": "https://www.hrs.com/en/hotel/391864" }]
}
```

#### Bulk example — several hotels

```json
{
  "hotelIds": ["391864", "60754", "464662"],
  "maxReviews": 0
}
```

#### Incremental example — only new reviews since your last run

```json
{
  "hotelIds": ["391864"],
  "targetDate": "2025-06-01"
}
```

#### 💡 Tips for best results

- **Leave `languages` empty.** Restricting it is the single most common way to end up with an incomplete dataset. Only narrow it if you specifically want reviews from one market.
- **Where to find the hotel ID:** it's the number at the end of the URL — `hrs.com/en/hotel/`**`391864`**. Both `hrs.com` and `hrs.de` URLs work.
- **`maxReviews` doesn't reduce cost or runtime.** HRS sends a whole language's reviews in one page load, so the Actor still fetches everything and then trims. Use it to cap dataset size, not to run faster.
- **Run daily or weekly with `targetDate`** set to your last run date for efficient reputation monitoring.
- **Skip the proxy** unless you're running very large batches.

***

### 📤 Output Format

The Actor stores **one dataset record per hotel**, with that hotel's reviews nested inside it. Export as JSON, CSV, Excel, XML, or fetch via the Apify API.

```json
{
  "hotelId": 391864,
  "name": "Hotel Brandenburger Tor Potsdam",
  "url": "https://www.hrs.com/en/hotel/391864",
  "stars": 4,
  "street": "Brandenburger Str. 1 - 3",
  "city": "Potsdam",
  "postalCode": "14467",
  "country": "DEU",
  "latitude": 52.399619,
  "longitude": 13.048601,
  "imageUrl": "https://foto.hrsstatic.com/fotos/...jpg",
  "averageRating": 9.1,
  "ratingMax": 10,
  "totalRatings": 67,
  "recommendationRate": 97,
  "categoryAverages": {
    "hotelTidiness": 9.2,
    "friendlinessOfReception": 9.4,
    "breakfastService": 9.3,
    "pricePerformanceRatio": 8.8
  },
  "ratingsByTravellerType": [
    { "travellerType": "BUSINESS", "travellerTypeLabel": "Business traveller", "rating": 9, "recommendationRate": 94.7 }
  ],
  "languagesScraped": ["de", "en", "es", "fr", "it", "nl", "pl"],
  "reviewsScraped": 34,
  "reviewsAvailable": 34,
  "reviews": [
    {
      "reviewId": "5cc88d4ec4709c48",
      "rating": 9.1,
      "ratingMax": 10,
      "recommended": true,
      "reviewerName": null,
      "travellerType": "BUSINESS",
      "travellerTypeLabel": "Business traveller",
      "date": "2024-11-27T08:34:31.260Z",
      "siteLanguage": "de",
      "positiveComment": "freundliches Personal",
      "negativeComment": null,
      "text": "freundliches Personal",
      "hotelReply": "Lieber Gast, vielen Dank für Ihr Feedback! ...",
      "categoryRatings": { "hotelTidiness": 10, "qualityOfBeds": 9 },
      "sourceUrl": "https://www.hrs.com/de/hotel/391864"
    }
  ]
}
```

#### Hotel fields

| Field | Type | Description |
| --- | --- | --- |
| `hotelId` | Number | HRS hotel ID |
| `name` | String | Hotel name |
| `url` | String | Canonical HRS hotel page |
| `stars` | Number | Official star rating (1–5) |
| `street`, `city`, `postalCode`, `country` | String | Full address |
| `latitude`, `longitude` | Number | GPS coordinates |
| `imageUrl` | String | Main hotel photo |
| `averageRating` | Number | Overall guest score, **out of 10** |
| `totalRatings` | Number | All ratings HRS counts, including unpublished ones |
| `recommendationRate` | Number | % of guests who would recommend |
| `categoryAverages` | Object | Hotel-wide average per category |
| `ratingsByTravellerType` | Array | Score and recommendation rate per guest segment |
| `languagesScraped` | Array | Language sites successfully visited |
| `reviewsScraped` | Number | Reviews in this record |
| `reviewsAvailable` | Number | Reviews found before `maxReviews` trimmed them |

#### Review fields

| Field | Type | Description |
| --- | --- | --- |
| `reviewId` | String | Stable ID, consistent across runs — use it to deduplicate |
| `rating` | Number | Guest's overall score, **out of 10** (`ratingMax`) |
| `recommended` | Boolean | Whether the guest recommends the hotel |
| `reviewerName` | String | null | Display name; `null` when posted anonymously |
| `travellerType` | String | `BUSINESS`, `PRIVATE`, `MATURE_COUPLE`, `YOUNG_COUPLE`, … |
| `travellerTypeLabel` | String | Human-readable version of the above |
| `date` | String | ISO 8601 timestamp |
| `siteLanguage` | String | Which HRS language site the review came from |
| `positiveComment` | String | null | What the guest liked |
| `negativeComment` | String | null | What the guest disliked |
| `text` | String | null | Both comments combined — convenient for CSV and AI tools |
| `hotelReply` | String | null | The hotel's public response, if any |
| `categoryRatings` | Object | This guest's individual category scores |
| `sourceUrl` | String | Page the review was read from |

#### ⚠️ Three things worth knowing about the data

1. **Ratings are out of 10, not 5.** HRS uses a 1–10 scale. The `ratingMax` field carries this so nothing downstream has to assume.
2. **Reviews are split in two.** HRS asks guests separately what they liked and disliked, so `positiveComment` and `negativeComment` are distinct fields and either can be empty. The `text` field joins them for convenience.
3. **`siteLanguage` is the source site, not detected language.** It tells you which HRS site a review came from — usually but not always the language of the text. (The English site sometimes carries German-language reviews.) Don't use it as a language label for the review body.

***

### ▶️ How to Use

#### Step 1 — Find your hotel

Search on [hrs.com](https://www.hrs.com) and open the hotel's page. Copy the URL, for example:

```
https://www.hrs.com/en/hotel/391864
```

#### Step 2 — Add it to the input

Paste the URL into **Start URLs**, or just the number (`391864`) into **Hotel IDs**. Add as many hotels as you like.

#### Step 3 — Run it

Click **Start**. Most hotels finish in a few seconds.

#### Step 4 — Export

Open the **Dataset** tab and download as **JSON, CSV, Excel or XML**, or pull it via API:

```
https://api.apify.com/v2/datasets/<DATASET_ID>/items?format=csv
```

#### Common workflows

**Monitor your hotel weekly**
Schedule the Actor to run every Monday with `targetDate` set to the previous week. You'll only collect new reviews.

**Benchmark against competitors**
Put your hotel and 5–10 nearby competitors into `hotelIds` in one run, then compare `averageRating` and `categoryAverages` to see exactly where you win or lose.

**Build a training dataset**
Run across hundreds of hotel IDs with `maxReviews: 0` to build a large multilingual review corpus with matching category scores.

***

### 📈 Use Cases

#### 1. Hotel reputation monitoring

Track your score over time, catch negative reviews within days, and confirm your team is replying. `categoryAverages` shows precisely which areas — breakfast, cleanliness, value — are pulling your score down.

#### 2. Competitor benchmarking

Scrape every competing hotel in your city and compare average scores, recommendation rates, and category strengths. Discover that competitors consistently beat you on breakfast but lose on location — then price accordingly.

#### 3. Market research & travel analytics

Build datasets covering entire cities or regions. Because HRS is strong in DACH business travel, it's a valuable source for analysing the European corporate hotel market specifically.

#### 4. AI & sentiment analysis

Feed real multilingual guest feedback into sentiment models, topic clustering, or LLM pipelines. Each review arrives with a numeric score and category ratings attached — ideal labelled training data.

#### 5. Due diligence & property investment

Assessing a hotel acquisition or franchise deal? Pull its full review history to check guest satisfaction trends, recurring complaints, and how management handles criticism.

***

### 🛠️ Advanced Tips

**Performance.** The Actor makes 7 requests per hotel (one per language site) — roughly 3.5 seconds each. A 100-hotel run takes around 6 minutes. Runtime scales with hotel count, not review count.

**Cutting requests.** If you only need German-market reviews, set `languages: ["de"]` to make one request per hotel instead of seven. You'll be ~7× faster but you **will** miss reviews from other sites.

**Proxies.** HRS doesn't block scraping, so no proxy is needed for normal use. For very large batches, enable Apify Proxy (residential or datacenter) to spread requests across IPs. The Actor automatically retries with a fresh IP if a request fails.

**Scaling to thousands of hotels.** Split hotel IDs across several runs rather than one huge run — smaller runs are easier to monitor, retry, and resume.

**Deduplicating across runs.** Use `reviewId` as your primary key. It's derived from the review's content and stays identical across runs and language sites.

**Scheduling.** Use Apify **Schedules** for automated monitoring, and **Webhooks** to push new reviews straight into Slack, a database, or Google Sheets.

***

### ❓ FAQ & Troubleshooting

**Why is `reviewsScraped` lower than `totalRatings`?**
This is expected and correct. `totalRatings` counts every score the hotel ever received; HRS only publishes reviews that have written text and fall inside its ~24-month display window. Everything HRS makes public is in your dataset — the rest isn't accessible to anyone, including regular website visitors.

**The Actor returned very few reviews. Did it fail?**
Probably not — some hotels genuinely have few published reviews. Check `languagesScraped` in the output. If it lists all 7 languages, the scrape was complete. Also confirm you didn't restrict `languages` or set a `targetDate` that filters out most reviews.

**Why does it visit 7 URLs for one hotel?**
Because HRS only shows reviews on the language site where they were written. Visiting all 7 is the only way to get a complete set. (For hotel 391864, the English page alone would return 2 of 34 reviews.)

**Can I get reviews in one specific language?**
Yes — set `languages` to e.g. `["de"]`. Note this selects the *site* the review was posted on, which is usually but not always the language of the text.

**Do I need a proxy?**
No. HRS serves these pages without blocking. Proxies are optional and mainly useful for very large runs.

**Do I need an HRS account or API key?**
No. The Actor uses only publicly available data.

**"Hotel does not exist on HRS (404 on every locale)"**
The hotel ID is wrong or the property has been removed. Open the URL in your browser to confirm. The Actor skips it and continues with your other hotels.

**Can I scrape hotel prices or availability?**
No. This Actor focuses on reviews and hotel profile data. Prices depend on dates and occupancy and are out of scope.

**Why are some reviews missing a reviewer name?**
HRS lets guests post anonymously. Those come through as `reviewerName: null`.

**What about GDPR and legal use?**
The Actor collects only publicly available review data — no personal contact details or private information. Reviewer names appear as HRS publishes them (typically a first name and last initial). You are responsible for using the data in line with applicable laws and Apify's Terms of Service.

**Can I export to Google Sheets or a database?**
Yes. Use Apify's built-in integrations, webhooks, or the Dataset API. CSV export works directly with Sheets and Excel.

***

### 💰 Pricing

This Actor uses **pay-per-event** pricing: you're charged per review successfully scraped and stored. Hotels that fail to scrape cost you nothing, and hotels with no reviews are free.

Apify platform usage (compute, proxy) is billed separately under your plan.

***

### 📞 Support

Questions, bug reports, or feature requests are welcome.

- **Email:** <me@ahmedhrid.com>
- **Issues:** Use the **Issues** tab on this Actor's page in Apify Console

If you're reporting a problem, please include the hotel URL or ID and your run ID — it makes diagnosis much faster.

Need a scraper for a different booking platform, or a customised version of this one? Get in touch.

***

### 🔗 Related Keywords

HRS scraper · HRS.com scraper · HRS.de scraper · hotel reviews scraper · hotel review data · guest review scraping · hotel reputation monitoring · web scraping · Apify actor · travel data extraction · hospitality market research · European hotel data · booking platform scraper · hotel ratings API alternative

# Actor input Schema

## `startUrls` (type: `array`):

HRS hotel page URLs, e.g. https://www.hrs.com/en/hotel/391864 or https://www.hrs.de/de/hotel/391864. Any locale of the same hotel works — the scraper reads the hotel ID out of the URL and then visits every language itself. Leave empty if you are using Hotel IDs instead.

## `hotelIds` (type: `array`):

HRS numeric hotel IDs, one per line, e.g. 391864. The hotel ID is the number at the end of a hotel URL: hrs.com/en/hotel/<HOTEL ID>. Use this instead of Start URLs, or alongside them — a hotel listed in both is only scraped once.

## `languages` (type: `array`):

Which HRS language sites to collect from. Leave empty to sweep all of them, which is what you want in almost every case: an HRS hotel page only shows the reviews written in that page's language, so the complete review set is the union of all seven. Restricting this is for when you only want reviews in specific languages.

## `maxReviews` (type: `integer`):

Maximum number of reviews to keep per hotel, newest first. 0 means no limit. This trims the result after collection — it does not make the run cheaper, because HRS ships a whole language's reviews in one page load.

## `targetDate` (type: `string`):

Skip reviews posted before this date (ISO 8601, e.g. 2025-01-01). Useful for incremental runs — set it to the date of your last run.

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

Residential proxy by default. HRS serves its hotel pages without a bot wall, so the actor also runs fine on datacenter proxy or with no proxy at all — but residential IPs are the safest choice on long multi-hotel runs and give the retry loop a clean IP to fall back on.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.hrs.com/en/hotel/391864"
    }
  ],
  "maxReviews": 0,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `hotels` (type: `string`):

Dataset with one record per HRS hotel: name, hotel ID, address, coordinates, star rating, average score, rating counts, per-category averages, and the array of review objects.

# 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 = {
    "startUrls": [
        {
            "url": "https://www.hrs.com/en/hotel/391864"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("reviewly/hrs-hotels-reviews-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 = {
    "startUrls": [{ "url": "https://www.hrs.com/en/hotel/391864" }],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("reviewly/hrs-hotels-reviews-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 '{
  "startUrls": [
    {
      "url": "https://www.hrs.com/en/hotel/391864"
    }
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call reviewly/hrs-hotels-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,reviewly/hrs-hotels-reviews-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/1WqXTxfYKUyvvf93h/builds/oijY5SYwLkUMCqPyM/openapi.json
