scrapeunblocker 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9349609e20a6b8949d1cc3d77b919cf6c916b4b1917d181bb2d18ec441f46777
4
- data.tar.gz: 55bf0f292146e6e166a502ecb43a33d15b2629261f808ed8754f4ca7a33d96fe
3
+ metadata.gz: d657c2dc1b9947fc1ed1d603a8d99c68ba61beaa9c8bc275ecaa32d574de5284
4
+ data.tar.gz: 97ee9d6183e046f644f67866023baec457cdcdb6375a5a925ee34c0504d04194
5
5
  SHA512:
6
- metadata.gz: 22da248af5842639758605b333930c888534405b796cd199f9c8a2c8bced4104e6dace474c62fbaa2aa878d321878aef3118121fb0563efc4f25a1d2c00451b6
7
- data.tar.gz: 3fea75e8eb0602eb156978c019fea92fe7cfca41aab0714f9049412d26207e707899750ad8547b0caf57d3da368d347d2f2ead34e82d74ee2d4764de5f90afb9
6
+ metadata.gz: a57f2edc6ba0017b291c625dbe6bcd2693af112769b23df3c1a3d7e1bfed7456c301f90e5547d73dad9261495c84194e4dbc0c368facd3917d25177832e6bd9a
7
+ data.tar.gz: ce221c9032035405e0eac225eb61f1bf85f5fc8b9edee5e25e738adfd278b8a42a0d0e58af71d0006b10e4cba0e9e3d30394648deebb5617d5e8436e1b1ea5d5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.14.0 (2026-10-23)
4
+
5
+ - 16 new plugin methods on `Client`:
6
+ - Google: `google_hotels(q)` (Google Hotels search with stay prices, ratings and amenities), `google_hotels_property(property_token)` (one property's prices from every booking site), `google_news(q)` (a search, or a section with `topic:`), `google_ads_transparency` (an advertiser's ads from the Ads Transparency Center, picked with `advertiser:` or `domain:`) and `google_ads_transparency_advertisers(q)` (advertiser lookup by name).
7
+ - E-commerce: `shopee_search(q)` and `shopee_product(url)` (Shopee in eight markets, `country:`), `taobao_search(q)` and `taobao_product(item_id)` (Taobao and Tmall), `smyths_inventory(code)`, `smyths_details(code)`, `smyths_search(query)` and `smyths_category(category)` (Smyths Toys UK), `wish_search(query)` and `wish_product(product)`.
8
+ - Jobs: `upwork_jobs(q)`.
9
+ - The first argument of `google_news`, `shopee_search`, `shopee_product`, `taobao_search`, `taobao_product` and `upwork_jobs` is optional, for calls that pass another input instead: `google_news(topic: "technology")`, `shopee_product(shop_id: ..., item_id: ...)`, `taobao_product(url: ...)`, `upwork_jobs(sort: "recency")` for the newest jobs. `google_news` takes the API's `when:` window as is (`google_news("tesla", when: "1d")`).
10
+ - `temu_search` and `temu_product` are deprecated: Temu's plugin is unlisted. They are kept for compatibility with the same signatures, now defined on `Client` instead of `ScrapeUnblocker::Plugins`, and are no longer listed in the README.
11
+ - `google_shopping` docs: result cards also carry `currencyCode` and Google's own product ids (`productId`, `catalogId`, `offerId`).
12
+
13
+ No breaking changes.
14
+
15
+ ## 0.13.0 (2026-10-23)
16
+
17
+ - New `get_markdown(url, main_content_only: true, ...)` returns the rendered page as clean Markdown for LLMs, agents and RAG pipelines: headings, lists, tables, absolute links, images with their alt text and code blocks, without scripts, styles, navigation, footers, forms or hidden elements. It sends `markdown=true` to `/getPageSource`, is billed like an HTML call, and returns a UTF-8 `String`.
18
+ - `main_content_only:` (default `true`) keeps only the page's main content, without navigation, site header, footer and sidebars; pass `false` to convert the whole page. The method also takes `proxy_country:`, `time_sleep:`, `method:`, `value:`, `method_timeout:` and `steps:`, like `get_page_source`.
19
+ - A target page that answers 404 or 410 raises `TargetNotFoundError`, as with `get_page_source`; the not-found page, as Markdown, is on `#html`.
20
+
21
+ No breaking changes.
22
+
3
23
  ## 0.12.0 (2026-10-15)
4
24
 
5
25
  - `google_images` takes `time_range:` (`"hour"`, `"day"`, `"week"`, `"month"` or `"year"`) to return only the images Google found in that recent period, so a query scanned on a schedule returns only what is new. For a fixed window pass `date_from:` and/or `date_to:` (`"YYYY-MM-DD"`) instead; the API answers 400 when `time_range` is combined with either.
data/README.md CHANGED
@@ -62,6 +62,21 @@ html = su.get_page_source(
62
62
  )
63
63
  ```
64
64
 
65
+ ## Markdown for LLMs
66
+
67
+ Get the rendered page as clean Markdown instead of HTML, ready for an LLM prompt, an agent or a RAG pipeline. Headings, lists, tables, absolute links and images with their alt text are kept; scripts, styles, navigation, footers and forms are dropped. Billed like an HTML call.
68
+
69
+ ```ruby
70
+ md = su.get_markdown("https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html")
71
+ puts md # "# A Light in the Attic\n\n£51.77\n..." (a UTF-8 String)
72
+
73
+ # Only the main content by default (no navigation, site header, footer or sidebars).
74
+ # Pass main_content_only: false to convert the whole page:
75
+ full = su.get_markdown("https://example.com", main_content_only: false)
76
+ ```
77
+
78
+ `get_markdown` takes the same `proxy_country:`, `time_sleep:`, `method:` / `value:` / `method_timeout:` and `steps:` options as `get_page_source`. More in the [Markdown guide](https://docs.scrapeunblocker.com/guides/markdown?utm_source=rubygems&utm_medium=integration&utm_campaign=ruby-sdk).
79
+
65
80
  ## Browser steps
66
81
 
67
82
  Drive the page after it loads with an ordered list of browser actions - fill a form, click through, wait for content, then capture the rendered result. Steps run once in the real browser, in order.
@@ -134,14 +149,14 @@ jobs = su.career_site_jobs("https://jobs.ashbyhq.com/ramp", keyword: "engineer")
134
149
  | Category | Methods |
135
150
  | --- | --- |
136
151
  | Travel | `skyscanner.flights`, `skyscanner.hotels`, `skyscanner.carhire`, `booking_hotels`, `booking_reviews`, `airbnb_search`, `airbnb_room`, `airbnb_reviews`, `tripadvisor_list`, `tripadvisor_reviews` |
137
- | Google | `google_local`, `google_images`, `maps_place_details`, `google_trends`, `google_shopping`, `google_jobs` |
152
+ | Google | `google_local`, `google_images`, `maps_place_details`, `google_trends`, `google_shopping`, `google_jobs`, `google_hotels`, `google_hotels_property`, `google_news`, `google_ads_transparency`, `google_ads_transparency_advertisers` |
138
153
  | Search engines | `bing_search`, `bing_images`, `duckduckgo_search`, `yahoo_search`, `yahoo_images`, `brave_search`, `brave_images` |
139
- | E-commerce | `amazon_product`, `amazon_search`, `amazon_bestsellers`, `amazon_reviews`, `walmart_search`, `target_search`, `bestbuy_search`, `homedepot_search`, `etsy_search`, `temu_search`, `temu_product`, `shein_search`, `zalando_search`, `aliexpress_search`, `alibaba_search`, `alibaba_product`, `alibaba1688_search`, `alibaba1688_product`, `tiktok_shop_search`, `tiktok_shop_product`, `tiktok_shop_store`, `oopbuy_search` |
154
+ | E-commerce | `amazon_product`, `amazon_search`, `amazon_bestsellers`, `amazon_reviews`, `walmart_search`, `target_search`, `bestbuy_search`, `homedepot_search`, `etsy_search`, `shein_search`, `zalando_search`, `aliexpress_search`, `alibaba_search`, `alibaba_product`, `alibaba1688_search`, `alibaba1688_product`, `tiktok_shop_search`, `tiktok_shop_product`, `tiktok_shop_store`, `oopbuy_search`, `smyths_inventory`, `smyths_details`, `smyths_search`, `smyths_category`, `wish_search`, `wish_product`, `shopee_search`, `shopee_product`, `taobao_search`, `taobao_product` |
140
155
  | Classifieds & Resale | `ebay_search`, `vinted_search`, `leboncoin_search` |
141
156
  | Groceries | `woolworths_search`, `woolworths_category`, `woolworths_specials`, `woolworths_product`, `coles_search`, `coles_category`, `coles_specials`, `coles_product`, `aldi_au_search`, `aldi_au_category`, `aldi_au_specials`, `aldi_au_product` |
142
157
  | Real estate | `zillow_search`, `zillow_detail`, `redfin_search`, `redfin_detail`, `rightmove_search`, `idealista_search`, `idealista_detail`, `immobilienscout24_search` |
143
158
  | Vehicles | `mobile_de`, `autoscout24_search` |
144
- | Jobs | `indeed_jobs`, `glassdoor_jobs`, `glassdoor_reviews`, `glassdoor_salaries`, `naukri_jobs`, `wellfound_jobs`, `wellfound_job`, `wellfound_company`, `wellfound_company_search`, `career_site_jobs` |
159
+ | Jobs | `indeed_jobs`, `glassdoor_jobs`, `glassdoor_reviews`, `glassdoor_salaries`, `naukri_jobs`, `wellfound_jobs`, `wellfound_job`, `wellfound_company`, `wellfound_company_search`, `career_site_jobs`, `upwork_jobs` |
145
160
  | Reviews & Companies | `trustpilot_reviews`, `g2_reviews`, `g2_search`, `capterra_reviews`, `capterra_search`, `product_hunt`, `crunchbase_company`, `crunchbase_search` |
146
161
  | Local & Maps | `yelp_search`, `yelp_business`, `yelp_reviews`, `naver_place_search`, `naver_place_details` |
147
162
  | Apps | `google_play_app`, `google_play_search`, `app_store_app`, `app_store_search`, `app_store_reviews` |
@@ -299,7 +314,7 @@ end
299
314
  | `NotFoundError` | 404 | What you asked for does not exist - a plugin lookup found nothing |
300
315
  | `TargetNotFoundError` | 404 / 410 | The target page itself does not exist; carries `origin_status`, `html`, `destination_url` (subclass of `NotFoundError`, billed) |
301
316
  | `BrowserTimeoutError` | 408 | Our browser run timed out before the page was ready |
302
- | `UnsupportedContentError` | 415 | The URL serves something other than HTML (`get_page_source`) or other than an image (`get_image`) |
317
+ | `UnsupportedContentError` | 415 | The URL serves something other than HTML (`get_page_source`, `get_markdown`) or other than an image (`get_image`) |
303
318
  | `ValidationError` | 422 | Missing or wrong-typed parameter; `body` holds the `detail` array |
304
319
  | `RateLimitError` | 429 | Too many requests |
305
320
  | `UpstreamOutageError` | 503 | The target origin is down |
@@ -322,7 +337,7 @@ end
322
337
 
323
338
  `TargetNotFoundError` subclasses `NotFoundError`, so `rescue ScrapeUnblocker::NotFoundError` catches it too. A 404 without `X-Origin-Status` is the API's own and stays a plain `NotFoundError`.
324
339
 
325
- With `get_parsed` the body is the parsed-data JSON (`{"data": {"page_type": "not_found", ...}}`), so `html` is `nil` there; the raw JSON is on `#body`.
340
+ With `get_parsed` the body is the parsed-data JSON (`{"data": {"page_type": "not_found", ...}}`), so `html` is `nil` there; the raw JSON is on `#body`. With `get_markdown` the not-found page on `#html` is Markdown.
326
341
 
