Purpose
Enumerate and compare the professional product catalogs exposed by First Trust Global Portfolios for multiple country/localized paths. The catalog is represented by product links under .FundsTable__group; each product URL normally carries its ISIN in the isin_code query parameter.
Optionally inspect product detail pages to determine whether an SFDR classification is actually present in the rendered DOM, rather than treating a missing scraper field as a literal dash value.
When to Use
Use when verifying whether the same product universe is available in several country sites, identifying country-specific additions or omissions, or comparing product counts and ISIN coverage. Supply country path codes such as {country-codes} and compare the returned ISIN sets across locales.
Use the optional detail-page diagnostic when a static scraper reports - for SFDR on every product and you need to distinguish an absent/hidden field from a real displayed dash or an incorrect selector.
Workflow
- For each requested country code, navigate directly to:
https://www.ftglobalportfolios.com/{country-code}/professional/Products/ - If the site presents its consent/professional-investor gate, select the professional-investor option when shown, then select the visible
Acceptbutton. Wait briefly for the catalog to render. - On the loaded catalog page, run this self-contained evaluator:
(() => {
const links = [
...document.querySelectorAll('.FundsTable__group a[href*="/Products/"]'),
];
const products = [
...new Map(
links.map((a) => {
const href = (() => {
try {
return new URL(a.href, location.href).href;
} catch (_) {
return a.href;
}
})();
const url = (() => {
try {
return new URL(href);
} catch (_) {
return null;
}
})();
const isin = url ? (url.searchParams.get("isin_code") || "").trim() : "";
return [
href,
{
name: (a.textContent || "").replace(/\s+/g, " ").trim(),
url: href,
isin: isin || null,
},
];
}),
).values(),
];
const isins = [...new Set(products.map((p) => p.isin).filter(Boolean))];
return {
country: location.pathname.split("/").filter(Boolean)[0] || null,
url: location.href,
productCount: products.length,
uniqueIsinCount: isins.length,
products,
isins,
productsWithoutIsin: products
.filter((p) => !p.isin)
.map((p) => ({ name: p.name, url: p.url })),
};
})();Aggregate one evaluator result per country. Compare
isinsas sets, reporting the union, intersection, and each country’sonlyInCountryandmissingFromCountryvalues. Treat products without anisin_codeas separately reviewable rather than silently discarding them.Optionally diagnose SFDR for any product URL obtained from step 3. Navigate directly to the catalog-provided URL, preserving its
isin_codequery parameter; do not guess a product slug or opaque identifier. After the consent gate is accepted and the detail page has rendered, run this evaluator:
(() => {
const clean = (s) => (s || "").replace(/\s+/g, " ").trim();
const sets = [...document.querySelectorAll(".Values__set")];
const sfdrSets = sets.filter((set) =>
/SFDR\s*Classification/i.test(set.textContent || ""),
);
const exactLabels = [...document.querySelectorAll("*")].filter(
(el) =>
el.children.length === 0 &&
/^SFDR\s*Classification$/i.test(clean(el.textContent)),
);
const describe = (el) => {
const parent = el.parentElement;
const parentSet = el.closest(".Values__set");
const sibling = el.nextElementSibling;
const parentSibling = parent && parent.nextElementSibling;
return {
label: clean(el.textContent),
valueFromNextSibling: clean(sibling && sibling.textContent),
valueFromParentNextSibling: clean(parentSibling && parentSibling.textContent),
containerText: clean((parentSet || parent || el).textContent),
containerSelector: parentSet ? ".Values__set" : null,
};
};
return {
url: location.href,
isin: new URL(location.href).searchParams.get("isin_code"),
valuesSetCount: sets.length,
sfdrContainerCount: sfdrSets.length,
exactLabelCount: exactLabels.length,
sfdrContainers: sfdrSets.map((set) => clean(set.textContent)),
exactLabelMatches: exactLabels.map(describe),
bodyContainsSfdr: /SFDR\s*Classification/i.test(document.body.innerText || ""),
};
})();Interpret the result as follows: bodyContainsSfdr: false means the rendered page does not expose the field to a DOM-only static scraper; bodyContainsSfdr: true with zero .Values__set matches indicates the scraper’s container selector is wrong; a matching container or exact label with an empty sibling value indicates a structure/value-extraction issue; and an explicitly returned - in the container text is a genuine displayed dash. Repeat on representative products/locales before changing the scraper globally.
Site-Specific Gotchas
- The localized country code is the first path segment, while the professional catalog path remains
/professional/Products/; do not assume the Luxembourg path applies to every country. - A consent/investor modal can prevent the catalog from rendering. The button text is localized; the German flow exposed
Professioneller AnlegerbeforeAccept, while other locales may expose a different professional-investor label. - Product rows can contain duplicate links, so deduplicate by absolute URL before counting. Use the
isin_codequery parameter for cross-country identity rather than product-link text or slug. - Wait for the catalog after dismissing the modal before extracting
.FundsTable__grouplinks; an early extraction can return an incomplete or empty table. - Product detail pages are reached with the catalog-provided product URL and its
isin_codequery parameter. Do not fabricate opaque product identifiers from names. - For SFDR diagnosis, search both the visible body text and
.Values__setcontainers. A selector that assumes every field is a.Values__setentry can report dashes when the label is absent, rendered elsewhere, or not exposed in the static DOM. - The catalog selector is based on the observed
FundsTable__groupstructure and the detail diagnostic uses observed.Values__setmarkup; both should be rechecked if the site redesigns its markup.
Expected Output
For every requested country, return the country code, final URL, deduplicated product records (name, url, isin), product count, unique ISIN count, and products lacking an ISIN. Across countries, return set-based coverage differences and flag any count mismatch or missing-ISIN records.
For optional SFDR checks, return the product URL and ISIN, whether SFDR text is present in the body, counts of matching .Values__set containers and exact labels, the matched container text, and neighboring value candidates. State whether the dash is a real displayed value, an absent field, or a selector/DOM-structure mismatch.