# 591.com.tw Scraper - Rentals, Sales, Land & Commercial Listings (`abotapi/591-com-tw-scraper`) Actor

Extract property listings from 591房屋交易網 591.com.tw, Taiwan’s largest real estate marketplace. Search by city or use 591 URLs. Covers rentals, sales, land, and commercial properties. Returns 50–60 fields, including price, area, GPS coordinates, nearby transit, and agent/owner contact details.

- **URL**: https://apify.com/abotapi/591-com-tw-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Real estate, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 46 total users, 17 monthly users, 100.0% runs succeeded, 2 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.00 / 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

## 591.com.tw Scraper: Rent, Sale, Land and Commercial Listings

591.com.tw Scraper collects structured property listings from 591.com.tw, Taiwan's largest property marketplace, across four sections: Rent (租屋), Sale (買屋, resale and new homes), Land (土地), and Commercial (店面, 辦公, 廠房). Search by city and county with price and area filters, or paste any 591 list URL straight from your browser. Every record is a flat JSON object with up to 50 to 60 populated fields when detail fetching is on, including agent or owner contact details, GPS coordinates, and nearby transit with walking distances. Turn on Incremental mode for recurring monitoring, export to JSON, CSV or Excel, or pull results through the API.

### Why This Scraper?

- **Four sections in one actor.** Switch between Rent, Sale, Land, and Commercial, each returning that section's own native fields.
- **Deep records.** Up to 50 to 60 populated fields per listing when detail fetching is on, including contact name, mobile, phone, LINE and email, full GPS coordinates, nearby bus and metro stops with distances, lease rules, deposit, fees, and the full photo gallery.
- **Two ways to search.** Pick a section, regions, and filters, or paste 591 list URLs you already refined in your browser; pasted URLs keep every filter they carry.
- **Complete pagination by default.** A search or URL walks the whole result set until the storefront naturally runs out, bounded only by your Max total listings cap, so you decide the size of a run instead of an artificial page limit.
- **Built for monitoring.** Turn on Incremental mode to get only NEW and UPDATED listings on every scheduled run, or resume one specific interrupted walk with `resumeFromRunId`.
- **Resilient delivery.** Records are written one at a time as they're collected, so an interrupted run still keeps everything gathered so far, and the run automatically retries with a different connection when one is refused.
- **All of Taiwan.** Every city and county 591 covers, from Taipei City to Kinmen County.

### Use Cases

- **Property portals and lead generation:** aggregate rent and sale listings with agent or owner contact details to build your own listing feed or lead pipeline.
- **Market research and appraisal:** track price, unit price, and area across regions and property types to build comparable-sales datasets.
- **Relocation and rental search tools:** filter by region, price, and area to power a rental matching app or alert bot.
- **Land and commercial site scouting:** search plots and commercial units by region for feasibility and investment screening.
- **Recurring monitoring:** watch a saved search on a schedule and forward only new or changed listings into your own alerting system.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live listing.

| Field | Example |
| --- | --- |
| `id` | `99999999` |
| `section` | `"rent"` (also `"sale"`, `"land"`, `"commercial"`) |
| `listingType` | `"rent"` (also `"sale"`) |
| `url` | `"https://rent.591.com.tw/99999999"` |
| `title` | `"Sample Listing Title"` |
| `price` / `priceText` | `18000` / `"18,000"` |
| `priceUnit` | `"元/月"` |
| `kindName` | `"整層住家"` |
| `room` | `"2房1廳"` |
| `area` | `18` |
| `floor` | `"5F"` |
| `regionName` / `sectionName` | `"台北市"` / `"中山區"` |
| `address` | `"中山區 Sample Rd"` |
| `latitude` / `longitude` | `25.0600` / `121.5300` |
| `communityName` | `"Sample Community"` |
| `deposit` | `"押金二個月"` |
| `facilities` / `tags` | `["冰箱","洗衣機","冷氣","網路"]` / `["新上架","可開伙"]` |
| `nearbyTransit` | `[{"type":"subway","name":"Sample Station","distance":300}]` |
| `photoCount` / `coverPhoto` | `12` / `"https://img1.591.com.tw/house/0000/000000000.jpg"` |
| `postedAt` | `"此房屋在3天前發佈"` |
| `contactName` / `contactRole` | `"Sample Agent"` / `"仲介"` |
| `contactMobile` / `contactLine` | `"0900000000"` / `"sample-line-id"` |
| `isAgent` | `true` |
| `scrapedAt` | `"2026-01-01T00:00:00.000Z"` |