327
342
  ### No structured data on the page
328
343
 
@@ -75,6 +75,35 @@ module ScrapeUnblocker
75
75
  body
76
76
  end
77
77
 
78
+ # Fetch a URL and return the rendered page as clean Markdown (a UTF-8 String).
79
+ #
80
+ # Built for LLMs, agents and RAG pipelines. The page is rendered in a real
81
+ # browser behind the same anti-bot bypass chain as #get_page_source, then
82
+ # converted to Markdown: headings, lists, tables, absolute links, images with
83
+ # their alt text and code blocks, without scripts, styles, navigation,
84
+ # footers, forms or hidden elements. Billed like an HTML call.
85
+ #
86
+ # +main_content_only+ (default true) keeps only the page's main content,
87
+ # without navigation, the site header, footer and sidebars; pass false to
88
+ # convert the whole page. +proxy_country+, +time_sleep+, +method+, +value+,
89
+ # +method_timeout+ and +steps+ work as in #get_page_source.
90
+ #
91
+ # When the target page itself answers 404 or 410 this raises
92
+ # ScrapeUnblocker::TargetNotFoundError (billed; the not-found page, as
93
+ # Markdown, is on #html).
94
+ def get_markdown(url, main_content_only: true, proxy_country: nil, time_sleep: nil, method: nil, value: nil,
95
+ method_timeout: nil, steps: nil)
96
+ body = request("/getPageSource",
97
+ url: url, markdown: true,
98
+ main_content_only: (main_content_only == false ? false : nil),
99
+ proxy_country: proxy_country, time_sleep: time_sleep,
100
+ method: method, value: value, method_timeout: method_timeout,
101
+ steps: (steps ? JSON.generate(steps) : nil))[:body]
102
+ # The API always sends Markdown as UTF-8; Net::HTTP hands the body over
103
+ # as binary, which breaks string operations on non-ASCII text.
104
+ body.dup.force_encoding(Encoding::UTF_8)
105
+ end
106
+
78
107
  # Fetch a URL and return structured JSON instead of HTML.
79
108
  #
80
109
  # A page that rendered but held no structured data comes back as a
@@ -278,6 +307,37 @@ module ScrapeUnblocker
278
307
  url: url, max_comments: max_comments, proxy_country: proxy_country)
279
308
  end
280
309
 
310
+ # Search Temu by keyword. Returns the matching product urls (about 40 per
311
+ # page); fetch each url's data with #temu_product.
312
+ #
313
+ # @deprecated Temu's plugin is unlisted; kept for compatibility.
314
+ # @param keyword [String] Search phrase, e.g. 'kids toys' or 'phone case'.
315
+ # @param limit [Integer] Max product urls to return (one search page carries ~40). API
316
+ # default: 40.
317
+ # @param sort [String] Result order, using Temu's own sort: `relevance` (default),
318
+ # `best_selling`, `recent`, `price_asc` or `price_desc`.
319
+ # Calls POST /goods/temu-search.
320
+ def temu_search(keyword, limit: nil, sort: nil)
321
+ post_json("/goods/temu-search", {
322
+ keyword: keyword,
323
+ limit: limit,
324
+ sort: sort,
325
+ })
326
+ end
327
+
328
+ # Scrape a Temu product page.
329
+ #
330
+ # @deprecated Temu's plugin is unlisted; kept for compatibility.
331
+ # @param url [String] Full Temu product url, e.g.
332
+ # 'https://www.temu.com/xyz-g-601099512345678.html'. Must be a product page (its path ends
333
+ # in `-g-<id>.html`).
334
+ # Calls POST /goods/temu-product.
335
+ def temu_product(url)
336
+ post_json("/goods/temu-product", {
337
+ url: url,
338
+ })
339
+ end
340
+
281
341
  def post_json(path, params)
282
342
  JSON.parse(request(path, params)[:body])
283
343
  end
@@ -310,48 +310,6 @@ module ScrapeUnblocker
310
310
  })
311
311
  end
312
312
 
313
- # Search Temu by keyword.
314
- #
315
- # Searches Temu for `keyword` and returns the matching product urls (~40 per page). This is
316
- # discovery only - fetch each url's data (name, price, rating, reviews, images) through
317
- # `POST /goods/temu-product`. Use `sort` to pick Temu's own result order (relevance,
318
- # best_selling, recent, price_asc, price_desc). Response time is typically ~2s.
319
- #
320
- # @param keyword [String] Search phrase, e.g. 'kids toys' or 'phone case'.
321
- # @param limit [Integer] Max product urls to return (one search page carries ~40). API
322
- # default: 40.
323
- # @param sort [String] Result order, using Temu's own sort: `relevance` (default),
324
- # `best_selling` (top sales), `recent` (most recent), `price_asc` (price low to high) or
325
- # `price_desc` (price high to low). API default: relevance.
326
- # Calls POST /goods/temu-search.
327
- def temu_search(keyword, limit: nil, sort: nil)
328
- post_json("/goods/temu-search", {
329
- keyword: keyword,
330
- limit: limit,
331
- sort: sort,
332
- })
333
- end
334
-
335
- # Scrape a Temu product page.
336
- #
337
- # Fetches a single Temu product and returns it as JSON: `name`, `description`, `brand`,
338
- # `sku`, `price`, `priceCurrency`, `availability`, `itemCondition`, `rating`, `reviewCount`,
339
- # `variantGroupId`, `images`, `reviews` (per-review rating and date; Temu masks the review
340
- # text and author name in this document) and `video` (a `{contentUrl, thumbnailUrl,
341
- # uploadDate}` object when the product ships one, else null). Temu serves product data only
342
- # to search-engine crawlers, so this reads the ld+json SEO document rather than a browser
343
- # render. The url must be a product page (path ends in `-g-<id>.html`).
344
- #
345
- # @param url [String] Full Temu product url, e.g.
346
- # 'https://www.temu.com/xyz-g-601099512345678.html'. Must be a product page (its path ends
347
- # in `-g-<id>.html`); search/category urls have no product data and are rejected.
348
- # Calls POST /goods/temu-product.
349
- def temu_product(url)
350
- post_json("/goods/temu-product", {
351
- url: url,
352
- })
353
- end
354
-
355
313
  # Scrape AliExpress search results.
