TP Bennett Recent Projects Scraper
Purpose
Return tp bennett's recent architecture / interior-design projects, each enriched with who is involved (the named tp bennett project lead plus the client) and what is being done (sector, project stage/status, location, size, and a narrative summary). tpbennett.com is a Next.js site whose content is served from the Prismic headless CMS, which exposes an unauthenticated public JSON API. Prefer that API — it returns the identical content the site renders, structured, in one request, with no auth, cookies, proxies, or anti-bot stealth. Read-only; never writes.
When to Use
- Monitoring tp bennett's latest completed and in-progress projects.
- Building a feed of the practice's work with structured metadata (client, sector, location, status) rather than scraping rendered HTML.
- Enriching a project list with the responsible tp bennett director/principal and a short description of the scheme.
- Any flow that would otherwise scrape
https://www.tpbennett.com/work/— the Prismic API is faster, cheaper, and structurally reliable.
Workflow
The site's content lives in the Prismic repository tp-bennett. Its public Content API v2 is at https://tp-bennett.cdn.prismic.io/api/v2 — no auth, no cookies, no proxy, no stealth. Individual projects are documents of type work_item. Lead with the API; a browser fallback is documented at the end but costs ~100× more (the pages are fully JS-rendered).
-
Get the master ref. Refs rotate on every content publish, so fetch a fresh one each run:
GET https://tp-bennett.cdn.prismic.io/api/v2Read
refs[]and take the entry whereisMasterRef === true(e.g.anxqLBIAAC0AuWDa). -
List recent projects (ordered most-recent-first). URL-encode the query; brackets/quotes/spaces must be encoded:
GET https://tp-bennett.cdn.prismic.io/api/v2/documents/search ?ref=<REF> &q=[[at(document.type,"work_item")]] &orderings=[document.first_publication_date desc] &pageSize=100Returns
total_results_size(12 today, single page) andresults[]. Usedocument.last_publication_date descinstead if you want "recently updated" rather than "recently added".Optionally, the site's own curated recent strip (what the
/work/landing page shows) is theproject_groupdocument withuid=work-recent:&q=[[at(document.type,"project_group")]]Its
data.projects[]is an ordered list ofwork_itemcontent-relationships (currently11-12-wellington-place,capital-group,dockside-canada-water). The embedded copy only carriesname/title/type/colour/media— fetch the fullwork_item(step 3) for the enrichment fields. -
Extract enrichment from each
work_item(results[i].data):- Identity / "what":
name(project name),title(editorial headline),type(sector, e.g.WORKPLACE,COMMERCIAL, RETROFIT,LIVING),media.url(hero image). - Structured facts: the slice with
slice_type === "project_info"→primary.info[]is an array of{ heading, text }rows. Observed headings: Client, Location, Size, Status, Sector, Discipline. Alsoprimary.content[](rich-text paragraphs — the project overview). - "Who is involved":
project_info.primary.quote_author— a named tp bennett lead with role, e.g."Mark Davies, Director"/"Emma Green, Principal Director". Pair with theClientinfo row for the full "who".quote_authoris null on some projects (e.g. masterplans) — treat as optional. - Narrative / "what is being done": slices with
slice_type === "header_paragraph"→primary.heading+primary.paragraph[](each project has 1–2). These describe the scheme ("A new base for the company's main trading operations…"). - Flatten Prismic rich-text arrays by joining each block's
.text.
- Identity / "what":
-
Build the canonical page URL for each project:
https://www.tpbennett.com/work/<uid>/(e.g.https://www.tpbennett.com/work/bp-london/). -
Emit the project list (see Expected Output). One page of results covers the full set today; paginate via
&page=Nonly iftotal_pages > 1.
Browser fallback (only if the Prismic API is unavailable)
browse open https://www.tpbennett.com/work/ --remotebrowse wait loadthenbrowse wait timeout 3000— the grid is client-rendered and populates 1–3s after load.- Collect each project link (
/work/<uid>/), then for each:browse openthe detail page,wait load+wait timeout 3000, andbrowse get text body. The rendered detail page carries the same fields (client, location, size, status, discipline, the named lead, and the overview copy). A bare (no proxy / no stealth) session is sufficient.
Site-Specific Gotchas
- The whole site is a Prismic-backed Next.js app. Repo id
tp-bennettis discoverable from the homepage's inline Prismic script +images.prismic.io/static.cdn.prismic.ioin the CSP. The API needs no auth, no cookies, no proxies, no--verified— a plain HTTP GET returns 200. - The ref expires.
refs[0].refchanges on every content publish. Always fetch/api/v2first and use the current master ref; a stale ref returns an error. Don't hardcode it. - Projects are
work_item, notwork. Theworkdocument type is the single/work/index page (a bag of layout slices), not the project list. Queryingdocument.type == "work"returns 1 doc with no projects. project_groupwork-recentis the site's curated "recent" set (3 projects, hand-ordered). Querying allwork_itemordered byfirst_publication_date descgives the fuller recency-ordered list (12). Pick whichever "recent" you mean; this skill returns the full list and flags the curated subset.- "Who is involved" comes from within the project, not a team join. There is a
project_team_membertype (16 leadership/people docs), but itsrelated_articlesfield does not reliably link members to specific projects (empty document links). Per-project attribution isproject_info.quote_author(named tp bennett lead) + theClientinfo row. Don't waste time trying to joinproject_team_member→work_item. quote_authorcan be null (e.g. masterplan/placemaking projects likeal-jabal-al-aali,33-canada-square). Emitlead: nullrather than failing.info[]headings are data, not a fixed schema. Read them dynamically (Client,Location,Size,Status,Sector,Disciplineare what's observed today) — don't assume positional order.type(sector) is free-text and sometimes compound — e.g."COMMERCIAL, RETROFIT","PLACEMAKING, LIFESTYLE". Casing is inconsistent (WORKPLACEvsWorkplacevsCommercial). Normalize if you need canonical sectors.- Prismic image URLs have a doubled query string (
...?auto=format,compress?auto=compress,format). It's harmless and served correctly — pass through as-is. tpbennett.com308-redirects towww.tpbennett.com; the API hosttp-bennett.cdn.prismic.iodoes not redirect.- Pre-run anti-bot probe: none detected, and the successful API run required neither proxies nor a verified/stealth session. Only reach for the browser fallback (which does need
wait load+wait timeout 3000on the JS-rendered grid) if the Prismic API is ever down. - Browser extraction is ~100× the cost of the API for identical data — the autobrowse inner agent, given only the task description, independently chose the Prismic API over scraping.
Expected Output
{
"success": true,
"source": "prismic-api",
"repository": "tp-bennett",
"ref": "anxqLBIAAC0AuWDa",
"count": 12,
"curated_recent_uids": ["11-12-wellington-place", "capital-group", "dockside-canada-water"],
"projects": [
{
"uid": "bp-london",
"name": "BP London",
"title": "Rethinking workplace inclusion",
"sector": "WORKPLACE",
"client": "BP",
"location": "London, UK",
"size": "253,000 sq ft",
"status": "Complete",
"discipline": "Interior Design",
"lead": "Mark Davies, Director",
"summary": "A new base for the company's main trading operations, spanning eight floors of the Cargo building in Canary Wharf; a fit-out designed around inclusivity and varied working styles.",
"hero_image": "https://images.prismic.io/tp-bennett/aiBBSAeQX7-eWqnL_bp-hero-1-.jpg?auto=format,compress",
"url": "https://www.tpbennett.com/work/bp-london/",
"first_publication_date": "2026-06-03T15:05:06+0000",
"last_publication_date": "2026-06-26T09:00:00+0000"
},
{
"uid": "al-jabal-al-aali",
"name": "Al Jabal Al Aali",
"title": null,
"sector": "PLACEMAKING, LIFESTYLE",
"client": "Ministry of Housing and Urban Planning",
"location": "Jabal Al Akdar, Oman",
"size": null,
"status": "Detailed Masterplan",
"discipline": null,
"lead": null,
"summary": "...",
"hero_image": "https://images.prismic.io/tp-bennett/...jpg",
"url": "https://www.tpbennett.com/work/al-jabal-al-aali/",
"first_publication_date": "2026-06-03T...",
"last_publication_date": "2026-06-...T..."
}
]
}
Failure shape (e.g. Prismic API unreachable and browser fallback also blocked):
{ "success": false, "source": "prismic-api", "error_reasoning": "Could not fetch master ref from https://tp-bennett.cdn.prismic.io/api/v2 (HTTP 5xx)." }