Yanolja Scraper: Stays, Rates & Reviews
Pricing
from $1.50 / 1,000 listing records
Yanolja Scraper: Stays, Rates & Reviews
Scrape Yanolja (NOL), Korea's largest accommodations marketplace, by region or URL. Every row carries name, address, coordinates, phone, star class, room types with per-night rates, review score and review text, amenities, policies and photos from one stay page read.
Pricing
from $1.50 / 1,000 listing records
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
1
Bookmarked
1
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Yanolja Scraper turns Yanolja (NOL), Korea's largest accommodations marketplace, into structured stay data. Get full records for motels, hotels, resorts, pensions and poolvillas: name, address, coordinates, the stay's own publicly displayed booking phone, room types with per-night rates, review score and review text, facilities, policies and photos. Browse by Korean region and stay group, or paste stay and collection links directly, then export to JSON, CSV or Excel, or pull results straight into your app through the API.
Why This Scraper?
- Two ways to scope a run. Browse the site's own ranked lists by Korean region and stay group, or paste stay and collection links directly; both return the same full record shape.
- Full stay detail, not just a listing card. Each enriched record carries room types with per-night rates, review score and highlighted review text, address and coordinates, the stay's own booking phone, facilities, policies and photos, all from one stay page read.
- Availability rates, not stale prices. Overnight and day-use rates come from the site's own next-night availability window, including sold-out flags, so a returned price is what the site is quoting right now.
- Reorder the rows you collected. Sort one run's output by price or review score with a single input field, instead of post-processing it yourself.
- Resume a big crawl without repaying for it. Paste a previous run's id and this run skips every record it already collected there.
- Built for schedules. Incremental mode returns only new, updated and reappeared records on recurring runs, and a refused run fails loudly instead of returning an empty dataset.
- Pipe results into the apps you use. Optional MCP export sends a condensed summary of each record to Notion, Linear or Airtable, while the full record always stays in the dataset.
Use Cases
- Travel meta-search and price comparison: track live overnight and day-use rates plus sold-out flags across motels, hotels and pensions in the regions you serve.
- Revenue management and market research: benchmark rate positioning, review scores and amenities against comparable stays in the same region and stay group.
- Travel content and itinerary tools: pull address, coordinates, photos and policy text to build region guides or booking adjacent content.
- Price drop and availability monitoring: schedule the same region with incremental mode and get only the stays whose rate or availability actually changed.
- Review and reputation tracking: collect rating value, review count and the site's own review highlights to watch sentiment over time.
Data You Get
Sample shape: values are illustrative placeholders, not from a real stay.
| Field | Example |
|---|---|
recordId / stayId | "100000001" |
rowType | "stay" |
url | "https://nol.yanolja.com/stay/domestic/100000001" |
name / subtitle | "Sample Stay at Sample District" / "Sample subtitle text" |
stayGroup | "Motel" (also "Hotel", "Resort", depending on the stay) |
hotelStar | "3" |
badges | ["Sample badge one"] |
address | "Sample-si, Sample-gu, Sample-daero 12" |
latitude / longitude | 37.11 / 127.02 |
cityName / cityDetail | "Sample-si" / "Sample-gu" |
sellerPhone | "0501234xxxx" |
ratingValue / reviewCount | 4.2 / 456 |
priceRange | "Room 20,000 KRW and up" |
lowestPriceKrw | 36000 |
overnightPriceKrw / dayUsePriceKrw | 36000 / null |
overnightSoldOut / dayUseSoldOut | false / true |
checkInTime / checkOutTime | "16:00" / "12:00" |
facilities | ["Free parking", "Sample amenity"] |
reviewHighlights | one row per highlighted review: reviewer initials, score, date, text |
gptReviewSummary | "Clean rooms, helpful staff" |
cancelPolicy | "Cancel 1 day prior: full refund" |
mainImageUrl / photoUrls | the stay's main photo and full property gallery |
rooms | one nested entry per room type: id, name, rate pair, benefits, up to three photos |
Every row also carries replyCount, ratingFromFd/ratingCountFromFd (a second, structured-data copy of the review score), descriptionText, popularityMessage, policyInformation, petsAllowed, availableLanguage and roomPhotoUrls. A bare listing stub (search mode with Fetch full stay details off) carries only recordId, stayId, rowType, url, name and listingImageUrl. With incremental mode on, rows also carry scrapedAt, changeType, changedFields, firstSeenAt and lastSeenAt.
How to Use
- Pick a mode:
search(browse by Korean region and stay group) orurl(paste stay or collection links). - For search mode, list one or more region names and optionally narrow to one stay group. For url mode, paste the stay or collection URLs.
- Set the ordering and Max items to shape the run, and switch Fetch full stay details on or off.
- Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.
Search a region for motels:
{"mode": "search","locations": ["서울"],"categoryGroup": "motel","fetchDetails": true,"maxItems": 20}
Search two regions, cheapest first:
{"mode": "search","locations": ["제주", "부산"],"sortResultsBy": "price_asc","maxItems": 50}
Paste a stay URL:
{"mode": "url","urls": ["https://nol.yanolja.com/stay/domestic/123456"]}
Paste a collection URL (a region's own ranked top list):
{"mode": "url","urls": ["https://nol.yanolja.com/programmatic/domestic-accommodation/서울-근처-모텔-숙소-추천-베스트-10"]}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/yanolja-com-scraper").call(run_input={"mode": "search", "locations": ["서울"], "maxItems": 20})for stay in client.dataset(run["defaultDatasetId"]).iterate_items():print(stay["name"], stay["overnightPriceKrw"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/yanolja-com-scraper').call({ mode: 'search', locations: ['서울'], 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.
How a search run is scoped and capped
Each region word is matched against the site's own ranked collection pages (one word can match several, for example one per district and stay group), and every matched collection is a single ranked top list of about ten stays, read once. So the practical size of a search run comes from how many regions and stay groups you list, not from paging through one long list. Max items is one run wide cap, split evenly across every region, stay group and pasted URL a run reads.
Resume, incremental mode, and a caveat on tight caps
- Resume (
resumeFromRunId) continues one interrupted run: paste its run or dataset id and this run skips every record already collected there, so you don't pay twice. - Incremental mode (
incrementalMode) is for scheduled runs over the same region, stay group and filters. Each record is classifiedNEW,UPDATED(withchangedFields, a rate move counts as a change),UNCHANGED(suppressed and not billed unlessemitUnchangedis on),REAPPEAREDorEXPIRED(only after a run that scanned the whole tracked scope, and only withemitExpired).stateKeynames or shares the stored state. With incremental mode off, output is exactly as before.
Keep Max items comfortably above what a run actually needs. Max items splits evenly across the regions, stay groups and URLs a run reads, and each collection is read once, in the site's own ranked order. When that split cuts a collection's list short: the trimmed off stays are still marked as read internally, so a DIFFERENT region or stay group that also lists one of those same stays skips it too, even though it was never actually collected; and, in incremental mode, a stay ranked below the cut is never compared against its stored state at all, so a genuine price or availability change on it goes undetected, run after run, for as long as the cap stays tight. Example: two collections both list the same popular stay, one near the top of its list and one near the bottom; if Max items caps the first collection before it reaches that stay, the second collection also skips it, and it is missing from that run's output entirely. Workaround: set Max items to 0 (unlimited) or to at least ten times the number of collections and URLs the run reads (the run log's Source 1/N line shows N; one region word can match several collections), so no collection's top ten is ever cut short. This matters most for incremental mode, where a trimmed stay's changes are not merely delayed, they stop being checked at all.
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).
- Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto 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 | search (browse by region and stay group) or url (paste stay or collection links). |
locations | array | ["서울"] | One or more Korean region or place names, matched against the site's own published collection pages. Search mode only. |
categoryGroup | string | all | Narrow search mode to hotel, motel, or pension. Leave as all for every group the region publishes. Search mode only. |
urls | array | sample stay URL | Stay or collection links to read directly. URL mode only. |
minPriceKrw | integer | (none) | Intended to keep only rows whose lowest rate is at least this many Korean won. Currently has no effect: the bound is checked before the stay page is read, when a row carries no rate yet, so every row passes. |
maxPriceKrw | integer | (none) | Intended to keep only rows whose lowest rate is at most this many Korean won. Currently has no effect, for the same reason as minPriceKrw. |
sortResultsBy | string | site_order | Arrange the rows this run collected, after they are read: site_order, price_asc, price_desc, or rating_desc. |
fetchDetails | boolean | true | Read each stay's own page for the full record (rooms, rates, reviews, policies, photos). Off returns the bare listing stub only (name, image, URL). |
maxItems | integer | 20 | Stop after collecting this many records across every region, group and URL. 0 for unlimited. |
maxPages | integer | 0 | Currently unused: accepted in the input, but each collection is one single ranked list read once, so this has no effect on any run. |
resumeFromRunId | string | (none) | Continue one previous interrupted run: paste its run or dataset id to skip records already collected there. |
incrementalMode | boolean | false | Turn on for recurring runs of the same region, group and filters, returning only new, updated and reappeared records since the last run. |
stateKey | string | (none) | Optional: name a monitoring campaign so its incremental state stays stable and separate from other scopes. |
emitUnchanged | boolean | false | Also return (and bill) records that have not changed since the last run. |
emitExpired | boolean | false | Also return (and bill) records no longer found, after a run that fully scanned the tracked scope. |
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 record pages are created. |
maxNotifyListings | integer | 50 | Cap on records written to each connector per run. Does not affect the dataset. |
proxy | object | Korean residential | Connection settings. The default pins a Korean resident type connection; the site's stay pages refused every attempt from cheaper shared pools during testing. |
Output Example
Sample shape: values are illustrative placeholders, not from a real stay.
{"recordId": "100000001","stayId": "100000001","rowType": "stay","url": "https://nol.yanolja.com/stay/domestic/100000001","name": "Sample Stay at Sample District","subtitle": "Sample subtitle text","stayGroup": "Motel","hotelStar": "3","badges": ["Sample badge one"],"address": "Sample-si, Sample-gu, Sample-daero 12","latitude": 37.11,"longitude": 127.02,"cityName": "Sample-si","sellerPhone": "0501234xxxx","ratingValue": 4.2,"reviewCount": 456,"priceRange": "Room 20,000 KRW and up","lowestPriceKrw": 36000,"overnightPriceKrw": 36000,"dayUsePriceKrw": null,"overnightSoldOut": false,"dayUseSoldOut": true,"checkInTime": "16:00","checkOutTime": "12:00","facilities": ["Free parking", "Sample amenity"],"reviewHighlights": [{ "reviewer": "Sample user*", "score": 5, "date": "2025.01.02", "content": "Clean rooms, quiet at night, would stay again." }],"gptReviewSummary": "Clean rooms, helpful staff","cancelPolicy": "Cancel 1 day prior: full refund","mainImageUrl": "https://example.com/sample-main.jpg","photoUrls": ["https://example.com/sample-1.jpg", "https://example.com/sample-2.jpg"],"rooms": [{"roomTypeId": 250000,"roomTypeName": "Sample room double","subTitle": "Sample walk-in type","overnightPriceKrw": 36000,"dayUsePriceKrw": null,"benefits": ["Streaming service included"]}],"scrapedAt": "2026-09-29T00:00:00Z"}
Plan Requirement
The default connection pins a Korean resident type proxy, because this storefront's stay pages were found to answer automated reads only from that class of connection during testing; shared, non resident pools were tried live and refused on every stay page attempt. Leave the default proxy setting in place for reads to succeed. Any proxy configuration you paste yourself is used exactly as given.
FAQ
How much does it cost?
You pay per record returned, plus the Stay detail enrichment charge only for a record whose stay page read actually succeeded this run. The Pricing tab shows the current rates. Use Max items to cap the cost of any run.
Is it legal to scrape Yanolja?
This actor collects only publicly available accommodation data. You are responsible for how you use it: follow Yanolja's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listing details are generally facts, but photos and review text may be subject to third party rights.
Can I get only new or changed stays on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new, updated and reappeared records, and unchanged ones are not billed unless you also turn on Emit unchanged.
Why did a stay disappear from my results, or never show a price change?
Max items splits evenly across every region, stay group and URL a run reads, and each collection is a single ranked list read once. A stay ranked below that split is skipped this run, and if a different collection also lists that same stay further up its own list, that collection skips it too, so it can be missing entirely even though it is a real, live stay. In incremental mode this also means the stay is never compared against its stored state, so a real rate or availability change on it goes undetected for as long as the cap stays tight. Set Max items to 0, or to at least ten times the number of sources shown in the run log's Source 1/N line, to avoid this.
Do the price filters narrow my results?
Not today. minPriceKrw and maxPriceKrw are accepted, but they are checked against the listing row before its stay page is read, and a listing row carries no rate yet, so every row passes. To work with a price range, collect the rows and filter on lowestPriceKrw after the run, or use Order the returned rows by to put the cheapest first.
Why did my run fail instead of returning an empty dataset?
If every collection or stay page this run tried was refused, the run stops with a clear message so "no stays matched" is never confused with "nothing could be read". Run it again in a few minutes, or check your proxy configuration. If some sources served pages while others were refused, you still get what was found.
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 results into Notion, Linear or Airtable.
🔗 Want more travel data?
Pair this actor with these related scrapers from the same team:
| 🏨 Booking.com Hotels, Prices and Reviews Scraper Scrape booking.com hotels, apartments, villas and hostels. Search any destination with... | 🏨 Rakuten Travel Scraper Scrape Rakuten Travel, Japan's largest domestic hotel-booking site, by prefecture... |
| 🏨 Wego Scraper Scrape Wego hotels by city or search URL. Get rates, providers, booking links... | 🏨 Hipcamp Scraper Scrape Hipcamp campgrounds, RV parks, glamping and unique stays by US or AU region or... |
| 🏨 Airbnb Scraper Scrape Airbnb stays by destination or link: nightly and total price, rating breakdown... | 🏨 Rover Pet Care Scraper Scrape Rover.com pet-care providers: sitter search by city, rates per stay type, ratings... |
💬 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.