356
314
  #
357
315
  # Searches AliExpress for `keyword` and returns the products as JSON: `title`, product
@@ -535,6 +493,60 @@ module ScrapeUnblocker
535
493
  })
536
494
  end
537
495
 
496
+ # Scrape Wish product search.
497
+ #
498
+ # Searches Wish (wish.com) for `query` and returns `products`: `id`, `name`, `url`, `price`,
499
+ # `originalPrice` (crossed-out price, when discounted), `currency`, `priceText`, `priceUsd`
500
+ # (Wish's USD value, comparable across markets), `shipping`, `rating`, `ratingCount`,
501
+ # `numBought`, `inventory`, `colors`, `sizes`, `merchant` (`id`, `name`, `rating`,
502
+ # `ratingCount`, `feedback`, `country`), `originCountry`, `shipsFrom`, `tileText`,
503
+ # `isWishPick`, `position`, `image` and `thumbnail`, plus `totalResults` and `nextPage`.
504
+ # Wish shows the catalogue and prices of the request's country (`proxy_country`, default
505
+ # US). Collects up to `max_results` from `page` on, billed one unit per page.
506
+ #
507
+ # @param query [String] What to search for, e.g. 'yoga mat' or 'phone case', or a
508
+ # wish.com/search/... link.
509
+ # @param page [Integer] 1-based results page to start from (1-300, up to 30 products per
510
+ # page). API default: 1.
511
+ # @param max_results [Integer] Products to collect across pages, 1-300. Each page fetched is
512
+ # billed one unit. API default: 30.
513
+ # @param proxy_country [String] Proxy country the request is sent from (ISO-2, e.g. 'US',
514
+ # 'GB', 'DE'). Wish shows the catalogue, prices and currency of that country. Default 'US'
515
+ # (prices in USD).
516
+ # Calls POST /marketplace/wish-search.
517
+ def wish_search(query, page: nil, max_results: nil, proxy_country: nil)
518
+ post_json("/marketplace/wish-search", {
519
+ query: query,
520
+ page: page,
521
+ max_results: max_results,
522
+ proxy_country: proxy_country,
523
+ })
524
+ end
525
+
526
+ # Scrape one Wish product.
527
+ #
528
+ # One Wish (wish.com) product as JSON: `id`, `name`, `url`, `price` (cheapest available
529
+ # variant), `originalPrice`, `currency`, `priceUsd`, `priceRange`, `shipping`, `inStock`,
530
+ # `rating`, `ratingCount`, `description`, `attributes`, `badges`, `images`, `merchant`
531
+ # (`id`, `name`, `handle`, `rating`, `ratingCount`, `tier`), `shipsFrom`, `shipsTo`,
532
+ # `deliveryEstimate`, `returnPolicy`, `variants` (`id`, `color`, `size`, `price`,
533
+ # `originalPrice`, `priceUsd`, `shipping`, `inventory`, `available`) and `topReviews`
534
+ # (`rating`, `comment`, `date`, `variant`, `upvotes`, `country`; no reviewer names). Prices
535
+ # are those of the request's country (`proxy_country`, default US). One unit per call.
536
+ #
537
+ # @param product [String] A Wish product id ('5f96ad46b3f6480c7b9d7b52') or a product link
538
+ # (https://www.wish.com/product/..., /c/...).
539
+ # @param proxy_country [String] Proxy country the request is sent from (ISO-2, e.g. 'US',
540
+ # 'GB', 'DE'). Wish shows the catalogue, prices and currency of that country. Default 'US'
541
+ # (prices in USD).
542
+ # Calls POST /marketplace/wish-product.
543
+ def wish_product(product, proxy_country: nil)
544
+ post_json("/marketplace/wish-product", {
545
+ product: product,
546
+ proxy_country: proxy_country,
547
+ })
548
+ end
549
+
538
550
  # Search TikTok Shop products by keyword or category.
539
551
  #
540
552
  # Lists TikTok Shop (US) products for a keyword (`q`) or a category (`category`, URL or id).
@@ -613,6 +625,158 @@ module ScrapeUnblocker
613
625
  })
614
626
  end
615
627
 
