Hotel detail scraping navigates to each hotel's own detail URL instead of
clicking the card at a given index and navigating back. The click-and-return
approach coupled every hotel to the results list surviving intact, so a single
failed click or back-navigation stranded the page and every later hotel logged
Card index N out of range (0 links). Google also recycles cards out of the
DOM while scrolling, so the click target went missing on healthy runs too.
Hotel detail scraping no longer stops after the first hotel. When the run
could not get back to the search results list, every later hotel resolved
against an empty list and logged Card index N out of range (0 links), so a
20-hotel query attempted exactly one detail page. Recovery now retries the
saved search URL with a real navigation wait and dismisses a re-shown consent
dialog; when recovery genuinely fails the detail loop stops and records a
partial reason instead of burning the remaining runtime.
OTA offer rows are given a 15s hydration budget instead of 3s. Google Hotels
renders the price panel well after domcontentloaded, and residential proxies
add several seconds more, so the old budget expired before any offer appeared.
OTA offers are no longer discarded because their booking link is a Google
redirect. Google wraps every offer's deep link, so the terminal-URL rule
rejected 100% of offers — throwing away the provider and price alongside the
link, which is the price comparison this Actor exists to deliver. Offers are
kept and the link is labelled bookingUrlStatus: "google_redirect". Embedded
redirect targets are still never unwrapped, and malformed, credentialed, or
non-HTTP links are still rejected.
Deduplication no longer collapses an entire batch into one row when Google
serves entity-form URLs. Those URLs carry the hotel identity in the path
(/entity/{id}/prices) and set the same qs=OAA stub on every card, so
reading qs gave all 20 hotels one identity and 19 were dropped before output
and charging. Identity now reads the entity id first and ignores qs stubs
shorter than a real token. This hit whole markets: the hotels in Seoul
smoke run delivered 1 of 20 hotels before the fix.
Search cards fall back to the hotel name link when the "View prices" anchor is
missing. That URL is both the detail-scrape target and the row's dedupe
identity, so an absent anchor previously gave every card in the batch the same
empty identity and collapsed 20 results into 1.
A batch whose rows mostly collapse during deduplication now logs a warning with
sample URLs instead of only incrementing a counter in the run summary.