Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ Start here: the **orchestration skills** call everything else automatically.
| **rapidapi** | Booking.com hotel prices. | RapidAPI |
| **serpapi** | Google Hotels search and destination discovery. | SerpAPI |
| **ticketsatwork** | TicketsAtWork (EBG) corporate-perks portal. Hotels, theme park tickets, attractions, live events. Often beats portals by 10-30%. Docker: `ghcr.io/borski/ticketsatwork`. | None (requires TaW account + Patchright) |
| **vrbo** | VRBO whole-home, condo, and cabin search via Patchright. Complements Airbnb for group stays. | None (requires Patchright) |
<!-- END: readme:hotels -->

Also use **tripadvisor** (under Destinations) for hotel ratings, rankings, subratings, and reviews.
Expand Down
1 change: 1 addition & 0 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Skill listings below are auto-generated from `skills/*/SKILL.md` frontmatter by
- [Rapidapi](https://github.com/borski/travel-hacking-toolkit/blob/main/skills/rapidapi/SKILL.md): Booking.com hotel prices.
- [Serpapi](https://github.com/borski/travel-hacking-toolkit/blob/main/skills/serpapi/SKILL.md): Google Hotels search and destination discovery.
- [Ticketsatwork](https://github.com/borski/travel-hacking-toolkit/blob/main/skills/ticketsatwork/SKILL.md): TicketsAtWork (EBG) corporate-perks portal. Hotels, theme park tickets, attractions, live events. Often beats portals by 10-30%. Docker: `ghcr.io/borski/ticketsatwork`.
- [Vrbo](https://github.com/borski/travel-hacking-toolkit/blob/main/skills/vrbo/SKILL.md): VRBO whole-home, condo, and cabin search via Patchright. Complements Airbnb for group stays.
<!-- END: llms:hotels -->

## Loyalty and Points
Expand Down
4 changes: 4 additions & 0 deletions plugins/travel-hacking-toolkit/skills/compare-hotels/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Run these in parallel where possible. **Never fail silently.**
| Source | Skill/Tool | Speed | What It Finds |
|--------|------------|-------|---------------|
| Airbnb | `airbnb_search` MCP tool | ~5s | Entire homes, private rooms, with total pricing |
| VRBO | `vrbo` skill (Patchright) | ~20s | Whole homes, condos, cabins. Professionally-managed units often not on Airbnb. Headed/Docker — behind Akamai. |

### Premium Property Databases (local, instant)

Expand All @@ -80,6 +81,9 @@ PARALLEL GROUP 1 (fast, ~3-5s):
- Airbnb: vacation rentals in the area
- Local data: check premium-hotels databases for the city

PARALLEL GROUP 1b (slower, ~20s, headed/Docker — behind Akamai):
- VRBO: whole homes / condos / cabins — vrbo skill (run alongside Airbnb for whole-home comparisons)

PARALLEL GROUP 2 (slow, ~45s, Docker):
- Chase Travel: --hotel --dest "City" --checkin YYYY-MM-DD --checkout YYYY-MM-DD --json
- Amex Travel: --hotel --dest "City" --checkin YYYY-MM-DD --checkout YYYY-MM-DD --json
Expand Down
9 changes: 9 additions & 0 deletions plugins/travel-hacking-toolkit/skills/vrbo/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM ghcr.io/borski/patchright-docker:latest

COPY scripts/search_vrbo.py /app/search_vrbo.py
COPY scripts/entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh

ENV VRBO_IN_DOCKER=1

ENTRYPOINT ["/app/entrypoint.sh"]
158 changes: 158 additions & 0 deletions plugins/travel-hacking-toolkit/skills/vrbo/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---
name: vrbo
description: Search VRBO (Vrbo / Expedia Group) vacation rentals including entire homes, condos, and cabins via Patchright browser automation. VRBO sits behind Akamai Bot Manager so plain HTTP and standard Playwright get a 429 bot wall. Use for whole-home or group stays, multi-bedroom rentals, and Airbnb-vs-VRBO comparisons. Trigger phrases include search VRBO, Vrbo rentals, vacation rental, cabin rental, whole house, condo for the group.
category: hotels
summary: VRBO whole-home, condo, and cabin search via Patchright. Complements Airbnb for group stays.
api_key: None (requires Patchright)
---

# VRBO Search

Search VRBO vacation rentals via Patchright and return whole-home / condo / cabin
listings with pricing and booking links. VRBO is the natural complement to the
Airbnb MCP for group and family stays — many mountain-town and resort property
managers cross-list on both, but VRBO carries professionally-managed condos and
cabins that don't always appear on Airbnb.

**Requires Patchright** (undetected Playwright fork). VRBO sits behind **Akamai
Bot Manager** — plain HTTP, standard Playwright, `@playwright/mcp`, and
agent-browser all get a `429 Too Many Requests` ("Provisioned request rate has
been exceeded") bot wall. Patchright passes Akamai's sensor and earns the
`_abck` cookie. This is the same wall — and the same fix — as the chase-travel /
amex-travel / southwest skills.

**Must run headed (`headless=False`).** Akamai flags headless browsers. On macOS
a Chrome window briefly appears. For background / unattended runs, use Docker
(Xvfb provides the virtual display).

**No login required.** VRBO search results are public — unlike chase-travel /
amex-travel, there are no credentials. The persistent profile only caches the
Akamai cookie so repeat searches re-challenge less.

## Prerequisites

```bash
pip install patchright && patchright install chromium
```

Or use Docker (no local install, headed via Xvfb):

```bash
docker build -t vrbo skills/vrbo/
```

## When to Use

- Group / family stays where you want a whole home, condo, or cabin
- "Compare Airbnb vs VRBO in <place>"
- Multi-bedroom rentals with separate rooms (the multi-person trip pattern)
- Resort / mountain towns (Canmore, Banff-area, Whistler, Muskoka) where
professional property managers list condos on VRBO that aren't on Airbnb

## When NOT to Use

- **Completing a booking.** This skill finds rentals and returns links only. Do
not attempt to complete a purchase.
- **Hotels.** Use compare-hotels / serpapi / liteapi / the portal skills.
- **Single rooms / shared rooms.** VRBO is whole-property only; for private
rooms in a shared home use the Airbnb MCP (`private_room`).

## Usage

```bash
# Local (pops up a Chrome window on macOS)
python3 skills/vrbo/scripts/search_vrbo.py \
--dest "Canmore, Alberta, Canada" \
--checkin 2026-06-26 --checkout 2026-07-02 --adults 3 --json

# Whole homes with 3+ bedrooms
python3 skills/vrbo/scripts/search_vrbo.py \
--dest "Canmore, Alberta" --checkin 2026-06-26 --checkout 2026-07-02 \
--adults 3 --min-bedrooms 3 --json

# Docker (headless host, headed browser via Xvfb)
docker run --rm vrbo \
--dest "Canmore, Alberta" --checkin 2026-06-26 --checkout 2026-07-02 \
--adults 3 --json
```

### Arguments

| Flag | Default | Notes |
|------|---------|-------|
| `--dest` | required | Destination keyword, e.g. `"Canmore, Alberta, Canada"`. VRBO resolves it to a regionId server-side. |
| `--checkin` / `--checkout` | required | ISO `YYYY-MM-DD`. Must be today or later. |
| `--adults` | 2 | Traveler count. |
| `--children` | 0 | |
| `--pets` | off | Pet-friendly only. |
| `--min-bedrooms` | 0 | Client-side filter (VRBO's URL filter is unreliable). |
| `--limit` | 20 | Max listings returned. |
| `--retries` | 4 | Akamai retry attempts (homepage warm-up → SERP). |
| `--json` | off | Emit JSON to stdout (use this for orchestration). |

## Output

JSON shape (with `--json`):

```json
{
"source": "vrbo",
"extractor": "__PLUS_REDUX_STORE__ | dom:lodging-card | dom:anchors",
"searchUrl": "https://www.vrbo.com/search?...",
"count": 12,
"listings": [
{
"id": "1234567",
"name": "Mountain-View Condo Steps from Main St",
"bedrooms": 3, "bathrooms": 2, "sleeps": 6,
"rating": 4.9, "reviewCount": 84,
"priceText": "The current price is CA $312 CA $312 CA $2,184 total includes taxes & fees",
"pricePerNight": 312, "priceTotal": 2184, "priceIncludesTaxes": true,
"lat": 51.08, "lng": -115.35,
"url": "https://www.vrbo.com/en-ca/cottage-rental/p1234567?..."
}
]
}
```

`extractor` tells you which layer produced the data. `__PLUS_REDUX_STORE__` is
richest (bedrooms, coords, review counts); `dom:lodging-card` is reliable but
thinner (the common case — names, prices, URLs); `dom:anchors` is a last-ditch
id+url scrape — if you see that, the page shape changed and selectors need a
refresh (file a P2 task).

**Prices:** the SERP price string is parsed into `pricePerNight` and
`priceTotal`. Verified against live data: VRBO's SERP **total includes taxes &
fees** (`priceIncludesTaxes: true`) — `priceTotal` is the all-in for the whole
stay, `pricePerNight` is the lead nightly rate. This satisfies the
hotel-comparison standard's all-in requirement directly; still open the listing
URL to confirm before booking, since a damage deposit or optional add-ons may
not be in the SERP figure.

## Tests

The pure logic (search-URL building, Akamai block/challenge detection, SERP
price parsing, skeleton filtering, listing enrichment) is covered by stdlib
`unittest` tests — no browser, network, or Patchright needed (the `patchright`
import is lazy):

```bash
python3 -m unittest discover -s skills/vrbo/tests -v # or: python3 -m pytest skills/vrbo/tests
```

17 tests, no third-party deps. The browser-driven path (Akamai bypass, DOM
extraction) can't be unit-tested without a live residential connection — verify
it manually with a real search (see Usage above). The repo smoke test
(`scripts/smoke-test.sh`) separately validates this skill's frontmatter and
structure.

## Known fragility

- **Akamai is IP-rate-limited.** Datacenter / CI / VPN IPs are frequently
blocked even with Patchright. Run on a residential connection for best
results. If every retry 429s, the script exits 1 with a clear message — that
is an environment block, not a code bug.
- **DOM selectors drift.** VRBO/Expedia revise their `data-stid` design system
periodically. The extractor tries the embedded state blob first (most stable),
then `data-stid` cards, then a generic anchor scrape. If `extractor` is
`dom:anchors`, refresh the Layer-1/2 selectors.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash
# Start a virtual display so Patchright can run headed (Akamai detects headless).
Xvfb :99 -screen 0 1440x900x24 -nolisten tcp &
export DISPLAY=:99
sleep 1

exec python3 /app/search_vrbo.py "$@"
Loading