628
+ # Scrape Shopee product search.
629
+ #
630
+ # Searches a Shopee market (Singapore, Indonesia, Malaysia, Thailand, Vietnam, Philippines,
631
+ # Taiwan or Brazil - `country`) for `q` and returns one page of results: up to 40
632
+ # `products`, each with `position`, `itemId`, `shopId`, `name`, `url`, `image`, `price`,
633
+ # `originalPrice` (when Shopee shows the crossed-out price), `discountPercent`, `currency`,
634
+ # `rating` (average stars), `location` (where the seller ships from) and `labels` (promotion
635
+ # badges). `sort` orders the results, `min_price` / `max_price` filter them, `shop_id` keeps
636
+ # one shop's products (or lists the shop without `q`), and `url` takes a Shopee search or
637
+ # category link instead. Shopee shows one page per search, so run several sort orders or
638
+ # price ranges for more products. `price` is what Shopee shows a signed-out shopper, which
639
+ # can be a promotional or new-customer price (see `labels`). Not available: stock, units
640
+ # sold, rating counts and reviews (Shopee does not publish them on the pages these endpoints
641
+ # read). One unit per call.
642
+ #
643
+ # @param q [String] What to search for, e.g. 'iphone case' or 'air fryer'. Optional with
644
+ # `shop_id`.
645
+ # @param country [String] Shopee market: 'sg' (shopee.sg), 'id' (shopee.co.id), 'my'
646
+ # (shopee.com.my), 'th' (shopee.co.th), 'vn' (shopee.vn), 'ph' (shopee.ph), 'tw'
647
+ # (shopee.tw), 'br' (shopee.com.br). Default 'sg'. Prices are in that market's currency.
648
+ # @param shop_id [String] Only this shop's products (the number in a shop's links, e.g.
649
+ # 442800909). Without `q` it lists the shop.
650
+ # @param url [String] A Shopee search or category link (e.g.
651
+ # https://shopee.sg/search?keyword=lego or https://shopee.sg/Cases-Covers-
652
+ # cat.11013350.11013434.11027839), used as it is; `q`, `country`, `shop_id`, `sort` and the
653
+ # price filters are then ignored.
654
+ # @param sort [String] Result order: relevance, sales, newest, price_asc, price_desc. API
655
+ # default: relevance.
656
+ # @param min_price [Integer] Lowest price, in the market's currency.
657
+ # @param max_price [Integer] Highest price, in the market's currency.
658
+ # @param max_results [Integer] Products to return, 1-40 (one Shopee results page). API
659
+ # default: 40.
660
+ # Calls POST /marketplace/shopee-search.
661
+ def shopee_search(q = nil, country: nil, shop_id: nil, url: nil, sort: nil, min_price: nil, max_price: nil, max_results: nil)
662
+ post_json("/marketplace/shopee-search", {
663
+ q: q,
664
+ country: country,
665
+ shop_id: shop_id,
666
+ url: url,
667
+ sort: sort,
668
+ min_price: min_price,
669
+ max_price: max_price,
670
+ max_results: max_results,
671
+ })
672
+ end
673
+
674
+ # Scrape one Shopee product.
675
+ #
676
+ # One Shopee product as JSON, from a product link (`url`) or `shop_id` + `item_id` +
677
+ # `country`: `title`, `price`, `originalPrice`, `discountPercent`, `currency`, `rating`
678
+ # (average stars), `labels`, `images`, `shopLocation` and `shop` (`name`, `username`, `url`,
679
+ # `rating`, `ratingCount`, `followers`, `itemCount`, `responseRatePercent`,
680
+ # `isOfficialShop`, `isPreferredPlusSeller`, `location`, `joinedAt`). With `dataLevel: full`
681
+ # it also has `description`, `brand`, `condition`, `categories`, `videos`, `variations`
682
+ # (option names), `models` (`modelId`, `name`), `attributes`, `isOfficialShop`,
683
+ # `isPreOrder`, `estimatedShippingDays` and `createdAt`; `dataLevel: meta` means Shopee
684
+ # showed only the product's title and main image (some markets), with price, rating and shop
685
+ # still included. `price` is what Shopee shows a signed-out shopper, which can be a
686
+ # promotional or new-customer price (see `labels`). Not available: stock, units sold, rating
687
+ # counts and reviews (Shopee does not publish them on the pages these endpoints read). One
688
+ # unit per call.
689
+ #
690
+ # @param url [String] A Shopee product link, e.g. https://shopee.sg/Beats-
691
+ # iPhone-17-Case-i.442800909.40270108836 or
692
+ # https://shopee.co.id/product/1426163997/40581375446.
693
+ # @param shop_id [String] Instead of `url`: the shop id (the first number after '-i.' in a
694
+ # product link).
695
+ # @param item_id [String] Instead of `url`: the item id (the second number after '-i.').
696
+ # @param country [String] With `shop_id` + `item_id`: Shopee market: 'sg' (shopee.sg), 'id'
697
+ # (shopee.co.id), 'my' (shopee.com.my), 'th' (shopee.co.th), 'vn' (shopee.vn), 'ph'
698
+ # (shopee.ph), 'tw' (shopee.tw), 'br' (shopee.com.br). Default 'sg'. Prices are in that
699
+ # market's currency.
700
+ # Calls POST /marketplace/shopee-product.
701
+ def shopee_product(url = nil, shop_id: nil, item_id: nil, country: nil)
702
+ post_json("/marketplace/shopee-product", {
703
+ url: url,
704
+ shop_id: shop_id,
705
+ item_id: item_id,
706
+ country: country,
707
+ })
708
+ end
709
+
710
+ # Scrape Taobao / Tmall keyword or category listings.
711
+ #
712
+ # Returns the products Taobao lists for a keyword `q` (or a `category`) as JSON: `products`
713
+ # (`position`, `itemId`, `title`, `url`, `image`, `price` in yuan with `currency` CNY,
714
+ # `priceLabel` (Taobao's price tag, e.g. 优惠价), `localPrices` (the same price in the
715
+ # storefront's currency, HKD and TWD), `shopId`, `shopName`, `descriptionMatchScore`,
716
+ # `freeShipping`, `categoryId`, `rootCategoryId`, `sellingPoints`, `reviewHighlights`),
717
+ # `totalResults` (how many listings Taobao reports), and `filterOptions` (brand and property
718
+ # facets; pass an option's `filter` value back as `filter`). Sort by relevance, sales or
719
+ # price and filter by a yuan price range (products whose listed price falls outside it are
720
+ # left out and counted in `droppedOutsidePriceRange`). Taobao shows signed-out visitors only
721
+ # the first 20 products of a list, so one call returns up to 20 (narrow with price ranges
722
+ # and filters to reach more). When Taobao has no list for the keyword, the call answers with
723
+ # the list of the closest keyword Taobao itself suggests and says so: `keywordSubstituted`
724
+ # true and `matchedKeyword`. Text comes in Chinese as sellers write it. Companion to
725
+ # `/marketplace/taobao-product`. One unit per call.
726
+ #
727
+ # @param q [String] Keyword, in Chinese or English, e.g. '手机壳', '连衣裙', 'phone case' (Chinese
728
+ # keywords match far more listings). A Taobao search or list link works too. Give `q` or
729
+ # `category`.
730
+ # @param category [String] A Taobao category id, e.g. '50005050', or a category list link
731
+ # (https://www.taobao.com/list/category/50005050.htm). Returns the category's best sellers;
732
+ # sort and filters apply to keyword searches only.
733
+ # @param sort [String] Order: relevance, sales, price_asc, price_desc (price in yuan). API
734
+ # default: relevance.
735
+ # @param price_min [Float] Only products at or above this many yuan (CNY).
736
+ # @param price_max [Float] Only products at or below this many yuan (CNY).
737
+ # @param filter [String] One brand or property filter, as the `filter` value of an option in
738
+ # `filterOptions` of an earlier answer, e.g. '20000-88634403'.
739
+ # Calls POST /marketplace/taobao-search.
740
+ def taobao_search(q = nil, category: nil, sort: nil, price_min: nil, price_max: nil, filter: nil)
741
+ post_json("/marketplace/taobao-search", {
742
+ q: q,
743
+ category: category,
744
+ sort: sort,
745
+ price_min: price_min,
746
+ price_max: price_max,
747
+ filter: filter,
748
+ })
749
+ end
750
+
751
+ # Scrape one Taobao / Tmall item.
752
+ #
753
+ # Returns one Taobao or Tmall item (by `item_id` or `url`) as JSON: `itemId`, `url`,
754
+ # `platform` (taobao / tmall), `title` (`titleTruncated` when Taobao shortens it, as it does
755
+ # for Tmall items), `price`, `originalPrice` and `priceMax` (for a price range) in yuan with
756
+ # `currency` CNY, `image`, `location` (ships from), `categoryId`, `rootCategoryId`,
757
+ # `categoryPath`, `freeShipping`, `paymentMethods`, `sellableLanguages`, `rating` (positive
758
+ # / neutral / negative split and counts; Taobao items only, Tmall does not publish it),
759
+ # `latestReview` (`rating`, `content`, `date`, `variant`, `hasMedia`, `followUps`; no
760
+ # reviewer identity, so no review photo or video URLs) and `shop` (`shopId`, `name`, `url`,
761
+ # `logo`, `sellerType` B = Tmall / C = Taobao, `creditLevel`, `openedAt`, `itemCount`,
762
+ # `scores` for description, service and delivery). This is what Taobao shows signed-out
763
+ # visitors: variants (SKUs), stock and monthly sales need a Taobao login and are not
764
+ # included. Companion to `/marketplace/taobao-search`. One unit per call.
765
+ #
766
+ # @param item_id [String] Numeric Taobao or Tmall item id, e.g. '765833709683' (the `id` in
767
+ # item.taobao.com/item.htm?id=...).
768
+ # @param url [String] Taobao or Tmall item link, e.g.
769
+ # 'https://item.taobao.com/item.htm?id=765833709683' or
770
+ # 'https://detail.tmall.com/item.htm?id=1081364414020' (world.taobao.com, mobile and
771
+ # overseas item links work too). Either item_id or url is required.
772
+ # Calls POST /marketplace/taobao-product.
773
+ def taobao_product(item_id = nil, url: nil)
774
+ post_json("/marketplace/taobao-product", {
775
+ item_id: item_id,
776
+ url: url,
777
+ })
778
+ end
779
+
616
780
  # Scrape Vinted product search.
617
781
  #
618
782
  # Searches Vinted for `query` and returns the listings as JSON: `title`, `price` and
@@ -1136,6 +1300,188 @@ module ScrapeUnblocker
1136
1300
  })
1137
1301
  end
1138
1302
 