Sale, Land, and Commercial records add section specific fields where that section publishes them, such as `unitPrice`, `mainArea`, `shape`, `houseAge`, `direction`, `roomCount`, `hallCount`, `bathroomCount`, `balconyCount`, and `allFloors`. `contactPhone`, `contactEmail`, `leaseMinTerm`, and `rule` are also part of every record's shape but are commonly empty on Rent listings and populated more often on Sale, Land, and Commercial listings, only when that listing itself publishes them. In Incremental mode, every record above also carries `changeType` (`NEW`, `UPDATED`, `REAPPEARED`, or `EXPIRED`; unchanged listings are suppressed and not returned), `changedFields` on `UPDATED` rows, and `firstSeenAt` / `lastSeenAt` timestamps.

### How to Use

1. Pick a **mode**: `search` (choose a section, regions, and filters) or `url` (paste 591 list URLs).
2. For Search mode, pick a **section** (Rent, Sale, Land, Commercial), one or more **regions**, and any price/area filters.
3. Turn **Visit each listing's detail page** on for the full field set, or off for a faster, cheaper, list-level-only run.
4. Set **Max total listings** to control run size and cost, then click **Start**. Download the dataset as JSON, CSV, or Excel, or read it through the API.

**Search rentals in Taipei and New Taipei City within a price range:**

```json
{
  "mode": "search",
  "section": "rent",
  "regions": ["taipei-city", "new-taipei-city"],
  "minPrice": 10000,
  "maxPrice": 25000,
  "fetchDetails": true,
  "maxPages": 5,
  "maxListings": 200
}
```

**Search resale and new homes for sale:**

```json
{
  "mode": "search",
  "section": "sale",
  "regions": ["taichung-city"],
  "minArea": 20,
  "maxPages": 10,
  "maxListings": 300
}
```

**Filter rentals by type (whole home, suite, shared suite, single room, parking):**

```json
{
  "mode": "search",
  "section": "rent",
  "regions": ["kaohsiung-city"],
  "rentKind": "whole",
  "fetchDetails": true,
  "maxPages": 3
}
```

**Paste 591 list URLs (several at once; the price, area, and rental type filters above still narrow the results of pasted list pages; a pasted single-listing page always returns that one listing):**

```json
{
  "mode": "url",
  "urls": [
    "https://rent.591.com.tw/list?region=1&kind=2",
    "https://land.591.com.tw/list?type=2&region=1&kind=11"
  ],
  "maxPages": 5,
  "maxListings": 150
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/591-com-tw-scraper").call(run_input={
    "mode": "search",
    "section": "rent",
    "regions": ["taipei-city"],
    "maxListings": 50,
})
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(listing["title"], listing["price"], listing["priceUnit"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/591-com-tw-scraper').call({
  mode: 'search',
  section: 'rent',
  regions: ['taipei-city'],
  maxListings: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets, or webhooks from the **Integrations** tab.

#### URL mode

Set `mode` to `url` and paste any of these 591 address shapes. Each is handled the same way every time:

| Shape | Example | Result |
| --- | --- | --- |
| Rent list | `https://rent.591.com.tw/list?region=1` | Walks the list; region, `kind`, `price`, sort and `page` parameters in the URL are applied. |
| Rent list, later page | `https://rent.591.com.tw/list?region=1&page=2` | Starts at page 2 and walks forward. |
| Rent list, filtered and sorted | `https://rent.591.com.tw/list?region=3&kind=2&price=10000_20000&order=posttime&orderType=desc` | The filters and sort order in the address are kept. |
| Rent site root | `https://rent.591.com.tw/` | Treated as the Taipei City rent list. Offered as a prefill example only, not in the shipped default, because it returns the same rows as the first rent list. |
| Rent listing | `https://rent.591.com.tw/22206852` | One row for that listing. |
| Sale list | `https://sale.591.com.tw/?shType=list&regionid=1` | Walks the sale list. |
| Sale list endpoint | `https://bff-house.591.com.tw/v1/web/sale/list?type=2&category=1&regionid=1&firstRow=0&shType=list` | Walks the sale list from the given offset. |
| Land list | `https://land.591.com.tw/list?type=2&region=1&kind=11` | Walks the land list. |
| Land listing | `https://land.591.com.tw/sale/21103521` | One row for that listing. |
| Commercial list | `https://business.591.com.tw/list?type=1&region=1` | Walks the commercial list. |
| Commercial listing | `https://business.591.com.tw/rent/21777031` | One row for that listing. |
| New-build project | `https://newhouse.591.com.tw/137287` | One row for that project (`section` is `newhouse`). |

