TripAdvisor is one of the most comprehensive travel review platforms on the web — covering hotels, restaurants, and attractions across virtually every major destination. The ratings, review counts, price tiers, and location data published there make it a valuable input for competitive benchmarking, sentiment analysis, destination marketing research, and price-monitoring pipelines.
The challenge is extraction: TripAdvisor's pages load much of their content through JavaScript, and the site actively filters automated traffic. The most reliable data path bypasses the rendered DOM entirely and reads from the JSON-LD block every venue page ships. That <script type="application/ld+json"> tag carries a clean schema.org object — Hotel, LodgingBusiness, or Restaurant — with the name, aggregate rating, review count, price range, and structured address. The samples below fetch through the Bright Data Web Unlocker and pull directly from that block.
Prerequisites
export PROXY_URL="http://brd-customer-<id>-zone-<unblocker_zone>:<password>@brd.superproxy.io:22225"
No Bright Data account yet? Get started with Bright Data's travel data tools →
TripAdvisor URL patterns follow a consistent structure: /Hotel_Review-<geo_id>-<hotel_id>-Reviews-<Slug>.html for hotels, /Restaurant_Review-... for restaurants, and /Attraction_Review-... for attractions. Pass the full page URL as the argument.
PHP
<?php
// Run: php tripadvisor.php "https://www.tripadvisor.com/Hotel_Review-g60763-d93589-..."
$proxy = getenv('PROXY_URL');
$url = $argv[1] ?? 'https://www.tripadvisor.com/Hotel_Review-g60763-d93589-Reviews-The_Peninsula_New_York.html';
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_PROXY => $proxy,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => ['Accept-Language: en-US,en;q=0.9'],
]);
$html = curl_exec($ch);
curl_close($ch);
// TripAdvisor ships venue data as JSON-LD — Hotel, LodgingBusiness, Restaurant, or TouristAttraction.
$doc = new DOMDocument();
@$doc->loadHTML($html);
$xp = new DOMXPath($doc);
$venue = null;
$types = ['Hotel', 'LodgingBusiness', 'Restaurant', 'TouristAttraction'];
foreach ($xp->query('//script[@type="application/ld+json"]') as $node) {
$ld = json_decode($node->textContent, true);
if (in_array($ld['@type'] ?? '', $types, true)) {
$venue = $ld;
break;
}
}
$rating = $venue['aggregateRating'] ?? [];
$address = $venue['address'] ?? [];
echo json_encode([
'name' => $venue['name'] ?? null,
'type' => $venue['@type'] ?? null,
'rating' => $rating['ratingValue'] ?? null,
'reviewCount' => $rating['reviewCount'] ?? null,
'priceRange' => $venue['priceRange'] ?? null,
'city' => $address['addressLocality'] ?? null,
'country' => $address['addressCountry'] ?? null,
'telephone' => $venue['telephone'] ?? null,
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), PHP_EOL;
Node.js
// tripadvisor.mjs — node tripadvisor.mjs "<url>"
// Install: npm i axios https-proxy-agent cheerio
import axios from 'axios';
import { HttpsProxyAgent } from 'https-proxy-agent';
import * as cheerio from 'cheerio';
const agent = new HttpsProxyAgent(process.env.PROXY_URL);
const url = process.argv[2]
?? 'https://www.tripadvisor.com/Hotel_Review-g60763-d93589-Reviews-The_Peninsula_New_York.html';
const { data: html } = await axios.get(url, {
httpsAgent: agent, proxy: false, timeout: 60_000,
headers: { 'Accept-Language': 'en-US,en;q=0.9' },
});
const $ = cheerio.load(html);
const VENUE_TYPES = new Set(['Hotel', 'LodgingBusiness', 'Restaurant', 'TouristAttraction']);
let venue = {};
$('script[type="application/ld+json"]').each((_, el) => {
try {
const ld = JSON.parse($(el).text());
if (VENUE_TYPES.has(ld['@type'])) venue = ld;
} catch { /* skip malformed blocks */ }
});
const rating = venue.aggregateRating ?? {};
const address = venue.address ?? {};
console.log(JSON.stringify({
name: venue.name ?? null,
type: venue['@type'] ?? null,
rating: rating.ratingValue ?? null,
reviewCount: rating.reviewCount ?? null,
priceRange: venue.priceRange ?? null,
city: address.addressLocality ?? null,
country: address.addressCountry ?? null,
telephone: venue.telephone ?? null,
}, null, 2));
Rust
// Cargo.toml:
// reqwest = { version = "0.12", features = ["blocking"] }
// scraper = "0.20"
// serde_json = "1"
use scraper::{Html, Selector};
use serde_json::Value;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let url = std::env::args().nth(1).unwrap_or_else(|| {
"https://www.tripadvisor.com/Hotel_Review-g60763-d93589-Reviews-The_Peninsula_New_York.html".into()
});
let client = reqwest::blocking::Client::builder()
.proxy(reqwest::Proxy::all(std::env::var("PROXY_URL")?)?)
.danger_accept_invalid_certs(true)
.build()?;
let html = client
.get(&url)
.header("Accept-Language", "en-US,en;q=0.9")
.send()?
.text()?;
let doc = Html::parse_document(&html);
let sel = Selector::parse(r#"script[type="application/ld+json"]"#).unwrap();
let venue_types = ["Hotel", "LodgingBusiness", "Restaurant", "TouristAttraction"];
let mut venue = Value::Null;
for el in doc.select(&sel) {
let raw = el.text().collect::<String>();
if let Ok(ld) = serde_json::from_str::<Value>(&raw) {
if let Some(t) = ld["@type"].as_str() {
if venue_types.contains(&t) {
venue = ld;
break;
}
}
}
}
let rating = &venue["aggregateRating"];
let address = &venue["address"];
let out = serde_json::json!({
"name": venue["name"],
"type": venue["@type"],
"rating": rating["ratingValue"],
"reviewCount": rating["reviewCount"],
"priceRange": venue["priceRange"],
"city": address["addressLocality"],
"country": address["addressCountry"],
"telephone": venue["telephone"],
});
println!("{}", serde_json::to_string_pretty(&out)?);
Ok(())
}
What the JSON-LD block contains
The JSON-LD on hotel and restaurant pages is fairly rich. Beyond the fields extracted above, you will typically also find:
image— an array of image URLs or a single URL for the primary venue photo.url— the canonical TripAdvisor URL for the venue, useful as a stable identifier.description— a short editorial description where TripAdvisor provides one.servesCuisine— present on restaurant pages, listing cuisine categories.amenityFeature— on hotel pages, an array ofLocationFeatureSpecificationobjects naming amenities such as free WiFi, parking, or a pool.
Nightly pricing and live availability are not in JSON-LD — TripAdvisor loads them dynamically. Pulling those fields requires either intercepting the XHR calls the page makes after load or using a data provider that handles the dynamic layer for you.
Handling multiple venues and pagination
For a one-off pull on a handful of known venues, looping the scraper over a CSV of URLs and writing JSON output to disk is straightforward. For ongoing monitoring — for example tracking rating drift across a portfolio of competitor hotels — add a timestamp column and store results in a SQLite or Postgres table keyed by venue ID.
TripAdvisor paginates reviews separately from the main venue page. Review list pages follow a URL pattern like /Hotel_Review-...-Reviews-or<offset>-<Slug>.html where or0, or10, or20, and so on step through results ten at a time. Each of those pages also embeds JSON-LD, but aggregateRating only appears on the root (offset-zero) page, so fetch that first to capture the summary metrics, then page through review offsets if you need individual review text.
IP rotation matters here. Fetching dozens of review pages from a single address in quick succession is exactly the pattern TripAdvisor's bot-detection is tuned to catch. See How to Avoid Getting Blocked for general strategies, and the Bright Data Web Unlocker review for how the proxy layer handles fingerprint randomisation and CAPTCHA solving automatically.
Comparing TripAdvisor with other travel targets
TripAdvisor covers the review and rating layer of travel research well, but it doesn't surface booking availability or nightly pricing the way Airbnb and Booking.com do. For a complete travel intelligence pipeline you typically combine sources:
- Airbnb Scraping — listing prices, occupancy calendars, and host details for short-term rentals.
- Booking.com Scraping — hotel rates, availability windows, and guest scores across traditional accommodation.
- Kayak Flight Scraping — airfare pricing and route-level demand signals.
The Travel Data Scraping pillar ties all these sources together if you're building a broader aggregation or price-monitoring product.
Scaling beyond a handful of venues
Running the scraper above on dozens of URLs a day is manageable. Scaling to thousands of venues — across multiple markets, on a daily refresh schedule — means maintaining proxy rotation, absorbing TripAdvisor layout changes, and handling transient blocks, which adds engineering overhead that compounds quickly.
Bright Data's travel data collection tools handle that infrastructure layer and deliver structured venue and review data without requiring you to manage a scraping fleet. For bulk historical pulls the pre-built Bright Data datasets are worth evaluating as a complement.
Notes
ratingValueis typically a decimal string like"4.5"— cast it to float before storing.reviewCountis a string on some pages; normalise to integer before aggregating.- TripAdvisor sometimes emits multiple JSON-LD blocks on the same page (breadcrumb, FAQPage, etc.). The loop above breaks on the first venue-type match, which is always the primary entity.
- The same code handles restaurants and attractions without modification — just pass the appropriate URL.
See the Travel Data Scraping overview, our Airbnb Scraping and Booking.com Scraping guides for complementary data sources, and the Bright Data Web Unlocker review for full proxy setup details.
Collect TripAdvisor data at scale with Bright Data →
FAQ
Is scraping TripAdvisor data legal?
TripAdvisor publishes its hotel and restaurant information publicly for anyone to read. Collecting that publicly available data — names, ratings, addresses, price ranges — is widely practiced for research and aggregation. TripAdvisor's terms of service restrict automated access to certain features, so staying focused on public listing pages and respecting crawl rates keeps you on solid ground. Always consult your own legal counsel for your specific use case.
What data does TripAdvisor's JSON-LD contain?
Most hotel and restaurant detail pages embed a schema.org JSON-LD block typed as Hotel, LodgingBusiness, or Restaurant. It typically includes the venue name, aggregateRating (ratingValue and reviewCount), priceRange, address (locality, country), telephone, and one or more images. Availability and nightly rate data are dynamic and must be extracted from the rendered DOM or a dedicated data API rather than JSON-LD.
Does TripAdvisor block scrapers?
TripAdvisor uses CAPTCHA challenges, bot-score checks, and rate limiting that can block naive curl or fetch requests. Routing requests through a residential or ISP proxy network — such as Bright Data's Web Unlocker — rotates IPs and handles browser fingerprinting automatically, which makes the fetches appear organic. Without this layer most headless requests get blocked or served thin JavaScript-only pages.
How do I scrape many venues in bulk?
For small datasets, loop the single-page scraper across a list of URLs and persist output to a database. For larger pulls, Bright Data's travel data tools supply structured venue and review datasets that skip the crawling layer entirely, which reduces the infrastructure you need to maintain at scale.