Provide a direct, reusable scraper for Brookstone ETF product pages, including the page title, ticker and other labeled fund fields, data-block contents, objectives, listing information, and fund-document URLs.
Use Cases
Use when the caller has a Brookstone product-page URL or its path slug and needs a structured snapshot of the ETF page. Substitute {product-slug} with the supplied path segment; do not guess an unknown slug from a fund name.
Automation Flow
- Build the product URL as
https://www.brookstoneam.com/{product-slug}. For a URL already supplied by the caller, use it directly. - In one browser call, navigate to that URL with
waitUntil: "domcontentloaded", then run thisevaluate()function on the loaded page:
(() => {
const clean = (s) => (s || "").replace(/\s+/g, " ").trim();
const text = (el) => clean(el ? el.innerText || el.textContent : "");
const first = (sels) => {
for (const s of sels) {
const el = document.querySelector(s);
if (el && text(el)) return text(el);
}
return null;
};
const sectionByHeading = (heading) => {
const hs = [...document.querySelectorAll("h2,h3,h4")].filter(
(h) => clean(h.textContent).toLowerCase() === heading.toLowerCase(),
);
if (!hs.length) return null;
let node = hs[0];
for (let i = 0; i < 5 && node; i++, node = node.parentElement) {
if (text(node).length > text(hs[0]).length) return text(node);
}
return text(hs[0].parentElement);
};
const blockEls = [
...document.querySelectorAll(
'.data-block-2col, .data-block.light, [class*="data-block"]',
),
];
const seen = new Set();
const dataBlocks = blockEls
.map((el) => ({
className: String(el.className || ""),
text: text(el),
}))
.filter((x) => x.text && !seen.has(x.text) && (seen.add(x.text), true));
const fields = {};
document.querySelectorAll(".text-style-tagline").forEach((label) => {
const key = clean(label.textContent);
if (!key || fields[key]) return;
const parent = label.parentElement;
if (!parent) return;
const clone = parent.cloneNode(true);
clone.querySelectorAll(".text-style-tagline").forEach((n) => n.remove());
const value = text(clone);
if (value) fields[key] = value;
});
const documentHeading = [...document.querySelectorAll("h2,h3,h4")].find(
(h) => clean(h.textContent).toLowerCase() === "fund documents",
);
let documentRoot = documentHeading;
for (
let i = 0;
i < 5 && documentRoot;
i++, documentRoot = documentRoot.parentElement
) {
if (
documentRoot.querySelector &&
documentRoot.querySelectorAll("a[href]").length
)
break;
}
const fundDocuments = documentRoot
? [...documentRoot.querySelectorAll("a[href]")]
.map((a) => ({ text: text(a), href: a.href }))
.filter((x) => x.text || x.href)
: [];
return {
url: location.href,
title: first(["h1"]) || document.title,
ticker: fields.Ticker || first(["[data-ticker]"]),
fields,
sections: {
objective: sectionByHeading("Objective"),
etfData: sectionByHeading("ETF Data"),
listingInformation: sectionByHeading("Listing Information"),
fundDocuments: sectionByHeading("Fund Documents"),
},
dataBlocks,
fundDocuments,
};
})();Possible Friction Points
- Brookstone product pages use path-based URLs such as
/brookstone-dividend-stock-etf; the product slug is not an opaque numeric ID, but an unprovided slug must still come from the caller or a prior site search rather than being invented. - Important content is organized under headings such as
ETF Data,Listing Information, andFund Documents; document links should be collected from the heading's nearby ancestor rather than from the entire page. - Labeled metadata uses the Webflow-style
.text-style-taglineclass, while larger metadata groups commonly use.data-block-2col,.data-block.light, or another class containingdata-block. The extractor includes fallbacks because these classes are presentation-oriented and may vary between templates. - Arlington Asset Management is a separate domain encountered during the run. Do not substitute its homepage for the Brookstone product URL when scraping this site.