Zigbang Scraper - Korea Property Listings, Rent & Agents
Pricing
from $1.20 / 1,000 listing records
Zigbang Scraper - Korea Property Listings, Rent & Agents
Scrape Zigbang property listings across Seoul and South Korea, including one-room, villa and officetel rentals and sales. Extract deposits, monthly rent, prices, floor plans, property details, subway access, locations and agent contacts.
Pricing
from $1.20 / 1,000 listing records
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
1
Bookmarked
3
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Features
Share
Zigbang Scraper: Oneroom, Villa and Officetel Listings with Deposits, Rent and Agent Contacts
Zigbang Scraper turns Zigbang, one of South Korea's largest home rental marketplaces, into structured listing data. Search by a subway station, neighbourhood or district, or paste listing links or bare ids directly, and pull oneroom (원룸), villa (빌라) and officetel (오피스텔) listings with deposits, monthly rent, size, floor, address and coordinates. Turn on detail enrichment for the full description, in-room options, elevator, bathroom count, direction, parking, move in date, nearby subway lines and the listing's public agent contact block. Schedule the same search to get only new and changed listings, then export to JSON, CSV or Excel, or read the results straight through the API.
Why This Scraper?
- Two ways in. Resolve a Korean location keyword (a subway station, neighbourhood or district) and read every listing around it, or paste listing URLs or bare numeric ids and read exactly those.
- Three listing verticals. Oneroom, villa and officetel, each with server side deal type, deposit, rent, maintenance fee and size filters, so a filtered run reads only the matching slice.
- Detail enrichment on demand. Switch on a per listing pass for the full description, options, elevator, bathroom count, direction, parking, move in date, nearby subway lines and the agent's public office contact block, and pay the surcharge only for listings you actually get back.
- Resume picks up where you left off. Continue an interrupted pull without paying again for records a previous run already collected.
- Incremental mode for recurring searches. Schedule the same search again and again and get every listing classified NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED against your own stored baseline.
- A refused run fails loudly. If nothing could be read at all, the run stops with a clear message instead of quietly returning an empty dataset, so "no listings matched" is never confused with "the connection failed."
- Stable, flat records. Every listing ships as one flat row with a consistent field contract, ready for JSON, CSV or Excel export, or the API.
Use Cases
- Renters and relocation services: compare oneroom, villa and officetel options around a subway line before booking viewings.
- Brokerage and property management tools: refresh a district's market snapshot on a schedule and act only on the listings that actually changed.
- Market research and pricing analysts: track deposit and monthly rent ranges by neighbourhood over time.
- Real estate apps and aggregators: pull structured listing data, including public agent contacts, behind your own API or app.
Data You Get
Sample shape: values are illustrative placeholders, not from a live record.
| Field | Example |
|---|---|
itemId | 12345678 |
url | "https://www.zigbang.com/home/oneroom/items/12345678" |
serviceType | "원룸" (oneroom) |
title | "Newly painted one room with a private entrance" |
salesType | "월세" (wolse, deposit plus monthly rent) |
deposit / rent | 1000 / 55 (만원, i.e. 10,000,000 / 550,000 KRW) |
manageCost | "60000" (maintenance fee, in KRW) |
sizeM2 / exclusiveAreaM2 | 21.4 / 18.6 |
floor / buildingFloor | "4층" / "6" |
registeredAt | "2026-08-20T09:15:00+09:00" |
isNew | true |
tags | ["역세권", "반려동물가능"] |
district / neighborhood | "샘플구" / "샘플동" |
thumbnailUrl | "https://ic.zigbang.com/ic/items/12345678/1.jpg" |
description | "Recently renovated one room with a bright south facing window." (with detail enrichment) |
options | ["에어컨", "냉장고", "세탁기"] (with detail enrichment) |
elevator / bathroomCount | true / 1 (with detail enrichment) |
roomDirection / parkingAvailableText | "남향" / "가능" (with detail enrichment) |
moveinDate / viewCount | "즉시입주" / 312 (with detail enrichment) |
subways | [{"name": "샘플역", "lines": "2호선"}] (with detail enrichment) |
agent | office name, phone, address and registry number (with detail enrichment) |
changeType / changedFields | "UPDATED" / ["rent"] (incremental mode only) |
firstSeenAt / lastSeenAt | ISO timestamps (incremental mode only) |
The agent object carries the listing's public business contact block exactly as the source publishes it on the listing page: an office name, a name, an office phone, an office address and a registry number. It, along with description, options, elevator, bathroomCount, roomDirection, parkingAvailableText, moveinDate, viewCount and subways, ships only when detail enrichment (fetchDetails) is on. changeType, changedFields, firstSeenAt and lastSeenAt ship only when incremental mode is on; without it, records look exactly like the rows above minus those four fields.
How to Use
- Pick a mode:
search(resolve a location keyword) orurl(paste listing links or bare ids). - In search mode, choose the listing type and narrow with deal type, deposit, rent, maintenance fee and size filters. URL mode ignores these; each pasted listing is read as it is.
- Turn on Fetch listing details if you need the description, options and agent contact block, and set Max items to control run size and cost.
- Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.
Search around a subway station:
{"mode": "search","query": "강남역","serviceType": "oneroom","subwayRadiusKm": 2,"maxItems": 50}
Narrow with deal type and price bounds (bounds are in 만원 units, i.e. 10,000 KRW):
{"mode": "search","query": "홍대입구","serviceType": "villa","salesTypes": ["wolse"],"depositMax": 2000,"rentMax": 100,"maxItems": 50}
Search with detail enrichment on:
{"mode": "search","query": "마포구","serviceType": "officetel","fetchDetails": true,"maxItems": 30}
Paste listing URLs or bare ids (url mode):
{"mode": "url","urls": ["https://www.zigbang.com/home/oneroom/items/12345678", "12345679"]}
URL mode accepts these shapes: m.zigbang.com or www.zigbang.com listing pages for oneroom, villa and officetel (a query string is ignored), the /v3/items/<id> API form, bare numeric ids, and a /v3/search?q=<keyword> API URL, which is read like search mode with that keyword. Map pages (/home/oneroom/map) are not supported; each unsupported entry is skipped with a warning in the run log. A keyword that matches no location fails with an error instead of returning nothing.
| URL shape | Example |
|---|---|
| Listing page, oneroom | https://m.zigbang.com/home/oneroom/items/12345678 |
| Listing page, villa | https://m.zigbang.com/home/villa/items/12345678 |
| Listing page, officetel | https://m.zigbang.com/home/officetel/items/12345678 |
| Listing page with query string | https://m.zigbang.com/home/oneroom/items/12345678?foo=1 |
| Numeric id API form | https://<api host>/v3/items/12345678 |
| Keyword search API form | https://<api host>/v3/search?q=%EA%B0%95%EB%82%A8%EC%97%AD |
Behaviour change: in URL mode, leaving the urls field untouched now scrapes one example per shape above (duplicates collapse to one listing each) instead of failing. Replace the list with your own URLs for real runs.
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/zigbang-property-scraper").call(run_input={"mode": "search", "query": "강남역", "serviceType": "oneroom", "maxItems": 20})for listing in client.dataset(run["defaultDatasetId"]).iterate_items():print(listing["title"], listing["deposit"], listing["rent"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/zigbang-property-scraper').call({mode: 'search', query: '강남역', serviceType: 'oneroom', maxItems: 20,});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
More fields per listing
Rows also carry won-denominated twins of the prices (deposit_krw, rent_krw, currency, rent_per_m2_krw), address_region, and, with Fetch details on, images, image_count, pnu, bjd_code, building_id, danji_name, manage_cost_includes, approve_date, direction_criterion, address_jibun, updated_at and flat agent_name, agent_phone, agent_address. Billing is unchanged: the same events, on the same rows, as before.
Search by coordinate, search URL and sort
Besides a keyword, search mode can read around a coordinate (lat and lng, with precision for cell size and ring for neighbouring cells) or from pasted searchUrls (apis.zigbang.com listing list URLs; price and size filters inside the URL are kept). sortBy orders the results after all are read (newest, price or size, ascending or descending), so Max items then keeps the first N of the ordered list.
Incremental mode and the changes view
With incremental mode on, ignoreFieldsForChanges lists fields or dotted paths (for example agent.phone) that are left out of change detection and changedFields but stay in the output. The run's Output tab also offers a Changes (incremental) dataset view showing changeType, changedFields and the timestamps. The first incremental run after this release re-baselines existing listings silently, so the new fields do not make every tracked listing show up as UPDATED.
How search scope and Max items interact
One search reads one location scope: a subway radius, or a single geohash cell (a 4 character cell is roughly 39 by 20 km). A dense central area can resolve to thousands of pins in that one scope, so listing cards are then read in batches of up to 15 and the run stops as soon as Max items pushed records is reached. Raise Max items, or set it to 0 for no limit, to read the whole scope. To cover a whole city, run separate searches per district rather than one very wide scope.
Resume and recurring updates, two different mechanisms
- Resume (
resumeFromRunId) continues one interrupted run: paste its run or dataset id and the actor skips pushing every listing id already collected there, whether that run used search or url mode. It still reads through the same search scope again, so a repeated search is read in full, but only the not yet seen listings count against Max items and get billed. - Incremental mode (
incrementalMode) is for scheduling the same search again and again. Each listing is classifiedNEW,UPDATED(withchangedFields),UNCHANGED(suppressed and not billed unlessemitUnchangedis on),REAPPEAREDorEXPIRED. A run cannot use both mechanisms at once; setting both fails the run before any request is made. EXPIREDrows are only produced by a scan that read every part of its scope: not resumed, not stopped early by Max items, and it saw at least one listing. That same complete scan always updates the stored baseline to mark absent listings gone, whether or notemitExpiredis on; the flag only controls whether theEXPIREDrow itself is returned and billed that run.REAPPEAREDfollows from that stored state: a listing a later run sees again after an earlier complete scan marked it gone comes back asREAPPEARED, even ifemitExpiredwas off on that earlier run.stateKeynames or shares the stored baseline; leave it empty to key the baseline on the search itself. With incremental mode off, output looks exactly like a plain search or url run.
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.
- Authorize a connector once under Apify > Settings > Integrations (Notion, Linear, Airtable, or Apify).
- Paste the connector's id (or ids) into the mcpConnectors input field.
- For Notion, also set
notionParentPageUrlto the page where items should be created. - Max items sent to a connector (
maxNotifyListings) caps how many of this run's records the export sends, independently ofmaxItems.
The connection is mediated by Apify's MCP proxy, so this actor never sees your third party credentials. Export is best effort: a failing connector never affects the scraped data. Leave mcpConnectors empty to skip it.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search resolves a location keyword; url reads exactly the listings you paste. |
query | string | 강남역 | Location keyword: a subway station, neighbourhood or district name (search mode). |
serviceType | string | oneroom | Listing type: oneroom (원룸), villa (빌라) or officetel (오피스텔). |
subwayRadiusKm | integer | 2 | Radius in km read around a resolved subway station, from 1 to 10. |
geohash | string | (none) | Read this geohash cell directly instead of resolving the keyword (advanced). |
lat | number | (none) | Centre latitude for a coordinate search instead of a keyword; give with lng. |
lng | number | (none) | Centre longitude for the coordinate search. |
precision | integer | 6 | Geohash cell size for the coordinate search (4 to 8). |
ring | integer | 1 | Cells read around the centre cell (0 = centre only, 1 = 3x3, up to 3). |
searchUrls | array | (none) | Pasted apis.zigbang.com listing list URLs with a geohash; price and size filters inside the URL are kept. |
sortBy | string | none | Orders results after reading them all: newest, price (deposit plus 100 times monthly rent) or size. Maximum items then keeps the first N of the ordered list. |
urls | array | one example per URL shape (see above) | Listing URLs or bare numeric ids to read directly (url mode). |
salesTypes | array | (none) | Narrow by deal type: jeonse, wolse, maede or dongi. Leave empty for all types available on the chosen listing type. |
depositMin | integer | (none) | Lowest deposit to include, in 만원 (10,000 KRW) units. |
depositMax | integer | (none) | Highest deposit to include, in 만원 units. |
rentMin | integer | (none) | Lowest monthly rent to include, in 만원 units. |
rentMax | integer | (none) | Highest monthly rent to include, in 만원 units. |
manageCostMin | integer | (none) | Lowest building maintenance fee to include, in 만원 units. |
manageCostMax | integer | (none) | Highest building maintenance fee to include, in 만원 units. |
sizeMin | number | (none) | Smallest floor size to include, in square meters. |
sizeMax | number | (none) | Largest floor size to include, in square meters. |
fetchDetails | boolean | false | Fetch each listing's detail page and bill the detail enrichment surcharge for it. |
maxItems | integer | 20 | Stop after this many listing records. 0 means no limit. |
resumeFromRunId | string | (none) | Continue one interrupted run without paying again for records already collected there. |
incrementalMode | boolean | false | Classify listings NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED against a stored baseline of the same search. |
stateKey | string | (none) | Name the incremental baseline yourself; otherwise it is keyed on the search scope. |
emitUnchanged | boolean | false | With incremental mode on, also return (and bill) UNCHANGED rows. |
emitExpired | boolean | false | With incremental mode on, also return (and bill) EXPIRED rows for listings no longer in scope. |
ignoreFieldsForChanges | array | (none) | Incremental mode only: field names or dotted paths (for example agent.phone) left out of change detection and changedFields; they stay in the output. viewCount, isNew and badges are always left out of the comparison. |
mcpConnectors | array | (none) | Optional: send a summary of each record to apps you authorized under Integrations. |
notionParentPageUrl | string | (none) | Notion connector only: page under which records are created. |
maxNotifyListings | integer | 50 | Cap on items sent to a connector export per run. |
proxy | object | {"useApifyProxy": true} | Connection settings. |
Output Example
Sample shape: values are illustrative placeholders, not from a live record.
{"itemId": 12345678,"url": "https://www.zigbang.com/home/oneroom/items/12345678","serviceType": "원룸","title": "Newly painted one room with a private entrance","salesType": "월세","deposit": 1000,"rent": 55,"manageCost": "60000","sizeM2": 21.4,"supplyAreaM2": 25.2,"exclusiveAreaM2": 18.6,"roomType": "02","roomTypeTitle": "원룸","floor": "4층","buildingFloor": "6","registeredAt": "2026-08-20T09:15:00+09:00","isNew": true,"status": true,"tags": ["역세권", "반려동물가능"],"badges": [],"address": "서울시 샘플구 샘플동 12-3","city": "서울시","district": "샘플구","neighborhood": "샘플동","lat": 37.55,"lng": 127.0,"thumbnailUrl": "https://ic.zigbang.com/ic/items/12345678/1.jpg","description": "Recently renovated one room with a bright south facing window.","options": ["에어컨", "냉장고", "세탁기"],"elevator": true,"bathroomCount": 1,"roomDirection": "남향","parkingAvailableText": "가능","moveinDate": "즉시입주","viewCount": 312,"imageUrl": "https://ic.zigbang.com/ic/items/12345678/1.jpg","subways": [{ "name": "샘플역", "lines": "2호선" }],"agent": {"name": "김민수","officeName": "샘플공인중개사사무소","officePhone": "02-1234-5678","officeAddress": "서울시 샘플구 샘플로 10","officeRegNumber": "12345-2026-00001"},"changeType": "UPDATED","changedFields": ["rent"],"firstSeenAt": "2026-08-01T02:00:00.000Z","lastSeenAt": "2026-09-20T02:00:00.000Z"}
Plan Requirement
The default connection (Apify datacenter proxy) is enough for the routes this actor reads: the listing API and the mobile listing pages answer a datacenter connection normally, so a run on a paid plan needs no residential proxy and no special setting. The desktop host www.zigbang.com refuses datacenter connections, which is why the actor reads listings through the API and mobile hosts and only writes the desktop address into the url output field.
On an Apify free plan, which has no proxy access, the actor continues on a backup connection and the log says so:
- Warning: "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. For residential access, upgrade at https://apify.com/pricing or supply your own proxy URLs."
- Info once pages are read that way: "Served over the backup connection (the default connection was unavailable)." The backup connection is part of the run you pay for; there is no separate charge for it.
If nothing at all can be read, the run fails loudly instead of returning an empty dataset, 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: https://apify.com/pricing. 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 Zigbang pages. Upgrade at https://apify.com/pricing 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."
You can supply your own proxy URLs in the proxy field at any time.
URL shapes
URL mode accepts these shapes, mixed freely in one list:
| Shape | Example | Result |
|---|---|---|
| Mobile listing page | https://m.zigbang.com/home/oneroom/items/12345678 | That listing |
| Villa or officetel path | https://m.zigbang.com/home/villa/items/12345678 | That listing (the type segment is ignored) |
| Listing page with query string | https://m.zigbang.com/home/oneroom/items/12345678?foo=1 | That listing |
| Desktop listing page | https://www.zigbang.com/home/oneroom/items/12345678 | That listing, read through the mobile and API hosts (not confirmed live from a datacenter connection) |
| Listing API form | https://apis.zigbang.com/v3/items/12345678 | That listing |
| Bare numeric id | 12345678 | That listing |
| Keyword search API URL | https://apis.zigbang.com/v3/search?q=... | Read like search mode for that keyword |
If the urls field is left untouched in url mode, it is not empty: it holds one example of each shape above, so the run scrapes those example listings and keyword. Clear the field and enter your own URLs or ids to read only those. A pasted URL that matches no shape is skipped with a warning naming it; if none match, the run fails with a clear message.
FAQ
How much does it cost?
Billing has three events, all shown with their current rates on the Pricing tab:
- Actor start (
apify-actor-start): charged when the run starts, one event per GB of run memory (minimum one; the default 512 MB run memory is one event). - Listing record (
apify-default-dataset-item): once per row actually pushed to the dataset. - Detail enrichment (
detail-enrichment): once per row that was enriched with Fetch listing details and pushed. A listing whose detail page could not be read, or a row that is suppressed (for example an UNCHANGED row in incremental mode), is not charged.
Use Max items to cap the cost of any run.
Is it legal to scrape Zigbang?
This actor reads only the public listing data Zigbang's own web app serves to every visitor, including agent contact details the source itself publishes on each listing page. Zigbang's robots.txt disallows general crawling outside a small search engine allowlist; this actor is not a search engine crawler, so you are responsible for how you use the data: follow Zigbang's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution.
Can I get only new or changed listings on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Unchanged listings are suppressed and not billed unless you also turn on Emit unchanged listings.
Can I use Resume and Incremental mode together?
No. They solve two different problems: Resume continues one specific interrupted run, Incremental mode tracks a recurring search over time. Setting both fails the run immediately with a message asking you to use one, not both.
Why did my run fail instead of returning an empty dataset?
If every request this run made was refused, the run stops with a clear connection problem message instead of returning an empty dataset, so "no listings matched" is never confused with "the connection failed." A search that genuinely matches nothing, or a pasted id the source does not recognize, still succeeds with an empty result.
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 results into Notion, Linear or Airtable.
🔗 More scrapers you might like
Pair this actor with these related scrapers from the same team:
| 🧩 Suumo.jp Scraper Scrape Suumo, Japan's largest property portal: rental rooms with rent, management fees... | 🧩 Naver Land Scraper Scrape structured property listings from Naver Land map URLs. Extract listing titles... |
| 🏠 QuintoAndar Scraper Scrape QuintoAndar property listings across Brazil with 80+ fields, including rent, sale... | 🏠 Realestate.co.nz Scraper Scrape Realestate.co.nz property listings across New Zealand. Search properties for sale... |
| 🏠 Rent.com Scraper Extract rental listings from rent.com. Get comprehensive data including monthly rent... | 🏠 Apartments.com Scraper Scrape Apartments.com rentals by city, ZIP, neighborhood, address or URL. Extract rent... |
💬 Support
- 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. 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.