In URL mode the rental type, price and area filters and Sort by (rent lists) still apply to pasted list pages; a pasted single-listing page always returns that one listing; Rooms and Keyword are Search-mode only.

Addresses of any other shape, such as the mobile host or the old business search form, are skipped with a warning in the run log, never silently turned into an empty result.

Behaviour change: the `urls` field now ships with one example per distinct shape above as its default (the rent site root is left out because it resolves to the first rent list). Running URL mode with the field left untouched scrapes those examples (capped by Max listings); clear the field and paste your own addresses to scrape only those. The alias `startUrls` is also accepted: when it is given and `urls` is still the untouched example default, only your `startUrls` are scraped. A single listing page always loads its own detail page, so it returns a full row even when Fetch details is off.

#### Walking the whole catalogue

By default `maxPages` is `0` (unlimited): a search or URL entry walks every result page until the storefront naturally runs out, either an empty page or a page that keeps repeating listings already collected, or until `maxListings` (the cap on the whole run, default 20, set `0` for unlimited, split evenly across the regions or URLs you list) is reached, whichever comes first. Set `maxPages` to a number only when you want an explicit page cap below that natural stop.

For a very large walk-all pull, use `resumeFromRunId` in a follow-up run instead of starting over: paste the ID of a previous run (or its dataset) and this run loads the listings it already saved, skips them, and appends only new listings to a fresh dataset.

#### Resume and recurring updates

Two different needs use two different fields. **Resume** (`resumeFromRunId`) continues one specific interrupted or previous large walk: paste a run ID or dataset ID and this run skips listings already collected there, returning only the remaining new ones. An automatic same-run checkpoint also protects against platform migrations without any input needed.

**Incremental mode** (`incrementalMode`) is for a schedule, for example daily: the actor remembers the previous run of the same search by itself, so you never paste a run ID. The first run returns everything as `NEW`. Later runs return only `NEW` and `UPDATED` listings; unchanged listings are suppressed, not returned, and not charged. Turn on `emitExpired` to also learn when a tracked listing disappears; once a run with `emitExpired` on has completed a full, uncapped scan, a listing that comes back afterward is returned as `REAPPEARED`, and one that stays gone is returned as `EXPIRED` and billed for. Change detection compares every output field, including nested values. Left out of the comparison but kept in the output: `scrapedAt`, `sourceUrl`, `pageIndex`, the view counters (`browseCount`, `browsePc`, `browseMobile`), the site's own `updatedAt` and `refreshedAt`, and the rotating photo-server number in photo URLs; add your own with `ignoreFieldsForChanges`. EXPIRED detection only runs after a complete scan: a Max listings or Max pages cut, a pasted URL starting at `?page=2` or later, or a resumed run never marks anything EXPIRED. State is isolated per section, region, URL, and filter combination automatically; set `stateKey` to name or deliberately share a monitoring campaign.

