# Midland Realty Scraper – HK Listings & Transactions (No API) (`claude_code_reviewer/midland-scraper-en`) Actor

Scrape Midland Realty Hong Kong without an API: 44K for-sale and for-rent listings, 1.8M historical transactions and 18K estates. Price, rent, saleable area, price per sq ft, bedrooms, school net and coordinates as JSON/CSV/Excel — for HK property research and agency inventory comparison.

- **URL**: https://apify.com/claude\_code\_reviewer/midland-scraper-en.md
- **Developed by:** [Chad](https://apify.com/claude_code_reviewer) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 83.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 listing / transaction / estates

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?

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

## Midland Realty Scraper (Hong Kong)

Scrape **Midland Realty**'s public Hong Kong property data with one Actor: **listings for sale and rent**, **historical transaction records**, and the **full estate directory**. Public JSON API — no login, no API key, no browser.

Midland has the largest branch network of any Hong Kong estate agency, and the dataset is a tier larger than most comparable sources:

| Mode | Rows available |
|---|---|
| Listings | **44,190** (31,530 for sale / 15,563 for rent) |
| Transactions | **1,855,568** |
| Estates | **17,964** |

### Output fields

#### Listings (`mode: search`)

| Field | Type | Description |
|---|---|---|
| `serial_no` | string | Midland listing number, e.g. `M351962478` |
| `deal_type` | string | `buy` or `rent` |
| `region` | string|null | Hong Kong Island / Kowloon / New Territories |
| `subregion` | string|null | Sub-region, e.g. Tsuen Wan |
| `district` | string|null | District, e.g. Ma Wan |
| `sm_district` | string|null | Smaller district subdivision |
| `estate` / `estate_id` | string|null | Estate name and Midland estate ID |
| `phase` | string|null | Phase, e.g. `Phase 1` |
| `building` / `building_id` | string|null | Building name and ID |
| `price_hkd` | number|null | Asking price in HKD. `null` for rentals |
| `rent_hkd` | number|null | Monthly rent in HKD. `null` for sale listings |
| `net_area_sqft` | number|null | Saleable area in square feet |
| `gross_area_sqft` | number|null | Gross area in square feet |
| `net_to_gross_ratio` | number|null | Efficiency ratio (%) |
| `net_unit_price_hkd` | number|null | Price per saleable square foot |
| `monthly_mortgage_hkd` | number|null | Midland's estimated monthly mortgage payment |
| `bedroom` / `sitting_room` | number|null | Bedroom / living room count |
| `orientation` | string|null | Facing direction |
| `primary_school_net` | number|null | Primary school net number |
| `has_video` / `has_photo_360` | boolean|null | Whether a video / 360° photo exists |
| `tags` | string\[]|null | Listing tags, e.g. `sole` (sole agency) |
| `agent_name_zh` / `agent_name_en` | string|null | Agent name |
| `latitude` / `longitude` | number|null | Coordinates |
| `photo_urls` | string\[]|null | Photo URLs |
| `listing_url` | string|null | Midland listing page |
| `updated_at` | string|null | Listing update time (ISO 8601) |
| `scraped_at` | string | Scrape time (ISO 8601) |

#### Transactions (`mode: transactions`)

`transaction_id`, `region` / `subregion` / `district`, `estate` / `estate_id`, `building`, `floor`, `flat`, `price_hkd`, `net_area_sqft`, `gross_area_sqft`, `net_unit_price_hkd`, `transaction_date`, `transaction_type`, `source` (e.g. `LANDREG` for Land Registry), `tags`, `latitude` / `longitude`, `transaction_url`, `updated_at`, `scraped_at`

#### Estates (`mode: estates`)

`estate_id`, `name`, `region` / `subregion` / `district` / `sm_district`, `first_op_date` (occupation date), `total_unit_count`, `total_block_count`, `primary_school_net`, `sale_listing_count` / `rent_listing_count`, `net_unit_price_hkd` (recent price per saleable sq ft), `recent_transaction_count`, `amenities` (nearby facilities with walking minutes), `latitude` / `longitude`, `photo_url`, `estate_url`, `scraped_at`

> Field names are always English snake\_case. **Missing values are always `null` — fields are never omitted**, so the output drops straight into a table.

### Modes

| Mode | What you get | Typical use |
|---|---|---|
| `search` | Listings for sale / rent | Cross-agency inventory comparison, sourcing, price monitoring |
| `transactions` | Historical sales records | Building your own price index, valuation models, trend analysis |
| `estates` | Estate directory and stats | Estate reference database, supply analysis |

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | enum | `search` | `search` / `transactions` / `estates` |
| `dealType` | enum | `buy` | `buy` or `rent` (ignored in Estates mode) |
| `regions` | string\[] | all | Hong Kong Island / Kowloon / New Territories; empty = all three |
| `bulkRegions` | string | — | One per line, merged with the field above |
| `bedrooms` | integer | — | Exactly N bedrooms |
| `bathrooms` | integer | — | Exactly N bathrooms |
| `schoolNet` | integer | — | Specific primary school net |
| `sort` | enum | `default` | Newest / price / saleable area / price per sq ft, ascending or descending |
| `language` | enum | `en` | Language of region and estate names (field names stay English) |
| `maxItems` | integer | 50 | Max rows for this run, max 500 |
| `proxyConfiguration` | object | residential | **Keep the default** — see Notes |

### Input examples

**1. 100 Kowloon listings for sale**

```json
{ "mode": "search", "dealType": "buy", "regions": ["Kowloon"], "maxItems": 100 }
```

**2. Three-bedroom listings across Hong Kong, cheapest price per sq ft first**

```json
{ "mode": "search", "dealType": "buy", "bedrooms": 3, "sort": "net_ft_price", "maxItems": 200 }
```

**3. Hong Kong Island rentals with Chinese estate names**

```json
{ "mode": "search", "dealType": "rent", "regions": ["Hong Kong Island"], "language": "zh-hk", "maxItems": 50 }
```

**4. New Territories transaction records**

```json
{ "mode": "transactions", "dealType": "buy", "regions": ["New Territories"], "maxItems": 300 }
```

**5. Full estate directory**

```json
{ "mode": "estates", "maxItems": 500 }
```

### Notes

- **Keep the proxy default (Apify residential).** Midland returns 403 to datacenter egress — even the page that issues the access token is blocked. Turning the proxy off or switching it to datacenter yields zero rows. JSON responses are about 4KB per row, so residential traffic cost is negligible.
- **The only filters that actually work are `regions`, `bedrooms`, `bathrooms` and `schoolNet`.** Midland's public API silently ignores estate-name, sub-district, price-range and area-range filters — sending them changes nothing in the response. This Actor deliberately does not send them, so you are never misled into thinking a filter applied. To filter by estate or price, filter the output yourself.
- `maxItems` is the total across all regions, not per region.
- `rent_hkd` is `null` on sale listings and `price_hkd` is `null` on rentals. Midland reports `0` in those fields; we normalise `0` to `null` so you never see a "HK$0" property.
- If new listings appear while scraping, pages shift and rows can repeat. This Actor de-duplicates across the whole run, so each row is output — and charged — only once.
- Public data only. No login, no authentication bypass.

### Pricing

A single event, `search-listing`, at **$0.002 per row output** (same in all three modes). Only rows successfully written to the dataset are charged; duplicates and filtered-out rows are not.

### FAQ

**Q: Can I scrape a single estate, e.g. Taikoo Shing?**
A: Not directly — Midland's public API has no estate or keyword filter (we tested `keyword`, `keywords`, `estate_id`, `q` and others; all are ignored or break the response). Use `estates` mode to pull the estate directory, or scrape the region and filter on the `estate` field.

**Q: How far back do the transaction records go?**
A: The database holds 1.86 million records, sourced from Land Registry filings (`source: LANDREG`) among others. Currently they can be paged by region and sale/rent only — there is no date-range parameter.

**Q: What's the difference between `net_area_sqft` and `gross_area_sqft`?**
A: `net_area` is saleable area and `area` is gross area; `net_to_gross_ratio` is the efficiency ratio. Hong Kong has required saleable-area disclosure since 2013, so prefer `net_area_sqft`. Some listings have no gross area, in which case the field is `null`.

**Q: Can I merge this with Centaline or Ricacorp data?**
A: Yes — that's a primary use case. All three output snake\_case fields, region hierarchies and estate names, which suits agency-vs-agency inventory and pricing comparison. Estate naming differs slightly between sites, so fuzzy-match on district plus estate name.

**Q: Why does it need a residential proxy? Is that expensive?**
A: Midland blocks datacenter IPs, so without residential you get no data at all. The API is very compact (about 4KB per row), so measured traffic cost is far below the per-row price.

**Q: I got fewer rows than `maxItems`.**
A: That filter combination is exhausted. Loosen the filters (drop `bedrooms`, say) or scrape another region.

### Disclaimer

This Actor is intended for lawful purposes only, such as personal research, academic analysis, market research and property trend studies. Please comply with Midland Realty's (midland.com.hk) terms of service and applicable Hong Kong law. Do not scrape at excessive volume and do not resell raw data. The author accepts no liability for any damage arising from use of this tool.

***

Hong Kong property scraper Midland Realty scraper HK real estate data property listings transaction records estate directory saleable area price per square foot Hong Kong estate agency property data export HK housing market research valuation model

***

### 📚 Related tutorials & tools

Hands-on guides and free tools built on this actor, at **[AI Social API](https://aisocialapi.com/?utm_source=apify\&utm_medium=readme\&utm_campaign=midland-scraper-en)**:

- 📖 Tutorial: [Hong Kong's CCL Index is up 12% in 2026 — and Sha Tin First City still sold at a HK$1M loss](https://aisocialapi.com/learn/hk-ccl-index-granularity-gap/?utm_source=apify\&utm_medium=readme\&utm_campaign=midland-scraper-en)
- 📊 Data: [Site coverage: modes, fields & pricing](https://aisocialapi.com/coverage/?utm_source=apify\&utm_medium=readme\&utm_campaign=midland-scraper-en) — 40 sites, their modes, the exact output fields each returns, and per-item pricing — generated from the actors' own schemas
- 🛠 Free tools: [Hong Kong property lookup](https://aisocialapi.com/tools/hk-property/?utm_source=apify\&utm_medium=readme\&utm_campaign=midland-scraper-en) · [Apify cost calculator](https://aisocialapi.com/tools/apify-cost/?utm_source=apify\&utm_medium=readme\&utm_campaign=midland-scraper-en)
- 🤝 Rather not run it yourself? [I run it for you](https://aisocialapi.com/monitor/?utm_source=apify\&utm_medium=readme\&utm_campaign=midland-scraper-en) — scheduled daily, delivered to your Google Sheet or Slack, from US$299/mo.

Maintained by an operator running 40+ live Apify actors.

# Actor input Schema

## `mode` (type: `string`):

What to scrape. Pick one mode and fill in the matching fields below.

## `dealType` (type: `string`):

Scrape for-sale or for-rent listings. Ignored in Estates mode.

## `regions` (type: `array`):

Which regions to scrape. Leave empty to cover all three. Accepts Hong Kong Island, Kowloon, New Territories (Chinese names also work).

## `bulkRegions` (type: `string`):

Paste many at once: one region per line. Merged with the field above.

## `bedrooms` (type: `integer`):

Keep only properties with exactly N bedrooms. Leave empty for no filter.

## `bathrooms` (type: `integer`):

Keep only properties with exactly N bathrooms. Leave empty for no filter.

## `schoolNet` (type: `integer`):

Keep only properties in this primary school net, e.g. 41 (Kowloon City) or 62 (Yuen Long). Leave empty for no filter.

## `sort` (type: `string`):

How to order the listings.

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

Language for region, estate and building names. Field names are always English snake\_case.

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

Maximum rows to output for this run, across all regions combined. Default 50, max 500.

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

Runs through the Apify residential proxy by default. Midland returns 403 to datacenter egress — even the page that issues the access token is blocked — so residential is a hard requirement for this Actor to work at all (measured: direct 200, datacenter 403, residential 200). JSON responses are about 4KB per row, so proxy traffic cost is negligible. Turn it off only for your own proxy or local testing.

## Actor input object example

```json
{
  "mode": "search",
  "dealType": "buy",
  "regions": [
    "Kowloon"
  ],
  "sort": "default",
  "language": "en",
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `dataset` (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 = {
    "mode": "search",
    "dealType": "buy",
    "regions": [
        "Kowloon"
    ],
    "language": "en",
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("claude_code_reviewer/midland-scraper-en").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 = {
    "mode": "search",
    "dealType": "buy",
    "regions": ["Kowloon"],
    "language": "en",
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("claude_code_reviewer/midland-scraper-en").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 '{
  "mode": "search",
  "dealType": "buy",
  "regions": [
    "Kowloon"
  ],
  "language": "en",
  "maxItems": 50
}' |
apify call claude_code_reviewer/midland-scraper-en --silent --output-dataset

```

## MCP server setup

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

```

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/9pJNRUw2jfaI43i53/builds/g6JibpTQKA43kxnkQ/openapi.json