1303
+ # Smyths Toys UK product stock, price, details and listings.
1304
+ #
1305
+ # Smyths Toys UK (smythstoys.com) as JSON, in four modes. `mode=product` (the default when
1306
+ # `code` is given) returns one product's stock and price as the JSON Smyths' own storefront
1307
+ # reads (`/api/uk/en-gb/product/product-inventory`), unchanged: `code`, `type`, `prices`
1308
+ # (`price`, `value`, `wasPrice`, `thenPrice`, `rrpPrice`, `discountedPercentage`),
1309
+ # `hdSection` (home delivery: `stockStatus`, `stockMessage`, `isAvailableForDelivery`,
1310
+ # `isPreOrder`, `expectedStockDate`, delivery messages), `ccSection` (click & collect: the
1311
+ # same stock fields plus `storeName`, `deliveryMessage`, `storeClosed`), `ageSuitabilities`,
1312
+ # `carrierType`, marketing messages and decals. `code` takes a product code (`225095`), a
1313
+ # product or bundle page link or a product-inventory link; one product is one billed
1314
+ # request. `mode=details&code=` returns the JSON Smyths' product page is built from
1315
+ # (`/api/uk/en-gb/product`, a bundle's from `/api/uk/en-gb/b`), unchanged, one billed
1316
+ # request: `name`, `brand`, `ean`, `description` (HTML), `webFeatures` (the key features
1317
+ # list), the safety warnings `euWarning`, `safetyText`, `urgentSafetyTextHeading` and
1318
+ # `urgentSafetyTextDescription`, `productImages` (the gallery: `thumbnail`, `original` and
1319
+ # `zoom` per `galleryIndex`, videos as `youtubeUrl`), `prices`, `averageRating`,
1320
+ # `numberOfReviews`, `breadcrumb`, `categoryHierarchy`, `ageSuitabilities`,
1321
+ # `assemblyRequired` and more; a bundle adds `subProducts`. `url` and `breadcrumb` paths are
1322
+ # relative to https://www.smythstoys.com/uk/en-gb. `mode=search&query=` and
1323
+ # `mode=category&category=` find product codes: they return `products` (`code`, `name`,
1324
+ # `url`, `prices` with the same fields, `currency`, `image`, `averageRating`, `productType`
1325
+ # - `BUNDLE` for bundles - and `decals`) with `totalResults`, `totalPages` and `nextPage`,
1326
+ # 60 per page; a category also returns its `title`, `breadcrumbs` and `subcategories`
1327
+ # (`code`, `name`, `count`). Smyths' search returns its best 240 matches at most, a category
1328
+ # every product it holds. Listings collect up to `max_results` from `page` on, billed one
1329
+ # unit per page.
1330
+ #
1331
+ # @param code [String] mode=product and mode=details: product code ('225095'), product or
1332
+ # bundle page link, or product-inventory link.
1333
+ # @param bundle [Boolean] mode=product and mode=details: ask for a bundle. A bundle page
1334
+ # link sets it, and a code Smyths does not answer as a product is asked again as a bundle.
1335
+ # API default: false.
1336
+ # Calls POST /marketplace/smyths-inventory.
1337
+ def smyths_inventory(code, bundle: nil)
1338
+ post_json("/marketplace/smyths-inventory", {
1339
+ mode: "product",
1340
+ code: code,
1341
+ bundle: bundle,
1342
+ })
1343
+ end
1344
+
1345
+ # Smyths Toys UK product stock, price, details and listings.
1346
+ #
1347
+ # Smyths Toys UK (smythstoys.com) as JSON, in four modes. `mode=product` (the default when
1348
+ # `code` is given) returns one product's stock and price as the JSON Smyths' own storefront
1349
+ # reads (`/api/uk/en-gb/product/product-inventory`), unchanged: `code`, `type`, `prices`
1350
+ # (`price`, `value`, `wasPrice`, `thenPrice`, `rrpPrice`, `discountedPercentage`),
1351
+ # `hdSection` (home delivery: `stockStatus`, `stockMessage`, `isAvailableForDelivery`,
1352
+ # `isPreOrder`, `expectedStockDate`, delivery messages), `ccSection` (click & collect: the
1353
+ # same stock fields plus `storeName`, `deliveryMessage`, `storeClosed`), `ageSuitabilities`,
1354
+ # `carrierType`, marketing messages and decals. `code` takes a product code (`225095`), a
1355
+ # product or bundle page link or a product-inventory link; one product is one billed
1356
+ # request. `mode=details&code=` returns the JSON Smyths' product page is built from
1357
+ # (`/api/uk/en-gb/product`, a bundle's from `/api/uk/en-gb/b`), unchanged, one billed
1358
+ # request: `name`, `brand`, `ean`, `description` (HTML), `webFeatures` (the key features
1359
+ # list), the safety warnings `euWarning`, `safetyText`, `urgentSafetyTextHeading` and
1360
+ # `urgentSafetyTextDescription`, `productImages` (the gallery: `thumbnail`, `original` and
1361
+ # `zoom` per `galleryIndex`, videos as `youtubeUrl`), `prices`, `averageRating`,
1362
+ # `numberOfReviews`, `breadcrumb`, `categoryHierarchy`, `ageSuitabilities`,
1363
+ # `assemblyRequired` and more; a bundle adds `subProducts`. `url` and `breadcrumb` paths are
1364
+ # relative to https://www.smythstoys.com/uk/en-gb. `mode=search&query=` and
1365
+ # `mode=category&category=` find product codes: they return `products` (`code`, `name`,
1366
+ # `url`, `prices` with the same fields, `currency`, `image`, `averageRating`, `productType`
1367
+ # - `BUNDLE` for bundles - and `decals`) with `totalResults`, `totalPages` and `nextPage`,
1368
+ # 60 per page; a category also returns its `title`, `breadcrumbs` and `subcategories`
1369
+ # (`code`, `name`, `count`). Smyths' search returns its best 240 matches at most, a category
1370
+ # every product it holds. Listings collect up to `max_results` from `page` on, billed one
1371
+ # unit per page.
1372
+ #
1373
+ # @param code [String] mode=product and mode=details: product code ('225095'), product or
1374
+ # bundle page link, or product-inventory link.
1375
+ # @param bundle [Boolean] mode=product and mode=details: ask for a bundle. A bundle page
1376
+ # link sets it, and a code Smyths does not answer as a product is asked again as a bundle.
1377
+ # API default: false.
1378
+ # Calls POST /marketplace/smyths-inventory.
1379
+ def smyths_details(code, bundle: nil)
1380
+ post_json("/marketplace/smyths-inventory", {
1381
+ mode: "details",
1382
+ code: code,
1383
+ bundle: bundle,
1384
+ })
1385
+ end
1386
+
1387
+ # Smyths Toys UK product stock, price, details and listings.
1388
+ #
1389
+ # Smyths Toys UK (smythstoys.com) as JSON, in four modes. `mode=product` (the default when
1390
+ # `code` is given) returns one product's stock and price as the JSON Smyths' own storefront
1391
+ # reads (`/api/uk/en-gb/product/product-inventory`), unchanged: `code`, `type`, `prices`
1392
+ # (`price`, `value`, `wasPrice`, `thenPrice`, `rrpPrice`, `discountedPercentage`),
1393
+ # `hdSection` (home delivery: `stockStatus`, `stockMessage`, `isAvailableForDelivery`,
1394
+ # `isPreOrder`, `expectedStockDate`, delivery messages), `ccSection` (click & collect: the
1395
+ # same stock fields plus `storeName`, `deliveryMessage`, `storeClosed`), `ageSuitabilities`,
1396
+ # `carrierType`, marketing messages and decals. `code` takes a product code (`225095`), a
1397
+ # product or bundle page link or a product-inventory link; one product is one billed
1398
+ # request. `mode=details&code=` returns the JSON Smyths' product page is built from
1399
+ # (`/api/uk/en-gb/product`, a bundle's from `/api/uk/en-gb/b`), unchanged, one billed
1400
+ # request: `name`, `brand`, `ean`, `description` (HTML), `webFeatures` (the key features
1401
+ # list), the safety warnings `euWarning`, `safetyText`, `urgentSafetyTextHeading` and
1402
+ # `urgentSafetyTextDescription`, `productImages` (the gallery: `thumbnail`, `original` and
1403
+ # `zoom` per `galleryIndex`, videos as `youtubeUrl`), `prices`, `averageRating`,
1404
+ # `numberOfReviews`, `breadcrumb`, `categoryHierarchy`, `ageSuitabilities`,
1405
+ # `assemblyRequired` and more; a bundle adds `subProducts`. `url` and `breadcrumb` paths are
1406
+ # relative to https://www.smythstoys.com/uk/en-gb. `mode=search&query=` and
1407
+ # `mode=category&category=` find product codes: they return `products` (`code`, `name`,
1408
+ # `url`, `prices` with the same fields, `currency`, `image`, `averageRating`, `productType`
1409
+ # - `BUNDLE` for bundles - and `decals`) with `totalResults`, `totalPages` and `nextPage`,
1410
+ # 60 per page; a category also returns its `title`, `breadcrumbs` and `subcategories`
1411
+ # (`code`, `name`, `count`). Smyths' search returns its best 240 matches at most, a category
1412
+ # every product it holds. Listings collect up to `max_results` from `page` on, billed one
1413
+ # unit per page.
1414
+ #
1415
+ # @param query [String] mode=search: what to search for, e.g. 'lego star wars'. Smyths
1416
+ # returns its best 240 matches at most - list a category to get every product of a range.
1417
+ # @param sort [String] Listing order. search: relevance, bestsellers, price_asc, price_desc,
1418
+ # rating_desc, newest (the first is the default). category: bestsellers, price_asc,
1419
+ # price_desc, rating_desc, newest (the first is the default).
1420
+ # @param page [Integer] 1-based listing page to start from (60 products per page). API
1421
+ # default: 1.
1422
+ # @param max_results [Integer] Products to collect across pages, 1-600. Each page fetched is
1423
+ # billed one unit. API default: 60.
1424
+ # Calls POST /marketplace/smyths-inventory.
1425
+ def smyths_search(query, sort: nil, page: nil, max_results: nil)
1426
+ post_json("/marketplace/smyths-inventory", {
1427
+ mode: "search",
1428
+ query: query,
1429
+ sort: sort,
1430
+ page: page,
1431
+ max_results: max_results,
1432
+ })
1433
+ end
1434
+
1435
+ # Smyths Toys UK product stock, price, details and listings.
1436
+ #
1437
+ # Smyths Toys UK (smythstoys.com) as JSON, in four modes. `mode=product` (the default when
1438
+ # `code` is given) returns one product's stock and price as the JSON Smyths' own storefront
1439
+ # reads (`/api/uk/en-gb/product/product-inventory`), unchanged: `code`, `type`, `prices`
1440
+ # (`price`, `value`, `wasPrice`, `thenPrice`, `rrpPrice`, `discountedPercentage`),
1441
+ # `hdSection` (home delivery: `stockStatus`, `stockMessage`, `isAvailableForDelivery`,
1442
+ # `isPreOrder`, `expectedStockDate`, delivery messages), `ccSection` (click & collect: the
1443
+ # same stock fields plus `storeName`, `deliveryMessage`, `storeClosed`), `ageSuitabilities`,
1444
+ # `carrierType`, marketing messages and decals. `code` takes a product code (`225095`), a
1445
+ # product or bundle page link or a product-inventory link; one product is one billed
1446
+ # request. `mode=details&code=` returns the JSON Smyths' product page is built from
1447
+ # (`/api/uk/en-gb/product`, a bundle's from `/api/uk/en-gb/b`), unchanged, one billed
1448
+ # request: `name`, `brand`, `ean`, `description` (HTML), `webFeatures` (the key features
1449
+ # list), the safety warnings `euWarning`, `safetyText`, `urgentSafetyTextHeading` and
1450
+ # `urgentSafetyTextDescription`, `productImages` (the gallery: `thumbnail`, `original` and
1451
+ # `zoom` per `galleryIndex`, videos as `youtubeUrl`), `prices`, `averageRating`,
1452
+ # `numberOfReviews`, `breadcrumb`, `categoryHierarchy`, `ageSuitabilities`,
1453
+ # `assemblyRequired` and more; a bundle adds `subProducts`. `url` and `breadcrumb` paths are
1454
+ # relative to https://www.smythstoys.com/uk/en-gb. `mode=search&query=` and
1455
+ # `mode=category&category=` find product codes: they return `products` (`code`, `name`,
1456
+ # `url`, `prices` with the same fields, `currency`, `image`, `averageRating`, `productType`
1457
+ # - `BUNDLE` for bundles - and `decals`) with `totalResults`, `totalPages` and `nextPage`,
1458
+ # 60 per page; a category also returns its `title`, `breadcrumbs` and `subcategories`
1459
+ # (`code`, `name`, `count`). Smyths' search returns its best 240 matches at most, a category
1460
+ # every product it holds. Listings collect up to `max_results` from `page` on, billed one
1461
+ # unit per page.
1462
+ #
1463
+ # @param category [String] mode=category: a category code ('SM060101') or a category page
1464
+ # link (https://www.smythstoys.com/uk/en-gb/toys/action-figures-and-playsets/c/SM060101).
1465
+ # The top categories are Toys SM0601, Baby SM0602, Outdoor SM0603 and Gaming & Tech SM0604;
1466
+ # each listing names its subcategories.
1467
+ # @param sort [String] Listing order. search: relevance, bestsellers, price_asc, price_desc,
1468
+ # rating_desc, newest (the first is the default). category: bestsellers, price_asc,
1469
+ # price_desc, rating_desc, newest (the first is the default).
1470
+ # @param page [Integer] 1-based listing page to start from (60 products per page). API
1471
+ # default: 1.
1472
+ # @param max_results [Integer] Products to collect across pages, 1-600. Each page fetched is
1473
+ # billed one unit. API default: 60.
1474
+ # Calls POST /marketplace/smyths-inventory.
1475
+ def smyths_category(category, sort: nil, page: nil, max_results: nil)
1476
+ post_json("/marketplace/smyths-inventory", {
1477
+ mode: "category",
1478
+ category: category,
1479
+ sort: sort,
1480
+ page: page,
1481
+ max_results: max_results,
1482
+ })
1483
+ end
1484
+
1139
1485
  # Scrape Booking.com hotels.