The actor also checkpoints its own progress internally, so if a run is interrupted by a platform migration it picks back up without re-saving, and re-charging for, listings it already wrote.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape, the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human readable **summary** of each record, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `"search"` | Two ways to drive the scraper: `search` (pick section, regions, and filters) or `url` (paste 591 list URLs). |
| `section` | string | `"rent"` | Which 591 marketplace to scrape: `rent`, `sale`, `land`, or `commercial`. Search mode only; a pasted URL carries its own section. |
| `regions` | array | prefill `["taipei-city"]` | One or more Taiwan cities or counties to search. Search mode only. |
| `rentKind` | string | `"any"` | Rental dwelling type: `whole`, `suite`, `shared-suite`, `single-room`, or `parking`. Rent section only; applies in both Search mode and URL mode. |
| `layout` | string | `"any"` | Rent, Search mode only. Bedroom count: `1`, `2` or `3`. |
| `keyword` | string | empty | Rent, Search mode only. Free-text keyword passed to 591 (a station or district name works well). |
| `sortBy` | string | `"default"` | Rent lists (Search mode and pasted rent list URLs): `newest`, `oldest`, `price-asc`, `area-asc`, `area-desc`. Other sections keep the site order. |
| `maxItemsPerUrl` | integer | empty | URL mode only. Caps the rows taken from each pasted URL. |
| `minPrice` | integer | (none) | Minimum price, in the same unit the listing shows: TWD per month for Rent, 萬 (10,000 TWD) for Sale, Land, and Commercial. Applies in Search mode and URL mode, including a pasted list URL (a pasted single-listing URL is always returned). |
| `maxPrice` | integer | (none) | Maximum price, same unit as Min price. Applies in Search mode and URL mode, including a pasted list URL (a pasted single-listing URL is always returned). |
| `minArea` | integer | (none) | Minimum floor area in 坪 (ping, about 3.3 square meters). Applies in Search mode and URL mode. |
| `maxArea` | integer | (none) | Maximum floor area in 坪 (ping). Applies in Search mode and URL mode. |
| `urls` | array | one example per supported shape (see "URL mode") | One or more 591 URLs: rent, sale, land, commercial list pages, single listing pages, and new-build project pages. URL mode only. Also accepted as `startUrls`; when both are sent and `urls` is the untouched example default, only `startUrls` is used. Pagination starts at the page in the URL and walks forward. |
| `fetchDetails` | boolean | `true` | Visit each listing's detail page for the full field set: description, contact details, lease rules, deposit, fees, nearby transit, and the full photo gallery. Turn off for a faster, cheaper, list-level-only run. |
| `maxListings` | integer | `20` | Cap on the whole run, split evenly across the regions or URLs you list. `0` means unlimited. |
| `maxPages` | integer | `0` | Safety bound on result pages walked per region or URL. `0` (default) means unlimited: the walk stops on its own once a page comes back empty or repeats listings already collected, or sooner once Max total listings is reached. |
| `resumeFromRunId` | string | (none) | Paste a previous run ID or dataset ID to continue one specific interrupted walk: listings it already saved are loaded and skipped, so this run only appends new listings. |
| `incrementalMode` | boolean | `false` | Turn on for recurring monitoring of the same search. The first run returns everything as `NEW`; later runs return only `NEW` and `UPDATED` listings, unchanged ones are suppressed. `REAPPEARED` needs `emitExpired` plus a prior full, uncapped scan, see "Resume and recurring updates" above. |
| `stateKey` | string | (none) | Optional name for a monitoring campaign, to keep its incremental state stable or deliberately share it. Auto derived from the section, regions or URLs, and filters when left empty. |
| `emitExpired` | boolean | `false` | Incremental mode only. Also return listings from a previous run no longer found, marked `EXPIRED`, once a run has fully scanned the tracked search. |
| `ignoreFieldsForChanges` | string\[] | `[]` | Incremental mode only. Field names left out of change detection (kept in the output). A bare name matches at any depth, a dotted path such as `nearbyTransit.distance` matches one nested field. |
| `proxy` | object | prefill `{"useApifyProxy": true}` | Connection configuration. Defaults to the lower cost connection tier; a more robust tier is used automatically as a fallback. |
| `maxResidentialRequests` | integer | `200` | Caps how many requests may use the more robust connection tier during automatic fallback. `0` turns off the automatic fallback to that tier. |
| `mcpConnectors` | array | (none) | Optional: send a summary of each listing to apps authorized under Apify Integrations (Notion, Linear, Airtable, or Apify). |
| `notionParentPageUrl` | string | (none) | Notion connector only: the page under which listing pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. Does not affect the dataset. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live listing.

