Automation

How I Built a Hotel Points Value Scraper for Hyatt Properties

Michael Pichardo April 20, 2026 Updated May 26, 2026

I wanted to know whether it was worth burning Hyatt points on specific properties in Brazil instead of paying cash. The answer required live rates and a CPP calculation. I built a Python scraper using Chrome cookie injection to pull both cash and award rates for a list of properties and compute the math automatically.

The Question I Was Trying to Answer

I have Hyatt points. I'm considering hotels in Brazil. Is it worth using the points, or should I just pay cash?

That question is easy to answer for one hotel on one date — open the Hyatt site, check both rates, divide. But if you want to compare multiple properties across a date range, or check periodically as rates change, the manual version gets tedious fast.

CPP — cents per point — is the standard unit. You divide the cash rate by the number of points required and multiply by 100. If you'd pay $230 for a room that costs 9,000 points, that's 2.56 cpp. The threshold I use: below 2.0 cpp, pay cash. Above 2.0, consider using points.

I built a scraper that pulls live cash and award rates for a list of properties and prints a CPP table.

The Hyatt site has reCAPTCHA on the login page. Automating through it is slow and unreliable.

There's a better way: I already have a valid Hyatt session in my Chrome browser. browser_cookie3 can read that session directly from Chrome's cookie database and inject it into a Selenium-controlled browser. The automation browser inherits the authenticated session — no login page, no CAPTCHA.

The one friction: Chrome on macOS stores cookie values encrypted using AES-128-CBC, with the decryption key stored in macOS Keychain under "Chrome Safe Storage." When browser_cookie3 reads your Hyatt cookies, it triggers a system Keychain access prompt. This is expected behavior — it's Chrome's own security design, not a bug in the script.

import browser_cookie3
cookies = browser_cookie3.chrome(domain_name='.hyatt.com')

After extracting the cookies, the script injects them into a Selenium Chrome session before navigating to any Hyatt page. The browser lands directly on the authenticated rates page.

The Rates Page Structure

For each property, the URL pattern is:

https://www.hyatt.com/shop/rooms/{SHOP_CODE}?checkinDate=YYYY-MM-DD&checkoutDate=YYYY-MM-DD&adults=1&rooms=1

The page loads with cash rates visible. Award rates appear after clicking the "Use Points" toggle (input[data-js='usePoints']).

Key selectors:

ElementSelector
Cash ratediv[data-locator='cash-rate'] span.rate-values
Public ratediv[data-locator='public-rate'] span.rate-values
Points ratediv[data-locator='points-rate']
Use Points toggleinput[data-js='usePoints']
Award categorydiv[data-locator='award-category-label']

The script clicks the toggle, waits for the points rate to appear, scrapes both values, and calculates CPP. Each property takes about 20–25 seconds due to page load times.

The Properties I Was Checking

Three properties in Brazil, all for August 10–12, 2026:

PropertyAward tierPts/night
A Category 3 property in Rio de JaneiroCategory 39,000 — standard rooms; 14,000 — club floor
A Category 5 property in São PauloCategory 5~17,000 (est.)
A WoH affiliate property in LeblonMr & Mrs Smith / WoH affiliateVariable — not standard Hyatt award chart

The Rio property returned real data on the first confirmed working run. São Paulo and the Leblon affiliate were not successfully scraped — a threading bug in the multi-worker path caused cookie injection to fail silently (details in the Open Bug section below). The Rio numbers are what drove the decision.

Results: Category 3 Rio Property, August 2026

The scraper confirmed 8 room types across two award tiers. Exchange rate used: USD/BRL ~5.35.

Room typeCash/night (BRL)Cash/night (USD)Pts/nightCPP
2 Twin BedsR$1,231$2309,0002.56
1 King BedR$1,231$2309,0002.56
2 Twin Beds — Ocean ViewR$1,323$2479,0002.75
1 King Bed OceanfrontR$1,469$2759,0003.05
2 Twin Beds OceanfrontR$1,469$2759,0003.05
1 King Bed With Club AccessR$1,966$36714,0002.62

Every room type came in above the 2.0 cpp threshold. The oceanfront rooms at 3.05 cpp crossed the "strong redemption" line I use as a secondary target.

The most interesting data point: all standard and ocean-view rooms share the same 9,000-point cost regardless of view. The only variable is the cash rate. An oceanfront room at R$1,469 is 19% more expensive in cash but costs the same number of points — that's where using points pulls ahead the most.

The Club floor rooms (14,000 pts) come in at 2.62 cpp. That's above threshold, but not enough of a premium over the 9,000-point rooms to justify the extra points spend. The standard 1 King Bed at 2.56 cpp is the straightforward value play; the oceanfront at 3.05 cpp is the better one if availability holds.

Running It

cd ~/projects/hyatt-cpp
source venv/bin/activate

# Check August 2026 dates
python hyatt_cpp.py --checkin 2026-08-10 --checkout 2026-08-12

# Test with a single property first
python hyatt_cpp.py --checkin 2026-08-10 --checkout 2026-08-12 --workers 1 --limit 1

The --workers flag controls parallelism. Multiple workers speed things up but each one opens a separate Chrome window with its own injected session. If Chrome has the Cookies SQLite database locked (because Chrome itself is open), cookie injection fails silently and you get empty rate cards. Close Chrome before running the script.

The Open Bug

There's a known issue with the multi-threaded version: cookie injection works perfectly in a standalone inline script but fails silently when called through hyatt_login.make_driver() in the threaded context. Single-worker mode (--workers 1) works reliably. The multi-worker threading is still being debugged.

Diagnostic to run first if you're seeing empty rate cards:

python - <<'EOF'
from hyatt_login import get_chrome_cookies, make_driver
from selenium.webdriver.common.by import By
import time

cookies = get_chrome_cookies()
print(f"Cookies extracted: {len(cookies)}")

driver = make_driver()
hyatt_cookies = [c for c in driver.get_cookies() if "hyatt" in c.get("domain", "")]
print(f"Hyatt cookies in browser: {len(hyatt_cookies)}")

driver.get("https://www.hyatt.com/shop/rooms/[SHOP_CODE]?checkinDate=2026-08-10&checkoutDate=2026-08-12&adults=1&rooms=1")
time.sleep(22)

cards = driver.find_elements(By.CSS_SELECTOR, "div.room-rate-card-wrapper")
print(f"Room cards found: {len(cards)}")  # Should be 8
driver.quit()
EOF

If Room cards found: 0, Chrome is likely locking the Cookies database. Close Chrome and retry.

What Comes Next

The scraper is a sourcing tool. Once it finds a deal above the threshold, the next step is automating the booking — a hyatt_book.py that imports from the same hyatt_login.py auth module and clicks through the booking flow.

That's on the roadmap. For now, the CPP table gets generated in a few minutes instead of an hour, and I know which properties are worth redeeming points at before I have to go through the full booking flow.


Back to Blog