1140
1486
  #
1141
1487
  # Searches Booking.com for `location` and returns the properties as JSON: `name`, `price`
@@ -1418,11 +1764,13 @@ module ScrapeUnblocker
1418
1764
  # Scrape Google Shopping product listings.
1419
1765
  #
1420
1766
  # Runs the Google Shopping vertical (`udm=28`) for `q` and returns the product cards as
1421
- # JSON: `title`, `price`, `currency`, `originalPrice`, `discount`, `merchant`, `rating` and
1422
- # `reviewCount`. Set `proxy_country`/`gl` to target a market (prices/merchants are region-
1423
- # specific) and `min_price`/`max_price` to filter. One render returns the first page of
1424
- # products (v1). Note: Google Shopping exposes no per-product URL, so `url` is omitted and
1425
- # `image` is best-effort.
1767
+ # JSON: `title`, `price`, `currency`, `currencyCode`, `originalPrice`, `discount`,
1768
+ # `merchant`, `rating`, `reviewCount` and Google's own ids for the product (`productId`,
1769
+ # `catalogId`, `offerId` - stable across searches, for tracking one product). Set
1770
+ # `proxy_country`/`gl` to target a market (prices/merchants are region-specific) and
1771
+ # `min_price`/`max_price` to filter. One render returns the first page of products (v1).
1772
+ # Note: Google Shopping exposes no per-product URL, so `url` is omitted and `image` is best-
1773
+ # effort.
1426
1774
  #
1427
1775
  # @param q [String] Product search term, e.g. 'wireless headphones'.
1428
1776
  # @param gl [String] Market of search, ISO-2, e.g. 'us', 'de'. Sets prices/merchants. API
@@ -1559,6 +1907,208 @@ module ScrapeUnblocker
1559
1907
  })
1560
1908
  end
1561
1909
 