```json
{
  "id": 99999999,
  "section": "rent",
  "listingType": "rent",
  "url": "https://rent.591.com.tw/99999999",
  "title": "Sample Listing Title",
  "description": "Full listing description appears here when fetchDetails is on.",
  "price": 18000,
  "priceText": "18,000",
  "priceUnit": "元/月",
  "deposit": "押金二個月",
  "kindName": "整層住家",
  "room": "2房1廳",
  "area": 18,
  "floor": "5F",
  "regionName": "台北市",
  "sectionName": "中山區",
  "address": "中山區 Sample Rd",
  "latitude": 25.0600,
  "longitude": 121.5300,
  "communityName": "Sample Community",
  "facilities": ["冰箱", "洗衣機", "冷氣", "網路"],
  "nearbyTransit": [{ "type": "subway", "name": "Sample Station", "distance": 300 }],
  "tags": ["新上架", "可開伙"],
  "photoCount": 12,
  "coverPhoto": "https://img1.591.com.tw/house/0000/000000000.jpg",
  "postedAt": "此房屋在3天前發佈",
  "contactName": "Sample Agent",
  "contactRole": "仲介",
  "contactMobile": "0900000000",
  "contactLine": "sample-line-id",
  "isAgent": true,
  "scrapedAt": "2026-01-01T00:00:00.000Z"
}
```

#### Output views

The dataset has three views, open them from the run's Output tab or add `?view=<name>` to the API items URL:

- **overview**: one row per listing with title, URL, price, kind, rooms, area, region, address, coordinates, main contact, photo count, and the incremental columns (`changeType`, `changedFields`, `firstSeenAt`, `lastSeenAt`).
- **detail**: the full-record fields from the detail page: price text, deposit, floor, community, description, every contact field, nearby transit, facilities, fees, lease term, and rules.
- **changes**: the incremental view, with `changeType`, `changedFields`, first and last seen timestamps, price, and unit.

Runs with several regions or URLs also save a per-search count summary under the `OUTPUT` key of the run's key-value store, so the output link always resolves.

### Plan Requirement

No special plan is needed to read 591.com.tw. In testing, a datacenter-class connection loaded both list pages and listing pages with real content and no challenge, and the default connection tier is the lower cost one. If a connection is refused, the run retries with a different one and escalates to the more robust residential tier only when the default tier is repeatedly refused, capped by `maxResidentialRequests`, then returns to the default tier once the connection is healthy. Heavy or frequent runs were not load-tested, so for those pick the more robust tier directly under Connection.

Apify free plans do not include residential proxies. If the run needs that tier and your plan lacks it, the log says: "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." When no page could be loaded at all, the run ends as failed instead of returning an empty success, with one of these messages:

- "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required. If you entered your own proxy URLs, check that their credentials are valid."
- "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested 591 pages. Upgrade your plan or supply your own proxy URLs, then try again."
- "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."

A search that loads fine but matches nothing finishes normally with an empty dataset.

### FAQ

#### How much does it cost?

