# HRS Hotel & Reviews Scraper (`memo23/hrs-hotels-scraper`) Actor

Scrape HRS.com hotels with every guest review — liked and disliked comments split apart, per-category scores, traveller segment, and the hotel's own published reply. Plus full profile: address, geo, stars, chain, contact email, amenities, images, sustainability. URL, ID, or all 181k hotels.

- **URL**: https://apify.com/memo23/hrs-hotels-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Travel, AI, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 hotels

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## HRS Hotel & Reviews Scraper — hotel profiles, ratings & guest reviews from hrs.com

Scrape **HRS** (hrs.com) hotels and their guest reviews. Each result is one hotel with its full profile — name, address, coordinates, star rating, chain, **contact email and phone**, 100+ amenities, image gallery, check-in times and sustainability figures — plus **every review HRS publishes for it**, with the positive and negative comment split apart, per-category scores, and **the hotel's own published reply**. Paste hotel URLs or IDs, or scrape straight from HRS's **181,000-hotel sitemap**. Clean JSON/CSV out.

> **About review volume:** HRS is a business-travel booking site, not a review platform. Most listed properties have a handful of numeric ratings and **no written review at all** — across a random sample of the sitemap, roughly 3% carried review text. Every review a hotel does have is returned in full. If you specifically want review content, switch on `onlyWithReviews` and the actor will skip past the empty properties for you. See the [FAQ](#faq).

![How HRS Hotel & Reviews Scraper works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-hrs.png)

### Why use this scraper?

- **Every review, first request** — the hotel page only ever displays three reviews, and the "show all" modal renders from data already in the page. This actor reads that payload directly, so you get the complete set with no pagination and no second round trip.
- **Hotel replies included** — HRS publishes management responses alongside guest feedback. They come through as `hotelReplyPositive` / `hotelReplyNegative`, which is the half of the conversation most review scrapers drop.
- **Liked and disliked, separately** — HRS asks guests what worked and what didn't as two fields. No sentiment model needed to tell them apart.
- **Per-category scores twice over** — the hotel's aggregate scores across 12 categories (cleanliness, value, breakfast, beds, bathroom, room size…), *and* the individual category votes inside each review.
- **Scores broken out by traveller type** — business, private, young couples, mature couples, families. Useful when your segment isn't the average one.
- **Contact details on every hotel** — the property's own email address and phone number, straight from the payload.
- **181,000 hotels reachable** — no competitor accepts anything but a hotel URL. Switch on `scrapeAll` and work through HRS's own sitemap instead of sourcing a URL list first.
- **No proxy needed** — HRS serves this data over a direct connection. Cheaper and faster than anything that has to buy its way past a bot wall.

### Overview

Give the actor HRS hotel URLs, bare hotel IDs, or nothing at all with `scrapeAll` switched on. For each hotel it makes one request, extracts the complete hotel object HRS embeds in the page, and returns a single clean row: identity, location, chain, contact, ratings, amenities, images, sustainability data, and the nested review list. Hotels arriving twice — say a pasted URL that also appears in the sitemap — are de-duplicated by hotel ID.

### Supported inputs

| Input | Example | Result |
|---|---|---|
| Hotel URL | `https://www.hrs.com/en/hotel/10369` | that hotel + all its reviews |
| Any language | `https://www.hrs.com/fr/hotel/10369` | same hotel, normalized to English |
| German domain | `https://www.hrs.de/de/hotel/10369` | same hotel |
| Bare hotel ID | `10369` | same hotel |
| Sitemap mode | `{ "scrapeAll": true }` | works through all ~181,000 HRS hotels |
| Sitemap + filters | `{ "scrapeAll": true, "onlyWithReviews": true }` | only hotels that have review text |

### Use cases

- **Reputation monitoring** — track what business travellers say about your properties, and whether your team is replying.
- **Competitive benchmarking** — compare category scores against rival hotels in the same city, segmented by traveller type.
- **Review-response auditing** — measure reply rate and response tone across a portfolio; the hotel-reply fields make this directly countable.
- **Hospitality market research** — chain footprint, star distribution, amenity coverage, and renovation years across a country.
- **Sustainability screening** — filter to HRS Green Stay properties and pull their per-night carbon, water and waste figures.
- **Hotel lead lists** — name, address, coordinates, email and phone for every hotel in a market.
- **AI/LLM training and RAG** — structured review text with scores attached, ready for sentiment and aspect extraction.

### How it works

1. **Resolve** — every input form (URL in any language, `hrs.de` domain, or a bare ID) is normalized to a canonical English hotel URL. With `scrapeAll`, hotel URLs are read from HRS's sitemap index instead.
2. **Fetch** — one request per hotel with browser-grade impit TLS, up to 20 in parallel. Typical response is around 330 ms, direct, no proxy.
3. **Extract** — HRS runs Next.js with the App Router, so the page data ships as an RSC flight payload split across `__next_f` chunks. Those are reassembled and the hotel object is read straight out of it — richer and more stable than scraping rendered HTML.
4. **Map** — the payload becomes one flat record, with reviews nested under it and HRS's `$undefined` sentinels normalized to `null`.
5. **Filter** — result-side filters are applied *before* the item cap, so a limited run returns that many hotels you actually wanted.
6. **Push** — one dataset row per hotel, JSON, CSV or Excel.

### Input configuration

```json
{
    "startUrls": [
        "https://www.hrs.com/en/hotel/10369"
    ],
    "hotelIds": ["391864", "250406"],
    "scrapeAll": false,
    "maxItems": 100,
    "maxConcurrency": 10
}
```

Scrape the whole of HRS instead, keeping only well-rated German hotels that have review text:

```json
{
    "scrapeAll": true,
    "countries": ["DE"],
    "minStars": 4,
    "minRating": 8,
    "onlyWithReviews": true,
    "maxItems": 500
}
```

| Field | Type | Default | Description |
|---|---|---|---|
| `startUrls` | array | — | HRS hotel page URLs, any language or domain |
| `hotelIds` | array | — | Bare HRS hotel IDs, one per line |
| `scrapeAll` | boolean | `false` | Seed from HRS's sitemap instead of a URL list |
| `maxItems` | integer | `100` | Hard cap on hotels collected — also your billing cap |
| `maxConcurrency` | integer | `10` | Parallel hotel fetches |
| `proxy` | object | none | Optional; direct is the tested and fastest path |

#### Filters

All filters are applied to the extracted data and run before `maxItems` is counted. Leave a filter empty to ignore it.

| Field | Type | Description |
|---|---|---|
| `minStars` | integer | Minimum HRS star rating, 1–5 |
| `minRating` | integer | Minimum guest rating on the HRS 1–10 scale |
| `minReviews` | integer | Minimum number of guest ratings |
| `onlyWithReviews` | boolean | Keep only hotels with at least one written review |
| `countries` | array | ISO code or name — `DE`, `DEU` and `Germany` all work |
| `chains` | array | Matched against chain *and* parent group, so `Radisson` catches `Radisson Hotel Group` |
| `amenities` | array | Hotel must have **all** listed keywords, e.g. `parking`, `sauna`, `conference` |
| `greenStayOnly` | boolean | Keep only HRS Green Stay properties |

### Output overview

One row per hotel, with reviews nested inside it.

```json
{
    "hotelId": 10369,
    "name": "Radisson Blu Hotel Bremen",
    "url": "https://www.hrs.com/en/hotel/10369",
    "address": {
        "street": "BOTTCHERSTRASSE 2",
        "city": "Bremen",
        "cityWithDistrict": "Bremen, Zentrum",
        "postalCode": "28195",
        "country": "DEU",
        "countryCode": "DEU"
    },
    "latitude": 53.07458,
    "longitude": 8.80571,
    "stars": 4,
    "chainName": "Radisson Hotel Group (Opt-in 14%)",
    "parentChainName": "Radisson Hotel Group",
    "email": "info.bremen@radissonblu.com",
    "phone": "+49 42136960",
    "ratingScore": 8.4,
    "ratingCount": 53,
    "recommendationRate": 83,
    "categoryScores": {
        "pricePerformanceRatio": 7.9,
        "hotelAmbiance": 8.5,
        "hotelTidiness": 8.5,
        "friendlinessOfReception": 9.1,
        "breakfastService": 8.9,
        "roomSize": 8.7,
        "qualityOfBeds": 8.3,
        "sanitaryFacilities": 7.9
    },
    "ratingsByTravellerType": [
        { "travellerType": "ALLHRS", "rating": 8.5, "reviewCount": 53, "recommendationRate": 83.3 },
        { "travellerType": "BUSINESS", "rating": 8.4, "reviewCount": 49, "recommendationRate": 84 },
        { "travellerType": "PRIVATE", "rating": 9.6, "reviewCount": 2, "recommendationRate": 100 }
    ],
    "writtenReviewCount": 6,
    "reviews": [
        {
            "reviewerName": "LITTLE I.",
            "travellerType": "BUSINESS",
            "date": "2025-12-08T09:39:47.330Z",
            "locale": "en",
            "score": 8.7,
            "positiveComment": "Clean, friendly and very welcoming.",
            "negativeComment": "Room far too hot to sleep properly. Air conditioning not working…",
            "hotelReplyPositive": null,
            "hotelReplyNegative": "Dear Little I, thank you for staying with us and leaving us your valuable feedback…",
            "recommends": true,
            "categoryScores": { "hotelTidiness": 10, "roomSize": 10, "roomConfiguration": 3 }
        }
    ],
    "amenities": ["LAUNDRY", "INTERNET_WLANINROOM", "AIRCONDITION", "SAUNA", "PETS"],
    "roomAmenities": ["Air conditioning", "Safe", "Minibar"],
    "images": ["https://foto.hrsstatic.com/fotos/…"],
    "imageCount": 38,
    "checkInFrom": "15:00",
    "checkOutUntil": "12:00",
    "reception24Hours": false,
    "yearOfConstruction": 1990,
    "yearOfLastRenovation": 2014,
    "numberOfStoreys": 5,
    "sustainability": {
        "greenStay": true,
        "carbonPerNight": 5.89941,
        "waterPerNight": 264.515,
        "wastePerNight": null,
        "certificationLink": "https://…"
    },
    "distances": {
        "toTrainStations": [{ "distance": 1, "description": "Bremen Hauptbahnhof", "type": "MAIN_TRAIN_STATION" }],
        "toAirports": [{ "distance": 3, "description": "Bremen Airport (BRE)", "type": "AIRPORT" }]
    },
    "scrapedAt": "2026-08-14T17:33:00.000Z"
}
```

### Key output fields

| Field | Type | Notes |
|---|---|---|
| `hotelId` | number | HRS's stable numeric ID — the key to join runs on |
| `name`, `stars` | string, number | Hotel name and HRS star rating (1–5) |
| `address`, `latitude`, `longitude` | object, number | Full postal address plus coordinates |
| `email`, `phone` | string | The property's own contact details |
| `chainName`, `parentChainName` | string | Chain and its parent group |
| `ratingScore` | number | Overall guest rating, **1–10 scale** (not 1–5) |
| `ratingCount` | number | Number of guest *ratings* — most carry no text |
| `recommendationRate` | number | Percent of guests who recommend the hotel |
| `categoryScores` | object | Aggregate scores across up to 12 categories |
| `ratingsByTravellerType` | array | Same scores split by business / private / couples / families |
| `writtenReviewCount` | number | How many reviews have comment text — often 0 |
| `reviews[]` | array | Full review objects; see below |
| `reviews[].positiveComment` / `.negativeComment` | string | What the guest liked / didn't, as separate fields |
| `reviews[].hotelReplyPositive` / `.hotelReplyNegative` | string | The hotel's published response, usually `null` |
| `reviews[].categoryScores` | object | That guest's own per-category votes |
| `amenities`, `roomAmenities` | array | HRS amenity codes and readable room amenities |
| `images`, `imageCount` | array, number | Full-size gallery URLs |
| `sustainability` | object | Green Stay status and per-night carbon/water/waste |
| `distances` | object | Distances to stations, airports, trade fairs, city centre |

### FAQ

**Why do so many hotels come back with zero reviews?**
Because that's what HRS has. It's a corporate booking platform, so most of its 181,000 listings are small properties that have never been reviewed in writing — about 3% of a random sitemap sample carried review text. Use `onlyWithReviews: true` and the actor will fetch past the empty ones and only bill you for hotels that have content.

**Does this get *all* the reviews, or just the first page?**
All of them. HRS embeds the complete review list in the page and the "show all reviews" button renders from that same data without making a request — so there is no page 2 to miss.

**What scale are the ratings on?**
1–10, not 1–5. `stars` is the separate 1–5 star classification.

**Do I need a proxy?**
No. HRS returned 200 on every request during testing over a direct connection, with no challenge. A proxy field exists if your network requires one, but direct is the tested and fastest path.

**Can I get room prices?**
No. HRS prices rooms through a separate date-and-occupancy request that isn't part of the hotel page, so nightly rates are out of scope for this actor.

**How do I scrape a whole country?**
Set `scrapeAll: true` with `countries: ["DE"]` and a `maxItems` budget. The actor walks HRS's sitemap and keeps only matching hotels.

**Can I run it incrementally?**
Yes — the hotel page always carries the newest reviews, so re-running on the same `hotelIds` and diffing on `reviews[].date` gives you what's new.

### Support

Found a bug or need a field that isn't here? Open an issue on the actor's Issues tab and it'll be looked at.

### Additional services

Need this data pushed somewhere specific, or a scraper for a site not yet covered? Get in touch through the Apify platform.

### Explore more scrapers

Other hotel and review scrapers in this collection: Booking.com Reviews, Agoda Reviews, TripAdvisor, Expedia, Trip.com, Choice Hotels, Google Reviews.

### 🤖 For AI Agents & LLM Apps

This actor is a clean data source for agent pipelines:

- **Deterministic input** — a bare hotel ID is a valid input, so an agent can go from a search result to structured data without URL construction.
- **One call, complete record** — hotel profile and full review history arrive together, so no follow-up calls or pagination state to manage.
- **Pre-split sentiment** — `positiveComment` and `negativeComment` are separate fields as published, removing a classification step before aspect extraction.
- **Scores attached to text** — every review carries both an overall score and per-category votes, making it directly usable for supervised tasks and RAG citations.
- **Stable joins** — `hotelId` is HRS's own key, so runs over time diff cleanly.

### ⚠️ Disclaimer

This actor collects only publicly available information from hrs.com — data any visitor can see without logging in. It does not access private, personal, or authentication-protected content, and it does not attempt to bypass access controls. You are responsible for ensuring your use of the scraped data complies with HRS's terms of service, applicable copyright law, and data-protection regulations including the GDPR. Review text and reviewer display names may constitute personal data in some jurisdictions; process them accordingly and establish your own lawful basis before storing or republishing them.

### SEO Keywords

HRS scraper, HRS.com scraper, HRS hotel scraper, HRS reviews scraper, HRS hotel reviews, hotel review scraper, hotel data extraction, hotel reviews API, guest review scraper, hotel ratings scraper, business travel data, hotel reputation monitoring, hotel review analysis, hospitality data scraping, hotel competitor analysis, German hotel data, hotel chain data, hotel email scraper, hotel contact extraction, hotel sustainability data, Green Stay hotels, hotel amenities data, travel data extraction, hotel market research, review sentiment data, hotel benchmarking, HRS API alternative, Apify hotel scraper

# Actor input Schema

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

Full HRS hotel page URLs in any language — https://www.hrs.com/en/hotel/391864, https://www.hrs.de/de/hotel/391864, and so on. All are normalized to the English page so results are consistent.

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

HRS hotel ids on their own, one per line — e.g. 10369. Handy when driving the actor from a spreadsheet or from a previous run's output.

## `scrapeAll` (type: `boolean`):

Ignore the lists above and work through every hotel in HRS's public sitemap — about 181,000 properties. Always bounded by 'Maximum hotels to scrape', so set that first.

## `minStars` (type: `integer`):

Keep only hotels with at least this many HRS stars. 0 = no minimum.

## `minRating` (type: `integer`):

Keep only hotels whose overall guest rating is at or above this value. HRS rates on a 1-10 scale. 0 = no minimum.

## `minReviews` (type: `integer`):

Keep only hotels with at least this many guest ratings. Useful for skipping the many small properties HRS lists that have never been rated. 0 = no minimum.

## `onlyWithReviews` (type: `boolean`):

Keep only hotels that have at least one review with actual comment text. Most HRS properties have ratings but no written review, so switch this on if you are after review content specifically.

## `countries` (type: `array`):

Keep only hotels in these countries. Accepts an ISO code or a name — DE, DEU, or Germany all work.

## `chains` (type: `array`):

Keep only hotels belonging to these chains, e.g. Radisson, Accor, Motel One. Matched against both the chain and its parent group, so 'Radisson' also catches 'Radisson Hotel Group'.

## `amenities` (type: `array`):

Keep only hotels that have every amenity keyword listed, e.g. parking, sauna, wlan, pets, conference, wheelchair. Matched against the hotel's amenity codes and room amenities, case-insensitive.

## `greenStayOnly` (type: `boolean`):

Keep only hotels enrolled in HRS Green Stay, the sustainability programme that reports per-night carbon, water, and waste figures.

## `maxItems` (type: `integer`):

Hard cap on the number of hotels collected. Each hotel is one dataset item and includes all of its reviews, so this is also your billing cap.

## `maxConcurrency` (type: `integer`):

How many hotel pages to fetch at once. HRS responds in about a third of a second and does not rate-limit aggressively, so the default is comfortable; lower it if you see failures.

## `proxy` (type: `object`):

Optional proxy. Leave unset to connect directly, which is the tested and fastest path.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.hrs.com/en/hotel/10369"
  ],
  "hotelIds": [],
  "scrapeAll": false,
  "minStars": 0,
  "minRating": 0,
  "minReviews": 0,
  "onlyWithReviews": false,
  "countries": [],
  "chains": [],
  "amenities": [],
  "greenStayOnly": false,
  "maxItems": 100,
  "maxConcurrency": 10,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# 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": [
        "https://www.hrs.com/en/hotel/10369"
    ],
    "hotelIds": [],
    "countries": [],
    "chains": [],
    "amenities": [],
    "proxy": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/hrs-hotels-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": ["https://www.hrs.com/en/hotel/10369"],
    "hotelIds": [],
    "countries": [],
    "chains": [],
    "amenities": [],
    "proxy": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/hrs-hotels-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": [
    "https://www.hrs.com/en/hotel/10369"
  ],
  "hotelIds": [],
  "countries": [],
  "chains": [],
  "amenities": [],
  "proxy": {
    "useApifyProxy": false
  }
}' |
apify call memo23/hrs-hotels-scraper --silent --output-dataset

```

## MCP server setup

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