1910
+ # Scrape Google Hotels search results (prices, ratings, amenities).
1911
+ #
1912
+ # Runs a Google Hotels search for `q` and returns the properties as JSON: `name`, `type`
1913
+ # (hotel / vacation_rental), `propertyToken`, `price` for the stay (`perNight`,
1914
+ # `perNightWithTaxes`, `total`, `totalWithTaxes`, `taxes`, Google's `display` string),
1915
+ # `deal`, `rating`, `reviews`, `ratingsBreakdown`, `reviewTopics`, `hotelClass`, `stars`,
1916
+ # `amenities`, `description`, `website`, `latitude`/`longitude`, `nearbyPlaces`,
1917
+ # `thumbnail`, `images` and `mapsUrl`, plus the page's sponsored `ads`. A query naming one
1918
+ # hotel returns just that hotel (`matchedProperty`). Set the stay with
1919
+ # `check_in`/`check_out`, the party with `adults` and `children` (ages), and `currency`,
1920
+ # `hl` (language) and `gl` (market). `applied` echoes the dates, currency and place Google
1921
+ # actually used. Collect up to `max_results` across pages (each Google page is billed as one
1922
+ # request, see `pagesFetched`); `nextPageToken` continues the list. Pass a `propertyToken`
1923
+ # to `/travel/google-hotels-property` for every booking site's price.
1924
+ #
1925
+ # @param q [String] What to search, as on Google Hotels: a place ('hotels in Paris',
1926
+ # 'Lisbon'), an area or landmark, or a hotel name.
1927
+ # @param check_in [String] Check-in date, YYYY-MM-DD. Leave check_in and check_out empty for
1928
+ # Google's default stay.
1929
+ # @param check_out [String] Check-out date, YYYY-MM-DD (stays of up to 30 nights).
1930
+ # @param adults [Integer] Number of adults (1-10). API default: 2.
1931
+ # @param children [String] Ages of the children, comma-separated, e.g. '7,10' (up to 6).
1932
+ # @param currency [String] Price currency, ISO 4217 (USD, EUR, GBP, ...). API default: USD.
1933
+ # @param hl [String] Language of names, amenities and price formats, e.g. 'en', 'de', 'fr'.
1934
+ # API default: en.
1935
+ # @param gl [String] Market (2-letter country). Google localizes the booking sites to it.
1936
+ # API default: us.
1937
+ # @param page_token [String] `nextPageToken` of a previous response, to continue where it
1938
+ # stopped.
1939
+ # @param max_results [Integer] Properties to collect (1-100). Google serves about 20 per
1940
+ # page; each page is billed as one request. API default: 20.
1941
+ # Calls POST /travel/google-hotels.
1942
+ def google_hotels(q, check_in: nil, check_out: nil, adults: nil, children: nil, currency: nil, hl: nil, gl: nil, page_token: nil, max_results: nil)
1943
+ post_json("/travel/google-hotels", {
1944
+ q: q,
1945
+ check_in: check_in,
1946
+ check_out: check_out,
1947
+ adults: adults,
1948
+ children: children,
1949
+ currency: currency,
1950
+ hl: hl,
1951
+ gl: gl,
1952
+ page_token: page_token,
1953
+ max_results: max_results,
1954
+ })
1955
+ end
1956
+
1957
+ # Scrape one Google Hotels property: prices from every booking site.
1958
+ #
1959
+ # Returns one property from Google Hotels as JSON, identified by the `property_token` of a
1960
+ # `/travel/google-hotels` result: `offers` from every booking site Google lists for the stay
1961
+ # (`provider`, `official` for the hotel's own site, `perNight`/`perNightWithTaxes`,
1962
+ # `total`/`totalWithTaxes`, a Google `link` to book), `rooms` with room-level rates and
1963
+ # free-cancellation deadlines where Google shows them, the lowest `price`, `address`,
1964
+ # `phone`, `website`, `description` and `about`, `rating`, `reviews`, `ratingsBreakdown`,
1965
+ # `reviewTopics`, `hotelClass`, `latitude`/`longitude`, `nearbyPlaces`, `images`, `placeId`
1966
+ # and `mapsUrl`. Set the stay with `check_in`/`check_out`, the party with
1967
+ # `adults`/`children`, and `currency`, `hl`, `gl`. Billed as one request.
1968
+ #
1969
+ # @param property_token [String] `propertyToken` of a /travel/google-hotels result.
1970
+ # @param check_in [String] Check-in date, YYYY-MM-DD. Leave check_in and check_out empty for
1971
+ # Google's default stay.
1972
+ # @param check_out [String] Check-out date, YYYY-MM-DD (stays of up to 30 nights).
1973
+ # @param adults [Integer] Number of adults (1-10). API default: 2.
1974
+ # @param children [String] Ages of the children, comma-separated, e.g. '7,10' (up to 6).
1975
+ # @param currency [String] Price currency, ISO 4217 (USD, EUR, GBP, ...). API default: USD.
1976
+ # @param hl [String] Language, e.g. 'en', 'de', 'fr'. API default: en.
1977
+ # @param gl [String] Market (2-letter country). Google localizes the booking sites to it.
1978
+ # API default: us.
1979
+ # Calls POST /travel/google-hotels-property.
1980
+ def google_hotels_property(property_token, check_in: nil, check_out: nil, adults: nil, children: nil, currency: nil, hl: nil, gl: nil)
1981
+ post_json("/travel/google-hotels-property", {
1982
+ property_token: property_token,
1983
+ check_in: check_in,
1984
+ check_out: check_out,
1985
+ adults: adults,
1986
+ children: children,
1987
+ currency: currency,
1988
+ hl: hl,
1989
+ gl: gl,
1990
+ })
1991
+ end
1992
+
1993
+ # Scrape Google News (search or section).
1994
+ #
1995
+ # Searches Google News for `q`, or lists a section with `topic` (world, nation, business,
1996
+ # technology, entertainment, sports, science, health, top, or a topic id), and returns
1997
+ # `articles`: `position`, `title`, `url` (the publisher's article URL), `source`,
1998
+ # `sourceUrl`, `publishedAt` (ISO 8601 UTC), `imageUrl` (+ `imageWidth`/`imageHeight`),
1999
+ # `authors`, `googleNewsId` and `googleNewsUrl`. Metadata only - no article text. Narrow a
2000
+ # search with `when` (e.g. '1d') or `after`/`before` dates; pick the edition with `hl` and
2001
+ # `gl`. Google News answers a search with up to about 100 articles (no paging - use date
2002
+ # windows to go further back). One unit per call.
2003
+ #
2004
+ # @param q [String] Search query, e.g. 'tesla' or '"web scraping"'. Google News operators
2005
+ # work: quotes, OR, -word, site:reuters.com, intitle:. Pass either `q` or `topic`.
2006
+ # @param topic [String] A section instead of a search: world, nation, business, technology,
2007
+ # entertainment, sports, science, health, or top (the edition's headlines). A Google News
2008
+ # topic id (the 'CAAq...' part of a news.google.com/topics/ URL) also works.
2009
+ # @param hl [String] Language of the Google News edition, e.g. 'en', 'en-GB', 'de', 'fr',
2010
+ # 'pt-BR'. API default: en.
2011
+ # @param gl [String] Country of the Google News edition (2-letter code), e.g. 'us', 'gb',
2012
+ # 'de'. API default: us.
2013
+ # @param when [String] Only articles from the last period: '1h' to '720h', '1d', '7d', '2w',
2014
+ # '1m', '1y'. Search (`q`) only.
2015
+ # @param after [String] Only articles published after this date (YYYY-MM-DD). Search only.
2016
+ # @param before [String] Only articles published before this date (YYYY-MM-DD). Search only.
2017
+ # @param max_results [Integer] Most articles to return, 1-250. Google News returns up to
2018
+ # about 100 articles for a search and about 250 for a section; one call is billed one unit
2019
+ # whatever the count. API default: 100.
2020
+ # Calls POST /search/google-news.
2021
+ def google_news(q = nil, topic: nil, hl: nil, gl: nil, when: nil, after: nil, before: nil, max_results: nil)
2022
+ post_json("/search/google-news", {
2023
+ q: q,
2024
+ topic: topic,
2025
+ hl: hl,
2026
+ gl: gl,
2027
+ when: binding.local_variable_get(:when),
2028
+ after: after,
2029
+ before: before,
2030
+ max_results: max_results,
2031
+ })
2032
+ end
2033
+
2034
+ # Scrape Google ads (Ads Transparency Center).
2035
+ #
2036
+ # Returns an advertiser's Google ads (Search, YouTube, Display, Shopping, Maps, Play) from
2037
+ # the Google Ads Transparency Center for competitor analysis: per ad `creativeId`,
2038
+ # `advertiserId`/`advertiserName`, `format` (text/image/video), `firstShown`, `lastShown`,
2039
+ # `daysShown`, `matchedDomain`, the preview (`imageUrl` with size, or `previewUrl`),
2040
+ # `googleCreativeId`, `googleCustomerId`, `adGroupId` and `adUrl`. Pick ads by `advertiser`
2041
+ # (name or id) or `domain`; filter with `region`, `platform`, `format` and
2042
+ # `date_from`/`date_to`. `include_details` adds per-region reach and targeting,
2043
+ # `include_preview` the ad's texts and YouTube video. Collects up to `max_results` (continue
2044
+ # with `page_token`); billed one unit per page of up to 100 ads, plus one per detail and per
2045
+ # preview fetched (`callUnits`).
2046
+ #
2047
+ # @param advertiser [String] Advertiser to pull ads for: a name (e.g. 'Nike') - resolved to
2048
+ # the best-matching advertiser, reported in `advertisersUsed` (a single brand name no
2049
+ # advertiser is named like falls back to its domain) - or an advertiser id
2050
+ # ('AR16735076323512287233'). Comma-separate up to 10. Pass `advertiser` or `domain`.
2051
+ # @param domain [String] Website the ads point to, e.g. 'nike.com' (ads of every advertiser
2052
+ # for that domain).
2053
+ # @param region [String] Where the ads were shown: a 2-letter country code ('US', 'DE'), a
2054
+ # comma list of them, or 'anywhere' (default).
2055
+ # @param platform [String] Only ads shown on these platforms (comma list): search, youtube,
2056
+ # maps, play, shopping.
2057
+ # @param format [String] Only one ad format: text, image or video.
2058
+ # @param date_from [String] Only ads shown on or after this date (YYYY-MM-DD).
2059
+ # @param date_to [String] Only ads shown on or before this date (YYYY-MM-DD).
2060
+ # @param sort [String] Order of the ads: 'last_shown' (most recently shown first). API
2061
+ # default: last_shown.
2062
+ # @param max_results [Integer] Ads to collect, 1-1000. Pages of up to 100 are fetched as
2063
+ # needed; each page is billed one unit. API default: 100.
2064
+ # @param page_token [String] Continue a previous call: its `nextPageToken`.
2065
+ # @param include_details [Boolean] Add `details` per ad: regions shown with first/last date
2066
+ # (plus impressions per platform for EU regions), EU reach, disclosed targeting and all
2067
+ # preview variations. One unit per ad, for up to 300 ads per call. API default: false.
2068
+ # @param include_preview [Boolean] Add `preview` per ad: what it shows - headline,
2069
+ # description and display URL of text ads, product title and merchant of shopping ads, image
2070
+ # URLs, the YouTube video of video ads. One unit per ad that has a preview script, for up to
2071
+ # 40 ads per call. Search text ads often exist only as an image (`imageUrl`). API default:
2072
+ # false.
2073
+ # Calls POST /ads/google-ads-transparency.
2074
+ def google_ads_transparency(advertiser: nil, domain: nil, region: nil, platform: nil, format: nil, date_from: nil, date_to: nil, sort: nil, max_results: nil, page_token: nil, include_details: nil, include_preview: nil)
2075
+ post_json("/ads/google-ads-transparency", {
2076
+ advertiser: advertiser,
2077
+ domain: domain,
2078
+ region: region,
2079
+ platform: platform,
2080
+ format: format,
2081
+ date_from: date_from,
2082
+ date_to: date_to,
2083
+ sort: sort,
2084
+ max_results: max_results,
2085
+ page_token: page_token,
2086
+ include_details: include_details,
2087
+ include_preview: include_preview,
2088
+ })
2089
+ end
2090
+
2091
+ # Look up Google advertisers (Ads Transparency Center).
2092
+ #
2093
+ # Finds advertisers on the Google Ads Transparency Center by name: `advertisers`
2094
+ # (`advertiserId` to use with /ads/google-ads-transparency, `name`, `country`,
2095
+ # `creativesMin`/`creativesMax` - how many ads they ran - and `verified`) and matching
2096
+ # website `domains`. One unit per call.
2097
+ #
2098
+ # @param q [String] Advertiser name or brand to look up, e.g. 'nike'.
2099
+ # @param region [String] Count ads shown in these regions only: a 2-letter country code, a
2100
+ # comma list, or 'anywhere' (default).
2101
+ # @param max_results [Integer] Most advertisers (and domains) to return, 1-50. API default:
2102
+ # 10.
2103
+ # Calls POST /ads/google-ads-transparency-advertisers.
2104
+ def google_ads_transparency_advertisers(q, region: nil, max_results: nil)
2105
+ post_json("/ads/google-ads-transparency-advertisers", {
2106
+ q: q,
2107
+ region: region,
2108
+ max_results: max_results,
2109
+ })
2110
+ end
2111
+
1562
2112
  # Search Bing.
