Here is a short Python client for the Suumo.jp Japan Real Estate Scraper on Apify. You open suumo.jp, narrow a search to the area and budget you care about, copy the address bar, and this script returns that result set as rows: listing ID, building name, monthly rent or asking price, kanrihi, shikikin, reikin, madori, square metres, floor, building age, address and every nearby station with walking minutes. Money is already integer yen, so the numbers go straight into pandas arithmetic. The crawling runs on Apify; locally you get results.json and results.csv.
Actor: Suumo.jp Japan Real Estate Scraper Β· Publisher: Devil Scrapes Β· Also in JavaScript: suumo-scraper-javascript
- Your search, not ours: the input is a results URL copied from suumo.jp, so every filter Suumo offers (ward, line, rent band, layout, age) is already applied before the Actor reads a page.
- One row per room, not per building: Suumo's rental cards group several vacant units under one building; each unit becomes its own row and inherits the building's name, address, type, age and stations.
- Yen as integers: display strings such as
6.5δΈεarrive as65000. A deposit or key money shown asγͺγbecomes0, while a dash staysnull, so "none charged" and "not stated" remain different things. - Every station on the card:
stationsis a list of objects withline,stationandwalk_minutes. Rental cards list up to three, used-condo cards list one. - Rentals and used condos with one schema: a
/jj/chintai/ichiran/URL is read as rent mode, a/jj/bukken/ichiran/URL as buy mode, andlisting_modeon each row says which. - Agency details when you ask for them: with
fetchListingDetailson, the Actor opens each listing page for the handling agency's name (plus phone on rentals, and management fee and floor on condos), billed as a separate event only when a name is found. - Caps that hold across URLs:
maxItemscounts rows over all your search URLs together, andmaxPageslimits how far each one paginates. Pages that answer with temporary errors are retried with growing pauses.
Rent alone hides the upfront bill. Add deposit_yen, key_money_yen, the first month's price_yen and management_fee_yen per row and you get a move-in figure you can sort on. The recipe below does this and ranks stations by median rent per square metre.
Save one search URL per ward of interest, run them weekly from cron and append each CSV to a Parquet file. Because rows carry area_sqm and building_age_years, a regression of rent on size, age and walk time is a notebook away.
Paste a /jj/bukken/ichiran/ search and every row comes back in buy mode with the sale price in yen, layout, floor area and construction-derived age. Divide price by area for a yen-per-mΒ² column and compare districts before anyone opens a listing page.
Names and addresses stay in Japanese, which is what you want for matching against maps or translation APIs. Filter in pandas by walk time and total upfront cost, translate only the shortlist, and send it as a spreadsheet.
You need an Apify account and an API token from Console β Settings β Integrations. Apify's free plan includes monthly usage credit; this Actor's event prices are listed under Pricing below.
git clone https://github.com/devilscrapes/suumo-scraper-python.git
cd suumo-scraper-python
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
export APIFY_TOKEN=your_token_here
python main.py # uses input.json
python main.py my-input.json --out data --max-charge 2The script starts the Suumo.jp Japan Real Estate Scraper, waits for it to finish, prints a link to the run in Apify Console and saves every result to results.json and results.csv. It exits with code 1 and the run's status message if the run fails.
If the wrapper script is more than you need, these five lines are the whole job: hand the Actor a Suumo URL, let call() wait for the run, then loop over the dataset.
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("DevilScrapes/suumo-japan-real-estate").call(run_input={'searchUrls': ['https://suumo.jp/jj/chintai/ichiran/FR301FC001/?ar=030&bs=040&ta=13&sc=13104&page=1'], 'maxItems': 10, 'maxPages': 1, 'proxyConfiguration': {'useApifyProxy': True}})
for item in client.dataset(run.default_dataset_id).iterate_items():
print(item)main.py forwards input.json unchanged. The shipped file is a rental search in Shinjuku ward, Tokyo, held to one results page and ten rows, which is enough to inspect the shape of the data for a fraction of a cent. Swap in your own Suumo URL, raise maxItems and set maxPages to 0 when you want the whole result set.
{
"searchUrls": [
"https://suumo.jp/jj/chintai/ichiran/FR301FC001/?ar=030&bs=040&ta=13&sc=13104&page=1"
],
"maxItems": 10,
"maxPages": 1,
"proxyConfiguration": {
"useApifyProxy": true
}
}| Field | Type | Required | Default | Allowed values |
|---|---|---|---|---|
searchUrls |
array | yes | ["https://suumo.jp/jj/chintai/ichiran/FR301FC001/?ar=030&bs=040&ta=13&sc=13104&page=1"] |
β |
maxItems |
integer | no | 100 |
β |
maxPages |
integer | no | 0 |
β |
fetchListingDetails |
boolean | no | false |
β |
proxyConfiguration |
object | no | {"useApifyProxy": true} |
β |
Each row is one rentable unit or one condo for sale. The money fields are price_yen (monthly rent in rent mode, total asking price in buy mode), management_fee_yen, deposit_yen and key_money_yen; the last two are always null for condos. Size and position come as layout (for example 1LDK), area_sqm, floor and building_age_years, where a brand-new building counts as 0. Location is address plus a stations list of {line, station, walk_minutes} objects. listing_id, url, name, property_type, listing_mode, agency_name, agency_phone and scraped_at complete the row. In the CSV export, Apify flattens stations into numbered columns, so prefer results.json for analysis.
| Field | Type | Always present |
|---|---|---|
listing_id |
string | no |
name |
string or null | no |
url |
string | no |
property_type |
string or null | no |
price_yen |
integer or null | no |
management_fee_yen |
integer or null | no |
deposit_yen |
integer or null | no |
key_money_yen |
integer or null | no |
address |
string or null | no |
stations |
array | no |
listing_mode |
string | no |
agency_name |
string or null | no |
agency_phone |
string or null | no |
layout |
string or null | no |
area_sqm |
number or null | no |
floor |
string or null | no |
building_age_years |
integer or null | no |
scraped_at |
string or null | no |
| Event | Price (USD) |
|---|---|
| Actor start | $0.00001 |
| Result emitted | $0.002 |
| Detail page enriched | $0.005 |
Prices as configured on 2026-08-12. The Actor page always shows the current price. Runs are billed to the Apify account that owns the token. Use --max-charge to cap what a single run can cost.
This pandas script (pip install pandas) reads results.json, picks the closest station for each unit, computes rent per mΒ² and the upfront cost of signing, then prints the stations with the lowest median rent per mΒ².
import json
import pandas as pd
units = pd.DataFrame(json.load(open("results.json", encoding="utf-8")))
units = units[units["listing_mode"] == "rent"].copy()
def closest(stations):
timed = [s for s in stations or [] if s.get("walk_minutes") is not None]
return min(timed, key=lambda s: s["walk_minutes"]) if timed else {}
near = units["stations"].map(closest)
units["station"] = near.map(lambda s: s.get("station"))
units["walk_min"] = near.map(lambda s: s.get("walk_minutes"))
units["yen_per_sqm"] = (units["price_yen"] / units["area_sqm"]).round()
fees = ["price_yen", "management_fee_yen", "deposit_yen", "key_money_yen"]
units["move_in_yen"] = units[fees].fillna(0).sum(axis=1).astype(int)
by_station = (
units.dropna(subset=["station", "yen_per_sqm"])
.groupby("station")
.agg(units=("listing_id", "count"), median_yen_per_sqm=("yen_per_sqm", "median"),
median_move_in=("move_in_yen", "median"), median_walk=("walk_min", "median"))
.sort_values("median_yen_per_sqm")
)
units.to_csv("units_with_costs.csv", index=False)
print(by_station.to_string())It handles rentals and used condominiums. A search URL containing /jj/bukken/ichiran/ is parsed in buy mode, where property_type is always δΈε€γγ³γ·γ§γ³. Other Suumo sections, such as new-build projects or detached houses for sale, are not parsed as their own mode; anything that is not a bukken URL is treated as a rental search.
The search pages do not show it. Set fetchListingDetails to true and the Actor visits each listing's own page to read the agency. That roughly doubles the requests and adds a per-row event, so turn it on only for the listings you will act on.
It is in whole years. For rentals it comes from the age Suumo prints on the card, with new construction as 0. For condos it is the current year minus the construction year, so it can be one year off depending on the month.
Open the URL in a browser first: if Suumo shows no results there, the Actor has nothing to read. If it does, look at the run log for HTTP errors and try a Japanese exit by adding "apifyProxyCountry": "JP" to proxyConfiguration. This Actor reports an empty result in its status message but still finishes as succeeded, so check the row count in scheduled jobs.
Set APIFY_TOKEN first: export the token in the same shell, or put it in a local.envfile your shell loads. Never commit it.- Run finished with
FAILEDorABORTED: open the printed Console link; the run log says why. Input validation errors name the field. - Fewer rows than expected: a
--max-chargecap also limits how many results can be charged. Raise the cap or lower the limits in your input. - Something looks wrong in the data: open an issue on the Actor page so the maintainers see it.
- Suumo.jp Japan Real Estate Scraper on Apify Store
- JavaScript version of this example
- All Devil Scrapes Actors
- Apify web scraping cookbook
Related Actors:
- Rightmove Scraper β UK Property Listings β UK sale and rental listings from Rightmove.
- Immowelt Germany Real Estate Scraper β German apartments and houses listed on Immowelt.
- Otodom Poland Real Estate Scraper β Polish property offers from Otodom.
MIT licensed example code. The Actor itself runs on Apify and is maintained by Devil Scrapes. Not affiliated with SUUMO, Suumo.jp or Recruit Co., Ltd..