# Hotel Details Scraper - Address, Stars & Amenities (`rocketyard/hotel-schema-scraper`) Actor

$1.35 per 1,000 results, no subscription. Extracts structured hotel data (name, description, website, image, telephone, email, street, city, region, postalCode, country, latitude and more) from any website that publishes schema.org markup. Paste URLs or crawl a site; get JSON, CSV or Excel.

- **URL**: https://apify.com/rocketyard/hotel-schema-scraper.md
- **Developed by:** [Rocketyard](https://apify.com/rocketyard) (community)
- **Categories:** Travel, Lead generation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.88 / 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.
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

## Hotel Details Scraper - Address, Stars & Amenities

### What is Hotel Details Scraper?

Hotel Details Scraper is a hotel scraper that extracts **hotel data** from any web page that publishes [schema.org](https://schema.org/Hotel) structured data (JSON-LD or microdata), which many websites add for Google rich results. Give it a list of URLs, or a start page plus crawling, and get back clean, normalized records. It reads that markup instead of the page layout, so one Actor works across many sites and keeps working through redesigns.

### Use cases

- **Build a hotel directory or travel guide from brand websites**
- **Track competitor hotels: star rating, room count, amenities, check-in times**
- **Geocoded lodging lists for lead generation and market research**

### Why use Hotel Details Scraper?

- Works on any website that publishes schema.org data (JSON-LD or microdata)
- Clean, normalized JSON ready for spreadsheets, databases and AI/RAG pipelines
- Pay only per result: no monthly rental, no minimums
- Respects robots.txt and crawl-delay automatically
- Lightweight HTTP extraction: fast and cheap, no browser needed

### How it compares

| Hotel scraper | Users (last 30 days) | Successful runs (last 30 days) | Price per 1,000 results |
| --- | --- | --- | --- |
| Google Hotels Scraper | 529 | 77.7% | $4.99 |
| Booking.com Hotel Scraper – Prices, Room Rates & Reviews | 221 | 100.0% | $1.00 |
| Booking.com Scraper: Hotel Prices, Ratings & Availability | 179 | 92.4% | $3.00 |
| **This Actor** | new | every release is tested on live pages first | **$1.35** |

Apify Store data from 2026-11-04, free-plan prices. Prices and stats change; check each listing before you compare.

### How to scrape hotel data

1. Click **Try for free**.
2. Add one or more page URLs to **Start URLs** (for example `https://www.langhamhotels.com/en/the-langham/london/`).
3. Optionally enable **Crawl the website** to discover more pages on the same site, and narrow it with **Link pattern**.
4. Click **Start**, then download results as JSON, CSV or Excel, or read them through the API.

#### How do I check that a site publishes the data?

Run the Actor on one page: a page without the markup returns a free `NO_STRUCTURED_DATA` error item, so the check costs nothing beyond the start fee. You can also open the page's source and search for `application/ld+json` and `Hotel`, or paste the URL into the [Schema Markup Validator](https://validator.schema.org/).

### Input

| Field | JSON key | What it does |
|---|---|---|
| Start URLs | `startUrls` | Pages with hotel data, e.g. https://www.langhamhotels.com/en/the-langham/london/. Each page needs schema.org Hotel markup (JSON-LD or microdata); a page without it returns a free error item. With Crawl the website on, the crawl starts from these pages. |
| Max results | `maxItems` | Stop after this many records in total. This also caps what the run can charge for results. |
| Crawl the website | `crawl` | Follow links on the same site from the start URLs to find more pages. When off, only the start URLs are read. |
| Max pages to visit | `maxPagesPerCrawl` | Only used when crawling: the most pages to fetch in one run. |
| Link pattern (regex) | `linkPattern` | Only used when crawling: follow only links matching this regular expression, e.g. /products/ (max 200 characters, no nested quantifiers). |
| Max concurrency | `maxConcurrency` | Pages fetched in parallel. A polite delay between requests to the same host always applies. |
| Proxy configuration | `proxyConfiguration` | Optional. Leave empty unless a site blocks direct requests. |

### Input example

```json
{
  "startUrls": [
    {
      "url": "https://www.langhamhotels.com/en/the-langham/london/"
    }
  ],
  "maxItems": 100,
  "crawl": false
}
```

### Output example

A real record from the latest test run (langhamhotels.com) on 2026-11-06. Fields the page didn't publish are left out here; the Actor returns them as `null`.

```json
{
  "url": "https://www.langhamhotels.com/en/the-langham/london/",
  "type": "Hotel",
  "name": "The Langham, London",
  "description": "The Langham, London is Europe's first grand hotel, opened in 1865. Located in the heart of the West End, it offers 380 rooms and suites, six dining venues including Palm Court and Artesian, the award-winning Chuan Spa, and 23 event venues spanning 2,509 square metres. Holder of Two Michelin Keys…",
  "website": "https://www.langhamhotels.com/en/the-langham/london/",
  "image": [
    "https://www.langhamhotels.com/content/dam/langhamhotels/europe-middle-east/london/tllon-exterior-building-hero.jpg"
  ],
  "telephone": "+44 (0) 20 7636 1000",
  "email": "tllon.info@langhamhotels.com",
  "street": "1c Portland Place, Regent Street",
  "city": "London",
  "region": "England",
  "postalCode": "W1B 1JA",
  "country": "GB",
  "latitude": 51.5186,
  "longitude": -0.1441,
  "starRating": "5",
  "priceRange": "$$$$",
  "checkinTime": "15:00",
  "checkoutTime": "12:00",
  "numberOfRooms": 380,
  "amenities": [
    "Swimming Pool",
    "Spa",
    "Fitness Centre",
    "Restaurant",
    "Bar",
    "Afternoon Tea",
    "Event Venues",
    "Concierge",
    "Room Service",
    "Valet Parking",
    "Private Transfers"
  ],
  "scrapedAt": "2026-11-06T07:03:24.899Z"
}
```

#### Output fields

| Field | Description | Filled |
|---|---|---|
| `url` | Page the record was found on | n/a |
| `type` | schema.org type (Hotel, LodgingBusiness, Resort, Hostel, Motel, BedAndBreakfast, Campground, VacationRental) | always |
| `name` | name | 100% |
| `description` | description | 100% |
| `website` | website | 100% |
| `image` | image | 100% |
| `telephone` | telephone | 100% |
| `email` | email | 100% |
| `street` | street | 100% |
| `city` | city | 100% |
| `region` | region | 100% |
| `postalCode` | postal code | 100% |
| `country` | country | 100% |
| `latitude` | latitude | 100% |
| `longitude` | longitude | 100% |
| `starRating` | star rating | 50% |
| `priceRange` | price range | 50% |
| `checkinTime` | checkin time | 100% |
| `checkoutTime` | checkout time | 100% |
| `numberOfRooms` | number of rooms | 50% |
| `amenities` | amenities | 50% |
| `ratingValue` | rating value | 0% |
| `reviewCount` | review count | 0% |
| `scrapedAt` | ISO timestamp of the extraction | always |

"Filled" is the share of records with a value in the latest test run. Sites decide what they publish, so yours can differ. A field without a value comes back as `null`, never guessed.

### Limits

- Pages without schema.org `Hotel` markup return no record, only a free error item.
- Markup added only by JavaScript in the browser is not read: the Actor reads the HTML the server sends.
- Pages behind a login or paywall are not accessed, paths that robots.txt disallows are skipped, and protections are never bypassed.

### Error items

When an input produces no data, the dataset gets one **error item** for it, so nothing is skipped silently. Error items are **free**: only result records are charged.

```json
{
  "url": "https://example.com/a-page-that-was-removed",
  "input": "https://example.com/a-page-that-was-removed",
  "error": {
    "type": "https://rocketyard.dev/errors#not-found",
    "title": "Not found",
    "status": 404,
    "detail": "HTTP 404",
    "instance": "https://example.com/a-page-that-was-removed",
    "code": "NOT_FOUND"
  }
}
```

The `error` object follows [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) (Problem Details). Its `type` links to the [explanation of the code](https://rocketyard.dev/errors), and `status` is the site's HTTP status when there was one. Filter on `error.code` to separate failures from data; it has its own column in CSV and Excel exports.

| Code | Meaning | Retry? |
|---|---|---|
| [`ROBOTS_DISALLOWED`](https://rocketyard.dev/errors#robots-disallowed) | The site's robots.txt does not allow automated access to this URL (or to the page it redirects to), so it was not fetched. | no |
| [`ROBOTS_UNREACHABLE`](https://rocketyard.dev/errors#robots-unreachable) | The site's robots.txt could not be loaded because of a server or network error. Under RFC 9309, the page is then not fetched. | yes |
| [`PRIVATE_ADDRESS`](https://rocketyard.dev/errors#private-address) | The URL, or a redirect from it, points to a private or local network address. Only public websites are fetched. | no |
| [`NOT_FOUND`](https://rocketyard.dev/errors#not-found) | The page or source does not exist (HTTP 404 or 410). | no |
| [`BLOCKED`](https://rocketyard.dev/errors#blocked) | The site refused access (HTTP 401, 403 or 429) or answered with a bot-check or consent page instead of the content. | sometimes |
| [`HTTP_ERROR`](https://rocketyard.dev/errors#http-error) | The site answered with another HTTP error: a server error (5xx) that persisted after retries, or an unexpected 4xx status. | sometimes |
| [`TIMEOUT`](https://rocketyard.dev/errors#timeout) | The site did not answer in time, even after retries. | yes |
| [`NETWORK_ERROR`](https://rocketyard.dev/errors#network-error) | The connection failed (DNS, TLS or a reset connection), even after retries. | yes |
| [`WRONG_CONTENT_TYPE`](https://rocketyard.dev/errors#wrong-content-type) | The URL returned something the Actor doesn't read, such as a PDF or image instead of an HTML page, or a web page where an API response was expected. | no |
| [`TOO_LARGE`](https://rocketyard.dev/errors#too-large) | The response exceeded the size limit (10 MiB for web pages, 48 MiB for API responses). | no |
| [`PARSE_ERROR`](https://rocketyard.dev/errors#parse-error) | The response could not be read (malformed HTML or JSON). | sometimes |
| [`NO_STRUCTURED_DATA`](https://rocketyard.dev/errors#no-structured-data) | The page loaded but has none of the data the Actor extracts: no schema.org data (JSON-LD or microdata), or, for the Shopify Actor, no public Shopify catalog on the site. | no |

### Pricing

Pay per result: **$1.35 per 1,000 hotel records**, plus a tiny per-run start fee. There is no monthly rental, and you can cap spend per run with **Maximum cost per run**.

### Integrations and API

- **Schedule**: an [Apify schedule](https://docs.apify.com/platform/schedules) runs it daily or weekly. Each run reads the pages as they are now, so compare runs to track changes.
- **Webhooks**: an [Apify webhook](https://docs.apify.com/platform/integrations/webhooks) calls your URL when a run finishes, with the run's dataset ID in the payload.
- **Make**: the Apify app's **Run an Actor** and **Get Dataset Items** modules, then any Make app, such as Google Sheets. [Steps](https://rocketyard.dev/hotel-schema-scraper#make).
- **Zapier**: the **Finished Actor run** trigger and the **Fetch dataset items** search. Zapier stops synchronous runs after 30 seconds, so use the trigger for anything larger than a small run. [Steps](https://rocketyard.dev/hotel-schema-scraper#zapier).
- **n8n**: Apify's community node (`@apify/n8n-nodes-apify`) or an HTTP Request node. [Workflow to copy](https://rocketyard.dev/hotel-schema-scraper#n8n).
- **Code**: the [Apify API](https://docs.apify.com/api/v2) and the JavaScript and Python clients. [Examples](https://rocketyard.dev/hotel-schema-scraper#api).
- **AI agents**: Apify's MCP server exposes it as a tool to any MCP client. [Setup](https://rocketyard.dev/hotel-schema-scraper#mcp).

### FAQ

**How much does it cost?** You pay per result: $1.35 per 1,000 hotel records, plus a start fee of $0.00005 per run, with no subscription. Cap any run with **Maximum cost per run**.

Your Apify plan sets your discount tier (Platinum and Diamond are enterprise tiers):

| Tier | Per 1,000 hotel records |
|---|---|
| Free | $1.35 |
| Bronze | $1.35 |
| Silver | $1.08 |
| Gold | $0.88 |
| Platinum | $0.88 |
| Diamond | $0.88 |

**How do I call it from the API?** One request starts a run and returns its results, using your [Apify API token](https://console.apify.com/settings/integrations):

```bash
curl -X POST "https://api.apify.com/v2/acts/rocketyard~hotel-schema-scraper/run-sync-get-dataset-items" -H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" -d '{"startUrls":[{"url":"https://www.langhamhotels.com/en/the-langham/london/"}],"maxItems":100}'
```

**How do I use it with AI agents?** Connect Apify's MCP server at `https://mcp.apify.com?tools=rocketyard/hotel-schema-scraper`, and the agent can call this Actor as a tool.

**Which websites does it work on?** Any public page that embeds schema.org `Hotel` data. If a page has no such markup, the Actor returns a free error item with the code `NO_STRUCTURED_DATA` for it instead of a record.

**Is it legal to scrape hotel data?** This Actor only reads publicly available pages, obeys robots.txt and crawl-delay, does not log in or bypass protections, and does not collect people's profiles, personal emails or personal phone numbers. You are responsible for complying with each website's terms and with applicable laws (for example copyright and GDPR) for how you use the data.

**What happens when a URL fails?** If a start URL can't be read, is blocked or has no data, the dataset gets one [error item](#error-items) for it with a code and the reason, and you are not charged for it. The other URLs in the run carry on.

**Can it scrape JavaScript-only pages?** It reads the server-rendered HTML, where structured data almost always lives. Pages that inject markup only via client-side JavaScript are not supported.

**I need a field or site that is not supported.** Open an issue on the Actor's Issues tab. Requests are reviewed regularly and drive new features.

### You might also like

- [Ashby Jobs API & Scraper](https://apify.com/rocketyard/ashby-jobs-scraper): every open job from Ashby job boards
- [Events Scraper - Any Event Site to Structured Data](https://apify.com/rocketyard/event-schema-scraper): event data from any website with schema.org markup
- [Greenhouse Jobs API & Scraper](https://apify.com/rocketyard/greenhouse-jobs-scraper): every open job from Greenhouse job boards
- [Job Postings API & Scraper - Any Career Site](https://apify.com/rocketyard/jobposting-schema-scraper): job posting data from any website with schema.org markup
- [Lever Jobs API & Scraper](https://apify.com/rocketyard/lever-jobs-scraper): every open job from Lever job boards

### Your feedback

Found a bug, a site that doesn't work or a missing field? Open an issue on the [Issues tab](https://apify.com/rocketyard/hotel-schema-scraper/issues): issues are public, so the answer helps the next person too. If the Actor did the job, a review on this page helps other people find it.

### Made by Rocketyard

[Rocketyard](https://rocketyard.dev) builds pay-per-result scrapers for public web data, and every release is tested on live pages before it ships. Guides for this Actor: [overview](https://rocketyard.dev/hotel-schema-scraper), [REST API](https://rocketyard.dev/hotel-schema-scraper#api), [MCP for AI agents](https://rocketyard.dev/hotel-schema-scraper#mcp) and [n8n, Make and Zapier](https://rocketyard.dev/hotel-schema-scraper#automation).

# Actor input Schema

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

Pages with hotel data, e.g. https://www.langhamhotels.com/en/the-langham/london/. Each page needs schema.org Hotel markup (JSON-LD or microdata); a page without it returns a free error item. With Crawl the website on, the crawl starts from these pages.

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

Stop after this many records in total. This also caps what the run can charge for results.

## `crawl` (type: `boolean`):

Follow links on the same site from the start URLs to find more pages. When off, only the start URLs are read.

## `maxPagesPerCrawl` (type: `integer`):

Only used when crawling: the most pages to fetch in one run.

## `linkPattern` (type: `string`):

Only used when crawling: follow only links matching this regular expression, e.g. /products/ (max 200 characters, no nested quantifiers).

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

Pages fetched in parallel. A polite delay between requests to the same host always applies.

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

Optional. Leave empty unless a site blocks direct requests.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.langhamhotels.com/en/the-langham/london/"
    }
  ],
  "maxItems": 100,
  "crawl": false,
  "maxPagesPerCrawl": 50,
  "maxConcurrency": 5
}
```

# Actor output Schema

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

Extracted hotel records (one per page and item), as JSON, CSV or Excel, plus a free error item for each URL that produced none.

## `runSummary` (type: `string`):

Pages visited, items extracted, pages skipped by robots.txt and errors.

# 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.langhamhotels.com/en/the-langham/london/"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("rocketyard/hotel-schema-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.langhamhotels.com/en/the-langham/london/" }] }

# Run the Actor and wait for it to finish
run = client.actor("rocketyard/hotel-schema-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.langhamhotels.com/en/the-langham/london/"
    }
  ]
}' |
apify call rocketyard/hotel-schema-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,rocketyard/hotel-schema-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/BHWTKmoQBkQtFBBhk/builds/y0qA92VXrHxGW5b42/openapi.json