1563
2113
  #
1564
2114
  # Searches Bing for `keyword` in a real browser and returns the results page as JSON:
@@ -2575,6 +3125,68 @@ module ScrapeUnblocker
2575
3125
  })
2576
3126
  end
2577
3127
 
3128
+ # Scrape Upwork job posts.
3129
+ #
3130
+ # Searches Upwork jobs for `q` (or lists the newest jobs without it) and returns `jobs`:
3131
+ # `id`, `ciphertext`, `url`, `title`, `description` (full text), `publishedAt`, `createdAt`,
3132
+ # `renewedAt`, `jobType` (hourly / fixed), `hourlyMin`, `hourlyMax`, `budget` (fixed price),
3133
+ # `currency`, `experienceLevel`, `duration`, `workload`, `skills`, plus `totalResults` and
3134
+ # `nextStart`. Filter with `job_type`, `experience`, `budget`, `hourly_rate`,
3135
+ # `client_hires`, `workload`, `duration`, `location`, `category`, `contract_to_hire`; sort
3136
+ # by `relevance` or `recency`. `details=true` adds, per job: `proposals`, `interviewing`,
3137
+ # `invitesSent`, `hired`, `positionsToHire`, `lastClientActivity`, `category`,
3138
+ # `qualifications` and `client` (`country`, `city`, `timezone`, `paymentVerified`, `rating`,
3139
+ # `reviews`, `totalSpent`, `hires`, `memberSince`; no client names). A private listing is
3140
+ # marked `privateListing: true` without those fields. Upwork shows signed-out visitors the
3141
+ # first 5000 results of a search. Billing: one unit per results page of up to 50 jobs, plus
3142
+ # one per job page fetched with `details=true` (`callUnits`).
3143
+ #
3144
+ # @param q [String] What to search for, e.g. 'python' or 'logo design'. Leave empty with
3145
+ # sort=recency for the newest jobs.
3146
+ # @param job_type [String] hourly or fixed.
3147
+ # @param experience [String] Experience level, comma list of: entry, intermediate, expert.
3148
+ # @param budget [String] Fixed-price budget in USD, comma list of ranges like '100-499',
3149
+ # '1000-4999' or '5000-'.
3150
+ # @param hourly_rate [String] Hourly rate in USD, e.g. '25-50' or '40-'.
3151
+ # @param client_hires [String] Client's past hires, comma list of: 0, 1-9, 10-.
3152
+ # @param workload [String] part_time (under 30 hrs/week) or full_time.
3153
+ # @param duration [String] Project length, comma list of: week, month, semester, ongoing
3154
+ # (under a month, 1-3 months, 3-6 months, over 6 months).
3155
+ # @param location [String] Client location, comma list of regions or countries as Upwork
3156
+ # names them, e.g. 'Europe' or 'United States'.
3157
+ # @param category [String] Job category, comma list of names (Accounting & Consulting, Admin
3158
+ # Support, Customer Service, Data Science & Analytics, Design & Creative, Engineering &
3159
+ # Architecture, It & Networking, Legal, Sales & Marketing, Translation, Web, Mobile &
3160
+ # Software Dev, Writing) or category ids.
3161
+ # @param contract_to_hire [Boolean] Only contract-to-hire jobs. API default: false.
3162
+ # @param sort [String] relevance (default with q) or recency (default without q).
3163
+ # @param start [Integer] 0-based position of the first job, 0-4999 (Upwork shows signed-out
3164
+ # visitors the first 5000 results); `nextStart` continues. API default: 0.
3165
+ # @param max_results [Integer] Jobs to collect, 1-5000. Each results page (up to 50 jobs) is
3166
+ # billed one unit. API default: 50.
3167
+ # @param details [Boolean] Add each job's page: proposals, client stats, qualifications. Up
3168
+ # to 100 jobs; one more unit per job page fetched. API default: false.
3169
+ # Calls POST /jobs/upwork.
3170
+ def upwork_jobs(q = nil, job_type: nil, experience: nil, budget: nil, hourly_rate: nil, client_hires: nil, workload: nil, duration: nil, location: nil, category: nil, contract_to_hire: nil, sort: nil, start: nil, max_results: nil, details: nil)
3171
+ post_json("/jobs/upwork", {
3172
+ q: q,
3173
+ job_type: job_type,
3174
+ experience: experience,
3175
+ budget: budget,
3176
+ hourly_rate: hourly_rate,
3177
+ client_hires: client_hires,
3178
+ workload: workload,
3179
+ duration: duration,
3180
+ location: location,
3181
+ category: category,
3182
+ contract_to_hire: contract_to_hire,
3183
+ sort: sort,
3184
+ start: start,
3185
+ max_results: max_results,
3186
+ details: details,
3187
+ })
3188
+ end
3189
+
2578
3190
  # Scrape Trustpilot company reviews.
2579
3191
  #
2580
3192
  # Returns a company's Trustpilot profile and reviews as JSON. `company` is the reviewed
@@ -3313,7 +3925,7 @@ module ScrapeUnblocker
3313
3925
  # @param video [String] Video id (dQw4w9WgXcQ) or any YouTube video URL.
3314
3926
  # @param lang [String] Preferred caption language code, e.g. 'en'. Omit for the
3315
3927
  # default/first track. A prefix like 'en' matches 'en-US'.
3316
- # @param format [String] Output shape: segments, text. 'segments' returns timestamps; 'text'
3928
+ # @param format [String] Output shape: text, segments. 'segments' returns timestamps; 'text'
3317
3929
  # returns only the joined plaintext. API default: segments.
3318
3930
  # @param proxy_country [String] Proxy country the request is sent from (ISO-2, e.g. 'US',
3319
3931
  # 'DE') for the watch page. Defaults to US.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ScrapeUnblocker
4
- VERSION = "0.12.0"
4
+ VERSION = "0.14.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scrapeunblocker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ScrapeUnblocker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-15 00:00:00.000000000 Z
11
+ date: 2026-10-23 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: JS-rendered pages that bypass Cloudflare, DataDome, PerimeterX and Akamai,
14
14
  plus Google SERP and 100+ ready-made site plugins (Amazon, Walmart, Zillow, Google