Skip to content

Commit 73f3596

Browse files
author
stlc-bot
committed
feat(scrape): add product extraction format to POST /web/scrape (#1212)
Stainless-Generated-From: 0c94f72
1 parent 1f0f7af commit 73f3596

4 files changed

Lines changed: 158 additions & 22 deletions

File tree

‎src/context/dev/resources/web.py‎

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ def scrape(
397397
markdown_params: web_scrape_params.MarkdownParams | Omit = omit,
398398
max_age_ms: int | Omit = omit,
399399
parse_params: web_scrape_params.ParseParams | Omit = omit,
400+
product_params: web_scrape_params.ProductParams | Omit = omit,
400401
screenshot_params: web_scrape_params.ScreenshotParams | Omit = omit,
401402
shared_params: web_scrape_params.SharedParams | Omit = omit,
402403
tags: SequenceNotStr[str] | Omit = omit,
@@ -413,16 +414,18 @@ def scrape(
413414
visit.
414415
415416
Each cache key includes only the settings that affect that output. HTML
416-
is shared with Markdown, parsed fields, highlights, and JSON extraction. Cached
417-
outputs can come from different visits within maxAgeMs; use 0 for a fresh
418-
capture. HTML-only requests use the existing fast acquisition path. Highlights
419-
return the plain-text passages most relevant to highlightsParams.query. One
420-
credit per request, including cache hits and missing pages, or two with browser
421-
actions; highlights add 3 credits when passages are returned; JSON extraction
422-
adds four credits and runs an LLM over the page Markdown on every request that
423-
has text to extract; PDF OCR adds one credit per recovered page on fresh
424-
extraction. Original response bytes and screenshots are limited to 20 MiB each,
425-
screenshots to 40 megapixels, and the combined browser capture to 60 MiB.
417+
is shared with Markdown, parsed fields, product data, highlights, and JSON
418+
extraction. Cached outputs can come from different visits within maxAgeMs; use 0
419+
for a fresh capture. HTML-only requests use the existing fast acquisition path.
420+
Highlights return the plain-text passages most relevant to
421+
highlightsParams.query. One credit per request, including cache hits and missing
422+
pages, or two with browser actions; highlights add 3 credits when passages are
423+
returned; JSON extraction adds four credits and runs an LLM over the page
424+
Markdown on every request that has text to extract; PDF OCR adds one credit per
425+
recovered page on fresh extraction; the product output adds one credit, plus six
426+
more when the specialized model is used. Original response bytes and screenshots
427+
are limited to 20 MiB each, screenshots to 40 megapixels, and the combined
428+
browser capture to 60 MiB.
426429
427430
Args:
428431
formats: Outputs to return. Enable at least one; omitted formats are false.
@@ -444,6 +447,8 @@ def scrape(
444447
445448
parse_params: Required when formats.parse is true.
446449
450+
product_params: Product options. Requires formats.product: true.
451+
447452
screenshot_params: Screenshot options. Requires formats.screenshot: true.
448453
449454
shared_params: Shared browser and content settings. Content filters leave screenshots and
@@ -483,6 +488,7 @@ def scrape(
483488
"markdown_params": markdown_params,
484489
"max_age_ms": max_age_ms,
485490
"parse_params": parse_params,
491+
"product_params": product_params,
486492
"screenshot_params": screenshot_params,
487493
"shared_params": shared_params,
488494
"tags": tags,
@@ -1887,6 +1893,7 @@ async def scrape(
18871893
markdown_params: web_scrape_params.MarkdownParams | Omit = omit,
18881894
max_age_ms: int | Omit = omit,
18891895
parse_params: web_scrape_params.ParseParams | Omit = omit,
1896+
product_params: web_scrape_params.ProductParams | Omit = omit,
18901897
screenshot_params: web_scrape_params.ScreenshotParams | Omit = omit,
18911898
shared_params: web_scrape_params.SharedParams | Omit = omit,
18921899
tags: SequenceNotStr[str] | Omit = omit,
@@ -1903,16 +1910,18 @@ async def scrape(
19031910
visit.
19041911
19051912
Each cache key includes only the settings that affect that output. HTML
1906-
is shared with Markdown, parsed fields, highlights, and JSON extraction. Cached
1907-
outputs can come from different visits within maxAgeMs; use 0 for a fresh
1908-
capture. HTML-only requests use the existing fast acquisition path. Highlights
1909-
return the plain-text passages most relevant to highlightsParams.query. One
1910-
credit per request, including cache hits and missing pages, or two with browser
1911-
actions; highlights add 3 credits when passages are returned; JSON extraction
1912-
adds four credits and runs an LLM over the page Markdown on every request that
1913-
has text to extract; PDF OCR adds one credit per recovered page on fresh
1914-
extraction. Original response bytes and screenshots are limited to 20 MiB each,
1915-
screenshots to 40 megapixels, and the combined browser capture to 60 MiB.
1913+
is shared with Markdown, parsed fields, product data, highlights, and JSON
1914+
extraction. Cached outputs can come from different visits within maxAgeMs; use 0
1915+
for a fresh capture. HTML-only requests use the existing fast acquisition path.
1916+
Highlights return the plain-text passages most relevant to
1917+
highlightsParams.query. One credit per request, including cache hits and missing
1918+
pages, or two with browser actions; highlights add 3 credits when passages are
1919+
returned; JSON extraction adds four credits and runs an LLM over the page
1920+
Markdown on every request that has text to extract; PDF OCR adds one credit per
1921+
recovered page on fresh extraction; the product output adds one credit, plus six
1922+
more when the specialized model is used. Original response bytes and screenshots
1923+
are limited to 20 MiB each, screenshots to 40 megapixels, and the combined
1924+
browser capture to 60 MiB.
19161925
19171926
Args:
19181927
formats: Outputs to return. Enable at least one; omitted formats are false.
@@ -1934,6 +1943,8 @@ async def scrape(
19341943
19351944
parse_params: Required when formats.parse is true.
19361945
1946+
product_params: Product options. Requires formats.product: true.
1947+
19371948
screenshot_params: Screenshot options. Requires formats.screenshot: true.
19381949
19391950
shared_params: Shared browser and content settings. Content filters leave screenshots and
@@ -1973,6 +1984,7 @@ async def scrape(
19731984
"markdown_params": markdown_params,
19741985
"max_age_ms": max_age_ms,
19751986
"parse_params": parse_params,
1987+
"product_params": product_params,
19761988
"screenshot_params": screenshot_params,
19771989
"shared_params": shared_params,
19781990
"tags": tags,

‎src/context/dev/types/web_scrape_params.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"ParseParams",
1919
"ParseParamsRules",
2020
"ParseParamsRulesUnionMember1",
21+
"ProductParams",
2122
"ScreenshotParams",
2223
"ScreenshotParamsArea",
2324
"ScreenshotParamsAreaElement",
@@ -65,6 +66,9 @@ class WebScrapeParams(TypedDict, total=False):
6566
parse_params: Annotated[ParseParams, PropertyInfo(alias="parseParams")]
6667
"""Required when formats.parse is true."""
6768

69+
product_params: Annotated[ProductParams, PropertyInfo(alias="productParams")]
70+
"""Product options. Requires formats.product: true."""
71+
6872
screenshot_params: Annotated[ScreenshotParams, PropertyInfo(alias="screenshotParams")]
6973
"""Screenshot options. Requires formats.screenshot: true."""
7074

@@ -130,6 +134,9 @@ class Formats(TypedDict, total=False):
130134
parse: bool
131135
"""Fields selected by parseParams.rules."""
132136

137+
product: bool
138+
"""Structured product data for product detail pages. Adds one credit."""
139+
133140
screenshot: bool
134141
"""An inline image of the page."""
135142

@@ -210,6 +217,18 @@ class ParseParams(TypedDict, total=False):
210217
"""
211218

212219

220+
class ProductParams(TypedDict, total=False):
221+
"""Product options. Requires formats.product: true."""
222+
223+
use_ai_fallback: Annotated[bool, PropertyInfo(alias="useAIFallback")]
224+
"""
225+
Extract the product with a specialized model when the page has no structured
226+
product data. Adds six credits when the model returns a verdict. If the fallback
227+
fails, returns a partial response with the deterministic result and no fallback
228+
charge. Request deadlines and client disconnects still apply.
229+
"""
230+
231+
213232
class ScreenshotParamsAreaElement(TypedDict, total=False):
214233
selector: Required[str]
215234
"""Must match one visible element."""

‎src/context/dev/types/web_scrape_response.py‎

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
"MetadataAlternate",
2323
"MetadataHeading",
2424
"Parsed",
25+
"Product",
26+
"ProductData",
27+
"ProductDataProduct",
28+
"ProductDataProductVariant",
2529
"Screenshot",
2630
"KeyMetadata",
2731
]
@@ -222,6 +226,98 @@ class Parsed(BaseModel):
222226
requested: bool
223227

224228

229+
class ProductDataProductVariant(BaseModel):
230+
attributes: Dict[str, str]
231+
"""
232+
Explicit variant attributes such as color, size, material, pattern and
233+
properties declared by page.
234+
"""
235+
236+
images: List[str]
237+
"""Original source image URLs explicitly attached to this variant."""
238+
239+
sku: Optional[str] = None
240+
241+
url: Optional[str] = None
242+
"""Variant or offer URL when provided by the source. May be shared by variants."""
243+
244+
245+
class ProductDataProduct(BaseModel):
246+
"""The extracted product, or null when the page is not a product detail page."""
247+
248+
availability: Optional[
249+
Literal[
250+
"in_stock", "out_of_stock", "limited_availability", "preorder", "backorder", "made_to_order", "discontinued"
251+
]
252+
] = None
253+
"""Stock or ordering availability."""
254+
255+
brand: Optional[str] = None
256+
"""Brand or vendor."""
257+
258+
category: Optional[str] = None
259+
"""Product category."""
260+
261+
currency: Optional[str] = None
262+
"""ISO 4217 currency code."""
263+
264+
description: Optional[str] = None
265+
"""Product description."""
266+
267+
dimensions: List[str]
268+
"""Product dimensions as shown on the page."""
269+
270+
features: List[str]
271+
"""Key features and specifications."""
272+
273+
images: List[str]
274+
"""Product image URLs, main image first."""
275+
276+
image_url: Optional[str] = FieldInfo(alias="imageUrl", default=None)
277+
"""Main product image URL."""
278+
279+
name: str
280+
"""Product name."""
281+
282+
price: Optional[float] = None
283+
"""Current price."""
284+
285+
regular_price: Optional[float] = FieldInfo(alias="regularPrice", default=None)
286+
"""List price before any discount."""
287+
288+
sku: Optional[str] = None
289+
"""Product identifier such as a SKU or model number."""
290+
291+
tags: List[str]
292+
"""Product tags."""
293+
294+
target_audience: List[str] = FieldInfo(alias="targetAudience")
295+
"""Intended audience."""
296+
297+
variants: List[ProductDataProductVariant]
298+
"""
299+
Product variations, such as different colors or sizes, with their attributes and
300+
images. Empty if none are found. May not include every variation offered by the
301+
store.
302+
"""
303+
304+
305+
class ProductData(BaseModel):
306+
is_product_page: bool = FieldInfo(alias="isProductPage")
307+
"""Whether the page is a product detail page."""
308+
309+
product: Optional[ProductDataProduct] = None
310+
"""The extracted product, or null when the page is not a product detail page."""
311+
312+
313+
class Product(BaseModel):
314+
"""Product detail page classification and the extracted product."""
315+
316+
data: Optional[ProductData] = None
317+
318+
requested: bool
319+
320+
225321
class Screenshot(BaseModel):
226322
"""An image data URL. Use directly as an image src."""
227323

@@ -284,6 +380,9 @@ class WebScrapeResponse(BaseModel):
284380
parsed: Parsed
285381
"""Fields produced by parseParams.rules, after shared content filters."""
286382

383+
product: Product
384+
"""Product detail page classification and the extracted product."""
385+
287386
request_id: str
288387
"""Unique id of this API call, also sent in the X-Request-Id response header.
289388
@@ -298,8 +397,10 @@ class WebScrapeResponse(BaseModel):
298397

299398
is_partial: Optional[Literal[True]] = FieldInfo(alias="isPartial", default=None)
300399
"""
301-
Present when return-partial captures a page that is still loading or returns
302-
images before image processing finishes. Partial responses are not cached.
400+
Present when return-partial captures a page that is still loading, returns
401+
images before image processing finishes, or cuts product AI extraction short.
402+
Also present if the optional product AI fallback fails. Partial responses are
403+
not cached.
303404
"""
304405

305406
key_metadata: Optional[KeyMetadata] = None

‎tests/api_resources/test_web.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ def test_method_scrape_with_all_params(self, client: ContextDev) -> None:
248248
"json": True,
249249
"markdown": True,
250250
"parse": True,
251+
"product": True,
251252
"screenshot": True,
252253
},
253254
url="https://example.com",
@@ -284,6 +285,7 @@ def test_method_scrape_with_all_params(self, client: ContextDev) -> None:
284285
},
285286
}
286287
},
288+
product_params={"use_ai_fallback": True},
287289
screenshot_params={
288290
"area": "viewport",
289291
"format": "png",
@@ -783,6 +785,7 @@ async def test_method_scrape_with_all_params(self, async_client: AsyncContextDev
783785
"json": True,
784786
"markdown": True,
785787
"parse": True,
788+
"product": True,
786789
"screenshot": True,
787790
},
788791
url="https://example.com",
@@ -819,6 +822,7 @@ async def test_method_scrape_with_all_params(self, async_client: AsyncContextDev
819822
},
820823
}
821824
},
825+
product_params={"use_ai_fallback": True},
822826
screenshot_params={
823827
"area": "viewport",
824828
"format": "png",

0 commit comments

Comments
 (0)