Billing is pay per event, at the rates shown on the **Pricing** tab. The events are: `apify-actor-start` (once per run), `apify-default-dataset-item` (once per listing row delivered to the dataset), and `detail-enrichment` (once per listing whose detail page was fetched, when **Visit each listing's detail page** is on, the default). Turn details off for a cheaper list-level run. In Incremental mode, suppressed unchanged listings are not charged. Use **Max total listings** to cap the cost of any run.

#### Is it legal to scrape 591.com.tw?

This actor collects only publicly available property listing data. You are responsible for how you use it: follow 591.com.tw's terms and the laws that apply to you, and get legal advice before any commercial redistribution, especially of contact details.

#### Can I get only new or changed listings on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only `NEW` and `UPDATED` listings; unchanged ones are suppressed and not billed. Turn on **Emit expired** too, and once a run has scanned the whole search end to end, a listing that reappears afterward comes back as `REAPPEARED`.

#### What is the difference between Resume and Incremental mode?

**Resume** (`resumeFromRunId`) continues one specific interrupted run, so you don't re-scrape what it already saved. **Incremental mode** is for running the exact same search again and again, for example daily, and getting back only what changed since last time; it remembers state itself, so you never paste a run ID.

#### Why did my run return no listings?

Check your filters and region first. If 591.com.tw refused the connection for the whole run, the log says so and the run still finishes with an empty dataset rather than failing; run it again in a few minutes, or switch to the more robust connection tier if you are consistently blocked.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push a summary of each listing into Notion, Linear, or Airtable.

### 🔗 Want more real estate data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🏠 <a href="https://apify.com/abotapi/apartments-com-scraper"><b>Apartments.com Scraper</b></a><br>Scrape Apartments.com rentals by city, ZIP, neighborhood, address or URL. Extract rent...</td><td>🏠 <a href="https://apify.com/abotapi/propertyguru-my-scraper"><b>PropertyGuru MY Scraper</b></a><br>Scrape PropertyGuru.com.my sale and rental listings with 30+ structured fields, including...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/realcommercial-au-scraper"><b>Commercial Property AU Scraper</b></a><br>Scrape Australian commercial property listings with full detail data. Extract...</td><td>🏠 <a href="https://apify.com/abotapi/ddproperty-scraper"><b>DDproperty Thailand Scraper</b></a><br>Scrape DDproperty.com Thailand listings at scale. Extract prices, property features...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/reiwa-com-au-scraper"><b>REIWA Scraper</b></a><br>Scrape REIWA.com.au across sale, rental, sold, commercial, business and rural properties...</td><td>🚗 <a href="https://apify.com/abotapi/dubizzle-com-scraper"><b>Dubizzle.com Scraper</b></a><br>Scrape dubizzle.com across the UAE for property, motors, and agent listings. Search by...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/591-com-tw-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

Search mode = pick a property section + regions + filters below. URL mode = paste 591 list URLs you already refined in a browser.

## `section` (type: `string`):

Which 591 marketplace to scrape. Rent = 租屋 (rentals). Sale = 中古屋/新建案 (resale + new homes). Land = 土地 (plots). Commercial = 店面/辦公/廠房 (shops, offices, factories).

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

One or more Taiwan cities/counties to search. Maps to 591 region codes automatically.

## `rentKind` (type: `string`):

Only applies when Section = Rent. Narrows by dwelling type. Applies in Search mode and URL mode (rent listings only).

## `minPrice` (type: `integer`):

Minimum price. Rent = TWD/month, Sale/Land/Commercial = TWD total (萬 are converted automatically). Leave blank for no minimum. Applies in Search mode and URL mode, including a pasted list URL (a pasted single-listing URL is always returned).

## `maxPrice` (type: `integer`):

Maximum price. Same unit as Min price. Leave blank for no maximum. Applies in Search mode and URL mode, including a pasted list URL (a pasted single-listing URL is always returned).

## `minArea` (type: `integer`):

Minimum floor area in 坪 (ping, 1 ping ≈ 3.3 m²). Leave blank for no minimum. Applies in Search mode and URL mode.

## `maxArea` (type: `integer`):

Maximum floor area in 坪 (ping). Leave blank for no maximum. Applies in Search mode and URL mode.

## `layout` (type: `string`):

Rent only, Search mode. Number of bedrooms (591 layout filter). Probe-verified on the rent list.

## `keyword` (type: `string`):

Rent only, Search mode. Free-text keyword matched by 591 (for example a metro station or district name, Chinese works best).

## `sortBy` (type: `string`):

Order of rent list results, in Search mode and applied to pasted rent list URLs. Other sections keep the site default order. Newest = most recently posted first.

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

One or more 591 URLs. Supported: rent list pages (region, page and filter/sort parameters are honoured), the rent site root, rent listing pages, sale list pages (the sale list address or its list endpoint), land list and land detail pages, commercial list and commercial detail pages, and new-build project pages (newhouse.591.com.tw). A detail page returns exactly one row. Pagination starts at the page in the URL and walks forward. Unsupported shapes (for example the mobile host or the old business search form) are skipped with a warning in the log. In URL mode the rental type, price and area filters and Sort by (rent lists) still apply to pasted list pages; a pasted single-listing page always returns that one listing; Rooms and Keyword are Search-mode only. Left untouched in URL mode, this field scrapes the example URLs below (the rent site root is offered as a prefill example only, because it resolves to the first rent list). Also accepted under the name startUrls: when startUrls is given and this field is left at its example default, only your startUrls are scraped.

## `fetchDetails` (type: `boolean`):

Adds the rich detail fields: full description, agent/owner name + mobile + phone + LINE, lease rules, deposit, fees, nearby transit (bus/MRT) with distances, full high-res photo gallery, and certificate info. Turn OFF for a faster, cheaper run with the list-level fields only (title, price, area, layout, address, primary photos).

## `maxListings` (type: `integer`):

The main hard cap across the whole run. Default 20. Set 0 for unlimited (then the run is bounded only by Max pages). 591 returns ~30-50 listings per page.

## `maxItemsPerUrl` (type: `integer`):

URL mode only. Caps the rows taken from each pasted URL (in addition to Max listings). Empty = no per-URL cap.

## `maxPages` (type: `integer`):

Safety bound on how many result pages to walk per region (or per URL). 0 (default) means unlimited — the walk stops naturally once a page comes back empty or keeps repeating listings already collected, or sooner once Max total listings is reached. Set a number only for an explicit page cap below that natural stop.

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

Default: Apify Datacenter (free-tier friendly, lowest cost). For maximum reliability you may select Residential; the actor rotates exit IPs and countries (Taiwan first) and falls back to a backup pool if needed.

## `maxResidentialRequests` (type: `integer`):

Caps how many requests may use the (expensive) Residential tier during automatic fallback. 0 = never use residential (datacenter + backup only). Default 200. Has no effect if you do not enable residential and datacenter keeps working.

## `resumeFromRunId` (type: `string`):

Optional. Paste a previous run ID or dataset ID to continue a large walk-all pull: listings already saved by that run are loaded before scraping starts and skipped, so this run only appends new listings. Leave empty for a normal fresh run. For recurring daily/weekly monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring of the same search. The first run returns every matching listing as NEW. Later runs return only NEW, UPDATED, and REAPPEARED listings; unchanged listings are suppressed (not returned, not billed). What is compared: every output field of the listing (price, title, area, address, photos, contact, tags and the rest, including nested values). What is excluded from comparison but still kept in the output: scrapedAt, sourceUrl, pageIndex, the view counters (browseCount, browsePc, browseMobile), the site's own updatedAt and refreshedAt stamps, and the rotating photo-server number (img1/img2) in photo URLs; add more with "Ignore fields when detecting changes" below. Turn on "Emit expired listings" below only if you also want listings that disappeared marked EXPIRED. State is kept separately per section/region/URL/filter combination; use State key to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a prior run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the section, regions/URLs, and filters — different searches then never mix state with each other.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return listings that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search, not when Max listings capped it or when Resume was used. This returns and bills extra synthetic rows, so leave it off unless you need expiry tracking.

## `ignoreFieldsForChanges` (type: `array`):

Optional. Field names to leave out of change detection, so a change in them alone does not make a listing UPDATED. The fields stay in the output. A bare name such as description matches that field at any depth; a dotted path such as nearbyTransit.distance matches exactly that nested field. Already excluded by default: scrapedAt, sourceUrl, pageIndex, browseCount, browsePc, browseMobile, updatedAt, refreshedAt and the photo-server number. Only used when Incremental changes is on.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "section": "rent",
  "regions": [
    "taipei-city"
  ],
  "rentKind": "any",
  "layout": "any",
  "sortBy": "default",
  "urls": [
    "https://rent.591.com.tw/list?region=1",
    "https://rent.591.com.tw/list?region=1&page=2",
    "https://rent.591.com.tw/list?region=3&kind=2&price=10000_20000&order=posttime&orderType=desc",
    "https://rent.591.com.tw/",
    "https://rent.591.com.tw/22206852",
    "https://sale.591.com.tw/?shType=list&regionid=1",
    "https://bff-house.591.com.tw/v1/web/sale/list?type=2&category=1&regionid=1&firstRow=0&shType=list",
    "https://land.591.com.tw/list?type=2&region=1&kind=11",
    "https://land.591.com.tw/sale/21103521",
    "https://business.591.com.tw/list?type=1&region=1",
    "https://business.591.com.tw/rent/21777031",
    "https://newhouse.591.com.tw/137287"
  ],
  "fetchDetails": true,
  "maxListings": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true
  },
  "maxResidentialRequests": 200,
  "incrementalMode": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `summary` (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",
    "regions": [
        "taipei-city"
    ],
    "urls": [
        "https://rent.591.com.tw/list?region=1",
        "https://rent.591.com.tw/list?region=1&page=2",
        "https://rent.591.com.tw/list?region=3&kind=2&price=10000_20000&order=posttime&orderType=desc",
        "https://rent.591.com.tw/",
        "https://rent.591.com.tw/22206852",
        "https://sale.591.com.tw/?shType=list&regionid=1",
        "https://bff-house.591.com.tw/v1/web/sale/list?type=2&category=1&regionid=1&firstRow=0&shType=list",
        "https://land.591.com.tw/list?type=2&region=1&kind=11",
        "https://land.591.com.tw/sale/21103521",
        "https://business.591.com.tw/list?type=1&region=1",
        "https://business.591.com.tw/rent/21777031",
        "https://newhouse.591.com.tw/137287"
    ],
    "proxy": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/591-com-tw-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 = {
    "mode": "search",
    "regions": ["taipei-city"],
    "urls": [
        "https://rent.591.com.tw/list?region=1",
        "https://rent.591.com.tw/list?region=1&page=2",
        "https://rent.591.com.tw/list?region=3&kind=2&price=10000_20000&order=posttime&orderType=desc",
        "https://rent.591.com.tw/",
        "https://rent.591.com.tw/22206852",
        "https://sale.591.com.tw/?shType=list&regionid=1",
        "https://bff-house.591.com.tw/v1/web/sale/list?type=2&category=1&regionid=1&firstRow=0&shType=list",
        "https://land.591.com.tw/list?type=2&region=1&kind=11",
        "https://land.591.com.tw/sale/21103521",
        "https://business.591.com.tw/list?type=1&region=1",
        "https://business.591.com.tw/rent/21777031",
        "https://newhouse.591.com.tw/137287",
    ],
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/591-com-tw-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 '{
  "mode": "search",
  "regions": [
    "taipei-city"
  ],
  "urls": [
    "https://rent.591.com.tw/list?region=1",
    "https://rent.591.com.tw/list?region=1&page=2",
    "https://rent.591.com.tw/list?region=3&kind=2&price=10000_20000&order=posttime&orderType=desc",
    "https://rent.591.com.tw/",
    "https://rent.591.com.tw/22206852",
    "https://sale.591.com.tw/?shType=list&regionid=1",
    "https://bff-house.591.com.tw/v1/web/sale/list?type=2&category=1&regionid=1&firstRow=0&shType=list",
    "https://land.591.com.tw/list?type=2&region=1&kind=11",
    "https://land.591.com.tw/sale/21103521",
    "https://business.591.com.tw/list?type=1&region=1",
    "https://business.591.com.tw/rent/21777031",
    "https://newhouse.591.com.tw/137287"
  ],
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/591-com-tw-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/591-com-tw-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/edCqW82MdQB705tLf/builds/Sx7hRXmef4RZYudHe/openapi.json
