# Canary Islands Vacation Rentals Scraper (Property Listings) (`steadfetch/lascasascanarias-scraper`) Actor

Public holiday-rental property listings from Las Casas Canarias in the Canary Islands: name, address, coordinates, images, island and advertised nightly price. For rental market research and price tracking. Sitemap-only discovery, schema.org JSON-LD and microdata, no browser and no proxies.

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

## Pricing

from $2.50 / 1,000 property listing delivereds

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

## Canary Islands Vacation Rentals Scraper (Property Listings)

Public holiday-rental property listings from [Las Casas Canarias](https://www.lascasascanarias.com/),
a local operator in the Canary Islands. One item per property page, with the
advertised nightly from-price, address, coordinates, images and island.

### What it does

- Discovers property pages **only** from the sitemaps declared in the source's
  `robots.txt` (the Las Casas Canarias sitemap and the Salobre Golf Villas
  sitemap it declares). No on-site search, no category crawling and no
  `/ajax.php`.
- Extracts each listing from the page's schema.org `Hotel` JSON-LD and
  `LodgingBusiness` microdata: `name`, `address`, `latitude`, `longitude`,
  `images`, `island` and the visible "from" price (`ab 260,00 Euro/Nacht` or
  `desde ... EUR/noche`).
- Returns `priceFromEur: null` when a page has no advertised price pattern.
  The Actor never invents a price.
- Drops landing pages (`/espanol/...`, `/es/...`), static pages and incomplete
  pages before they reach the dataset, so they are never billed.
- Keeps a per-URL dedupe state in the named key-value store
  `lascasascanarias-scraper-state` under `seen-urls-<apify-user-id>`
  (`seen-urls-local` when no user ID is present). The user ID isolates tenants,
  so one customer's runs can never hide listings from another customer's runs.
  A later run delivers only URLs that were not delivered before. Clear that key
  to backfill.
- Emits the common Steadfetch envelope (`url`, `scrapedAt`) on every item.
- Requests run sequentially with a fixed 1000 ms delay (concurrency 1). No
  browser, no proxies, no logins, no paywall or CAPTCHA bypass.

### Input

| Field | Type | Default | Meaning |
|---|---|---|---|
| `maxResults` | integer 1-500 | `100` | Maximum number of new listings to deliver. |
| `urls` | array of 1-500 URLs | omitted | Optional property-page URLs. When omitted, discovery uses the sitemaps declared in `robots.txt`. |

Only Las Casas Canarias and Salobre Golf Villas hosts are accepted in `urls`.

### Example output

```json
{
  "url": "https://www.lascasascanarias.com/la-palma/ferienwohnungen/villa-crow-74524.html",
  "scrapedAt": "2026-10-21T08:56:00.000Z",
  "name": "Villa Crow",
  "address": "Tijarafe, La Palma",
  "latitude": 28.693141,
  "longitude": -17.95586,
  "priceFromEur": 260.0,
  "currency": "EUR",
  "images": [
    "https://www.lascasascanarias.com/cmsAdmin/uploads/o_1f8t5fdq35udu8m1b812leg7711.jpg"
  ],
  "island": "la-palma",
  "source": {
    "publisher": "LasCasasCanarias",
    "url": "https://www.lascasascanarias.com/la-palma/ferienwohnungen/villa-crow-74524.html"
  }
}
```

The `images` array in a real run contains every image URL published in the
listing JSON-LD, not only the first one shown here.

### Legal and robots compliance

- `robots.txt` is read on every run. Its only wildcard rule is
  `Disallow: /ajax.php`; the Actor never requests it. Property pages and the
  declared sitemaps are allowed.
- Discovery is limited to the sitemaps declared in `robots.txt`; undeclared
  routes are never crawled.
- A 401, 403, 429 or 451 response, or a known challenge page, stops the run
  with an error. The Actor never tries to evade a block.
- Public property pages only. No owner names, no telephone numbers, no private
  profiles and no personal data. Only `name`, `address`, coordinates, images,
  island and price are read.
- The legal gate search performed before the build is recorded in
  `docs/legal-gate.md`.

### Pricing

Pay per event, no monthly rent:

- `apify-actor-start`: **0.00005 USD** per run (Apify-recommended default).
- `apify-default-dataset-item`: **0.0025 USD** per new listing, i.e.
  **2.50 USD per 1,000 results**.

There is no custom per-item event and no `Actor.charge()` call: the synthetic
dataset event already charges per result, so a listing is never billed twice.
Duplicate URLs, landing pages, incomplete pages and pages already seen are not
delivered and not charged.

#### Measured cost per 1,000 results

Local measurement on 2026-10-21 against the live source (no platform credit):

- A 20-page sequential run extracted **20 listings in 20.16 s**, i.e.
  **1.008 s per page** including the fixed 1000 ms politeness delay, in a
  256 MB container.
- 1,000 pages therefore take about 1,008 s = 0.28 hours = **0.07 compute
  units** (1 CU = 1 GB-hour) at 256 MB. At Apify's published **0.20 USD per
  CU** that is about **0.014 USD**.
- Pages are 277 KB uncompressed on average and about 33 KB compressed
  (measured sample: 285 KB to 33 KB). 1,000 pages are about 32 MB of
  compressed transfer, about **0.006 USD** at 0.20 USD/GB.
- 1,000 dataset writes add about **0.005 USD**.
- **Measured cost basis: about 0.025 USD per 1,000 results** (discovery adds
  four requests per run, negligible at 1,000 items). At 2.50 USD per 1,000 the
  gross margin is above 99%. The first paid platform run will replace this
  local estimate with the billed figure.

### FAQ

**Does it need a browser or a proxy?**
No. The source is static HTML; one plain HTTP GET per property page plus the
robots and sitemap requests. A proxy can still be added at the platform level
if the network requires it.

**Why does a later run return zero or few items?**
The Actor dedupes by `url` in a named key-value store, so it only delivers URLs
it has not delivered before. Delete `seen-urls-<apify-user-id>`
(`seen-urls-local` when no user ID is present) from
`lascasascanarias-scraper-state`, or use a fresh store, to backfill.

**Why is `priceFromEur` null for some listings?**
The page has no visible "from" price and no JSON-LD `priceRange`. The Actor
reports `null`; it never guesses or carries a price over from another page.

**Are owner names or telephone numbers collected?**
No. Those fields are deliberately excluded even when present in the page, to
keep the dataset free of personal data.

**How fresh is the data?**
Every run reads the live public pages. The `scrapedAt` field is the UTC
timestamp of that item's scrape.

### Maintenance

This Actor is **tested daily**: an automated run checks that the source is
reachable, that the declared sitemaps still expose property URLs and that every
item passes the dataset schema. Failures become issues in the Steadfetch Actors
repository. `npm test`, `npm run publish:check`, `npm run build` and
`apify-cli validate-schema` run in CI on every push.

Source: public pages of `www.lascasascanarias.com`. Steadfetch runs no logins,
no paywall bypass and no CAPTCHA evasion - see our
[company page](https://apify.com/steadfetch) for all Actors.

# Actor input Schema

## `maxResults` (type: `integer`):

Maximum number of new property listings to deliver.

## `urls` (type: `array`):

Optional Las Casas Canarias property-page URLs. When omitted, the Actor discovers pages from the sitemaps declared in robots.txt.

## Actor input object example

```json
{
  "maxResults": 10,
  "urls": [
    "https://www.lascasascanarias.com/la-palma/ferienwohnungen/villa-crow-74524.html"
  ]
}
```

# Actor output Schema

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

Dataset items validated against the item schema in dataset_schema.json (fields).

# 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 = {
    "maxResults": 10,
    "urls": [
        "https://www.lascasascanarias.com/la-palma/ferienwohnungen/villa-crow-74524.html"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadfetch/lascasascanarias-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 = {
    "maxResults": 10,
    "urls": ["https://www.lascasascanarias.com/la-palma/ferienwohnungen/villa-crow-74524.html"],
}

# Run the Actor and wait for it to finish
run = client.actor("steadfetch/lascasascanarias-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 '{
  "maxResults": 10,
  "urls": [
    "https://www.lascasascanarias.com/la-palma/ferienwohnungen/villa-crow-74524.html"
  ]
}' |
apify call steadfetch/lascasascanarias-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadfetch/lascasascanarias-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/C8kXZTQaurXEew1fi/builds/OlgU4pnwpDVGB7gdl/openapi.json
