Automated pipeline to collect, store, and analyze rental, resale, and new development listing data from 591.com.tw.
591_pipeline/
├── config.yaml # All tunable parameters
├── requirements.txt
├── README.md
├── data/
│ ├── raw/ # Date-stamped snapshots (rentals_2026-05-19.csv)
│ ├── processed/ # Weekly summary CSVs
│ ├── output/ # Final CSVs + market summary markdown
│ └── charts/ # Generated PNG charts
├── logs/ # Rotating log files
└── src/
├── utils.py # Shared: HTTP client, NUXT parser, normalization
├── scrape_rentals.py # 租屋 rental listings
├── scrape_new_developments.py # 新建案 (預售屋 + 新成屋)
├── scrape_resales.py # 中古屋 resale listings
└── process_weekly.py # Weekly aggregation, charts, summary
cd 591_pipeline
pip install -r requirements.txtNo additional dependencies required — uses requests, pandas, matplotlib, pyyaml.
Edit config.yaml to adjust:
active_cities: which cities to scrape (default:[1, 3, 8, 15, 17]= 台北/新北/台中/台南/高雄)http.delay_min/delay_max: request delay range (default 2–4 seconds)rental.max_pages: safety cap per city (default 60 = ~1800 listings/city)- Add more city IDs from
target_citiesto expand coverage
Each scraper runs independently and produces both a raw snapshot and a final CSV:
# Scrape rental listings
python src/scrape_rentals.py
# Scrape resale (中古屋) listings
python src/scrape_resales.py
# Scrape new development (新建案) listings
python src/scrape_new_developments.pyWith 5 cities and 2–4s delay per request:
- Rentals: ~30–60 min (largest dataset, ~50 pages/city)
- Resales: ~30–60 min
- New developments: ~10–20 min (fewer pages)
| Scraper | Raw snapshot | Final output |
|---|---|---|
| Rentals | data/raw/rentals_YYYY-MM-DD.csv |
data/output/591_rentals.csv |
| Resales | data/raw/resales_YYYY-MM-DD.csv |
data/output/591_resales.csv |
| New Dev | data/raw/new_developments_YYYY-MM-DD.csv |
data/output/591_new_developments.csv |
After running the scrapers, generate analysis:
python src/process_weekly.pyThis produces:
data/processed/weekly_rental_summary.csvdata/processed/weekly_resale_summary.csvdata/processed/weekly_new_development_summary.csvdata/charts/*.png— bar charts by city, trend charts (after 2+ weeks)data/output/weekly_market_summary.md— analytical summary
cd /Users/jimmylolo/591_pipeline
python src/scrape_rentals.py
python src/scrape_new_developments.py
python src/scrape_resales.py
python src/process_weekly.pyCreate ~/Library/LaunchAgents/com.jimmylolo.591-pipeline.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.jimmylolo.591-pipeline</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>cd /Users/jimmylolo/591_pipeline && python3 src/scrape_rentals.py && python3 src/scrape_new_developments.py && python3 src/scrape_resales.py && python3 src/process_weekly.py</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Weekday</key>
<integer>1</integer>
<key>Hour</key>
<integer>6</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/Users/jimmylolo/591_pipeline/logs/launchd_stdout.log</string>
<key>StandardErrorPath</key>
<string>/Users/jimmylolo/591_pipeline/logs/launchd_stderr.log</string>
</dict>
</plist>Load it:
launchctl load ~/Library/LaunchAgents/com.jimmylolo.591-pipeline.plistcrontab -e
# Add:
0 6 * * 1 cd /Users/jimmylolo/591_pipeline && python3 src/scrape_rentals.py && python3 src/scrape_new_developments.py && python3 src/scrape_resales.py && python3 src/process_weekly.py >> logs/cron.log 2>&1| Method | Pros | Cons |
|---|---|---|
| macOS launchd | Runs on missed schedule, native, no tokens | macOS only |
| cron | Universal, simple | Doesn't run missed jobs |
| GitHub Actions | Cloud-hosted, cross-machine | Needs repo push, IP may get blocked |
| Claude scheduled task | Easy setup | Costs tokens, may timeout |
Recommendation: Use launchd for local weekly runs. It costs zero tokens and handles laptop-sleep recovery.
| Section | Endpoint | Auth |
|---|---|---|
| 租屋 Rent | GET bff-house.591.com.tw/v3/web/rent/list |
None |
| 中古屋 Sale | GET bff-house.591.com.tw/v1/web/sale/list |
T591_TOKEN cookie |
| 新建案 New | GET api.591.com.tw/home/housing/list-search |
None |
listing_id, title, city, district, address, rent, area_ping, property_type, floor, rooms, posted_date, scrape_date, days_on_market, allows_cooking, allows_pet, has_elevator, near_metro, listing_url
listing_id, title, city, district, address, total_price_wan, total_price, unit_price_wan_per_ping, area_ping, main_area_ping, building_age, building_shape, rooms, floor, community_name, posted_date, scrape_date, days_on_market, listing_url
listing_id, project_name, city, district, developer, new_building_type, construction_status, unit_price_min_wan, unit_price_max_wan, total_price_min_wan, total_price_max_wan, area_min_ping, area_max_ping, room_config, listing_url
- Rate limiting: 591 may throttle or block after heavy use. The pipeline uses 2–4s randomized delays, but reduce
active_citiesor increase delays if issues arise. - Sale API token: The resale endpoint requires a
T591_TOKENcookie fromsale.591.com.tw. The pipeline auto-fetches this, but the token may expire during long runs. - Price display: Some new development prices show "價格待定" (price TBD) — these are stored as null.
- Posted date: Rental
posttimeis a unix timestamp. Resaleposttimeis also a timestamp. Some relative times like "3天前" are parsed as approximate dates. - Anti-bot: 591 uses Cloudflare. If
requestsgets blocked, the code can be adapted to usecloudscraperor subprocesscurl.
- Copy the entire
591_pipeline/folder - Run
pip install -r requirements.txt - Existing
data/raw/snapshots carry over —process_weekly.pyreads all historical snapshots for trend analysis - Adjust
config.yamlpaths if needed