mcp-scraper 0.6.0 → 0.8.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.
- package/dist/bin/api-server.cjs +3771 -3613
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +1 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +3 -3
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +4 -4
- package/dist/bin/paa-harvest.cjs +166 -74
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +1 -1
- package/dist/chunk-3MKSXDJ7.js +7 -0
- package/dist/chunk-3MKSXDJ7.js.map +1 -0
- package/dist/{chunk-APJO2XV5.js → chunk-HL33CGJF.js} +3 -3
- package/dist/{chunk-APJO2XV5.js.map → chunk-HL33CGJF.js.map} +1 -1
- package/dist/{chunk-3K5GT6VG.js → chunk-ITJC3NN4.js} +5 -4
- package/dist/{chunk-GWRIO6JT.js → chunk-R7EETU7Z.js} +134 -134
- package/dist/chunk-R7EETU7Z.js.map +1 -0
- package/dist/{chunk-AUCXKRRH.js → chunk-RMPPYKUV.js} +167 -75
- package/dist/chunk-RMPPYKUV.js.map +1 -0
- package/dist/{chunk-BL7BBSYF.js → chunk-RUGJE5EB.js} +2 -2
- package/dist/{extract-bundle-COS56ZDO.js → extract-bundle-U4D5LW5W.js} +4 -2
- package/dist/extract-bundle-U4D5LW5W.js.map +1 -0
- package/dist/index.cjs +166 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{server-WHM446IZ.js → server-WBDVVHR6.js} +151 -96
- package/dist/server-WBDVVHR6.js.map +1 -0
- package/dist/{site-extract-repository-U476J44K.js → site-extract-repository-NVSZH35Y.js} +3 -3
- package/dist/{worker-KJZ3ZN2N.js → worker-JQTS437L.js} +4 -4
- package/dist/worker-JQTS437L.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-AUCXKRRH.js.map +0 -1
- package/dist/chunk-F2BXETDX.js +0 -7
- package/dist/chunk-F2BXETDX.js.map +0 -1
- package/dist/chunk-GWRIO6JT.js.map +0 -1
- package/dist/extract-bundle-COS56ZDO.js.map +0 -1
- package/dist/server-WHM446IZ.js.map +0 -1
- package/dist/worker-KJZ3ZN2N.js.map +0 -1
- package/docs/spec-browser-routing.md +0 -108
- package/docs/spec-kernel-computer-controls.md +0 -166
- package/docs/spec-mcp-tools.md +0 -146
- package/docs/spec-server-instructions.md +0 -94
- package/docs/specs/agent-ready-seo-packet-spec.md +0 -237
- package/docs/specs/audit-visual-demo.md +0 -309
- package/docs/specs/cli-agent-wiring-spec.md +0 -206
- package/docs/specs/local-competitive-audit-spec.md +0 -311
- package/docs/specs/scheduled-workflows-api-spec.md +0 -304
- package/docs/specs/seo-cli-growth-roadmap-spec.md +0 -181
- package/docs/specs/seo-workflow-runner-and-reports-spec.md +0 -241
- /package/dist/{chunk-3K5GT6VG.js.map → chunk-ITJC3NN4.js.map} +0 -0
- /package/dist/{chunk-BL7BBSYF.js.map → chunk-RUGJE5EB.js.map} +0 -0
- /package/dist/{site-extract-repository-U476J44K.js.map → site-extract-repository-NVSZH35Y.js.map} +0 -0
|
@@ -1516,13 +1516,41 @@ var PAAExtractor = class {
|
|
|
1516
1516
|
}
|
|
1517
1517
|
return parts.join(" ").replace(/\s+/g, " ").trim();
|
|
1518
1518
|
}
|
|
1519
|
-
return Array.from(document.querySelectorAll(selectors.item)).map((pair) =>
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1519
|
+
return Array.from(document.querySelectorAll(selectors.item)).map((pair) => {
|
|
1520
|
+
let sourceTitle = pair.querySelector(selectors.sourceTitle)?.innerText?.trim() || void 0;
|
|
1521
|
+
let sourceSite = pair.querySelector(selectors.sourceSite)?.innerText?.trim() || void 0;
|
|
1522
|
+
let sourceCite = pair.querySelector(selectors.sourceCite)?.innerText?.trim() || void 0;
|
|
1523
|
+
if (!sourceCite) {
|
|
1524
|
+
const seenHrefs = /* @__PURE__ */ new Set();
|
|
1525
|
+
const anchors = [];
|
|
1526
|
+
for (const a of Array.from(pair.querySelectorAll("a[href]"))) {
|
|
1527
|
+
const href = a.href;
|
|
1528
|
+
if (!/^https?:\/\//.test(href) || seenHrefs.has(href)) continue;
|
|
1529
|
+
let host = "";
|
|
1530
|
+
try {
|
|
1531
|
+
host = new URL(href).hostname;
|
|
1532
|
+
} catch {
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
1535
|
+
if (/(^|\.)google(usercontent)?\.[a-z.]+$/.test(host)) continue;
|
|
1536
|
+
seenHrefs.add(href);
|
|
1537
|
+
anchors.push({ text: (a.textContent || "").trim(), href, host });
|
|
1538
|
+
}
|
|
1539
|
+
const primary = anchors.find((x) => x.text.length > 2) ?? anchors[0];
|
|
1540
|
+
if (primary) {
|
|
1541
|
+
sourceTitle = sourceTitle || primary.text || void 0;
|
|
1542
|
+
sourceSite = sourceSite || primary.host.replace(/^www\./, "");
|
|
1543
|
+
sourceCite = primary.href;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
return {
|
|
1547
|
+
question: pair.getAttribute(selectors.itemDataQ) || pair.getAttribute(selectors.itemDataInitQ) || pair.querySelector(selectors.itemQuestionEl)?.innerText?.trim() || "",
|
|
1548
|
+
answer: cleanText(pair.querySelector(selectors.answerContainer)) || void 0,
|
|
1549
|
+
sourceTitle,
|
|
1550
|
+
sourceSite,
|
|
1551
|
+
sourceCite
|
|
1552
|
+
};
|
|
1553
|
+
});
|
|
1526
1554
|
}, sels);
|
|
1527
1555
|
return raw.flatMap((item) => {
|
|
1528
1556
|
const cleaned = {
|
|
@@ -1564,54 +1592,115 @@ var PAAExtractor = class {
|
|
|
1564
1592
|
const seenQs = /* @__PURE__ */ new Set();
|
|
1565
1593
|
const orderedQs = [];
|
|
1566
1594
|
const results = [];
|
|
1567
|
-
const
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1595
|
+
const abandonedNeverReclick = /* @__PURE__ */ new Set();
|
|
1596
|
+
const clickedOnceEver = /* @__PURE__ */ new Set();
|
|
1597
|
+
const itemArgs = {
|
|
1598
|
+
sel: PAASelectors.item,
|
|
1599
|
+
dataQ: PAASelectors.itemDataQ,
|
|
1600
|
+
dataInitQ: PAASelectors.itemDataInitQ,
|
|
1601
|
+
questionEl: PAASelectors.itemQuestionEl,
|
|
1602
|
+
answerSel: PAASelectors.answerContainer,
|
|
1603
|
+
expandedClass: PAASelectors.expandedClass,
|
|
1604
|
+
clickTarget: PAASelectors.clickTarget
|
|
1605
|
+
};
|
|
1606
|
+
const readItemStates = () => page.evaluate(
|
|
1607
|
+
({ sel, dataQ, dataInitQ, questionEl, expandedClass, answerSel }) => {
|
|
1608
|
+
const answerText = (root) => {
|
|
1609
|
+
if (!root) return "";
|
|
1610
|
+
let out = "";
|
|
1611
|
+
const walk = (n) => {
|
|
1612
|
+
for (const c of Array.from(n.childNodes)) {
|
|
1613
|
+
if (c.nodeType === Node.TEXT_NODE) out += c.textContent || "";
|
|
1614
|
+
else if (c.nodeType === Node.ELEMENT_NODE && c.tagName !== "STYLE" && c.tagName !== "SCRIPT") walk(c);
|
|
1615
|
+
}
|
|
1616
|
+
};
|
|
1617
|
+
walk(root);
|
|
1618
|
+
return out.replace(/\s+/g, " ").trim();
|
|
1619
|
+
};
|
|
1620
|
+
return Array.from(document.querySelectorAll(sel)).map((el) => ({
|
|
1621
|
+
q: el.getAttribute(dataQ) || el.getAttribute(dataInitQ) || el.querySelector(questionEl)?.innerText?.trim() || "",
|
|
1622
|
+
done: el.classList.contains(expandedClass) || answerText(el.querySelector(answerSel)).length > 30
|
|
1623
|
+
})).filter((s) => s.q);
|
|
1624
|
+
},
|
|
1625
|
+
itemArgs
|
|
1572
1626
|
);
|
|
1627
|
+
const expandOneItemSerially = async (q) => {
|
|
1628
|
+
const clicked = await page.evaluate(
|
|
1629
|
+
({ sel, dataQ, dataInitQ, clickTarget, q: target }) => {
|
|
1630
|
+
const item = Array.from(document.querySelectorAll(sel)).find((el) => (el.getAttribute(dataQ) || el.getAttribute(dataInitQ)) === target);
|
|
1631
|
+
const btn = item?.querySelector(clickTarget);
|
|
1632
|
+
if (!btn) return false;
|
|
1633
|
+
btn.scrollIntoView({ block: "center" });
|
|
1634
|
+
btn.click();
|
|
1635
|
+
return true;
|
|
1636
|
+
},
|
|
1637
|
+
{ ...itemArgs, q }
|
|
1638
|
+
).catch(() => false);
|
|
1639
|
+
if (!clicked) return "failed";
|
|
1640
|
+
try {
|
|
1641
|
+
const outcome = await page.waitForFunction(
|
|
1642
|
+
({ sel, dataQ, dataInitQ, answerSel, q: target }) => {
|
|
1643
|
+
const answerText = (root) => {
|
|
1644
|
+
if (!root) return "";
|
|
1645
|
+
let out = "";
|
|
1646
|
+
const walk = (n) => {
|
|
1647
|
+
for (const c of Array.from(n.childNodes)) {
|
|
1648
|
+
if (c.nodeType === Node.TEXT_NODE) out += c.textContent || "";
|
|
1649
|
+
else if (c.nodeType === Node.ELEMENT_NODE && c.tagName !== "STYLE" && c.tagName !== "SCRIPT") walk(c);
|
|
1650
|
+
}
|
|
1651
|
+
};
|
|
1652
|
+
walk(root);
|
|
1653
|
+
return out.replace(/\s+/g, " ").trim();
|
|
1654
|
+
};
|
|
1655
|
+
const item = Array.from(document.querySelectorAll(sel)).find((el) => (el.getAttribute(dataQ) || el.getAttribute(dataInitQ)) === target);
|
|
1656
|
+
if (!item) return "gone";
|
|
1657
|
+
const text = answerText(item.querySelector(answerSel));
|
|
1658
|
+
if (/an error has occurred/i.test(text.slice(0, 80))) return "error";
|
|
1659
|
+
if (text.length > 30) return "ok";
|
|
1660
|
+
return false;
|
|
1661
|
+
},
|
|
1662
|
+
{ ...itemArgs, q },
|
|
1663
|
+
{ timeout: 6500, polling: 200 }
|
|
1664
|
+
);
|
|
1665
|
+
return await outcome.jsonValue() === "ok" ? "ok" : "failed";
|
|
1666
|
+
} catch {
|
|
1667
|
+
return "failed";
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1573
1670
|
let round = 0;
|
|
1574
|
-
|
|
1671
|
+
let growthWaits = 0;
|
|
1672
|
+
while (true) {
|
|
1575
1673
|
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
1576
1674
|
this.throwIfAborted(signal);
|
|
1577
|
-
|
|
1578
|
-
const
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
if (unexpandedCount === 0) break;
|
|
1583
|
-
this.reporter.onDepth(++round);
|
|
1584
|
-
for (let ci = 0; ci < unexpandedCount; ci++) {
|
|
1585
|
-
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
1586
|
-
this.throwIfAborted(signal);
|
|
1587
|
-
try {
|
|
1588
|
-
const btn = page.locator(unexpandedSel).first();
|
|
1589
|
-
await btn.scrollIntoViewIfNeeded();
|
|
1590
|
-
await btn.hover({ force: true });
|
|
1591
|
-
await page.waitForTimeout(100);
|
|
1592
|
-
await btn.click({ force: true });
|
|
1593
|
-
await page.waitForTimeout(500);
|
|
1594
|
-
} catch {
|
|
1675
|
+
const states = await readItemStates();
|
|
1676
|
+
for (const s of states) {
|
|
1677
|
+
if (!seenQs.has(s.q)) {
|
|
1678
|
+
seenQs.add(s.q);
|
|
1679
|
+
orderedQs.push(s.q);
|
|
1595
1680
|
}
|
|
1596
1681
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
}
|
|
1682
|
+
const kept = new Set(orderedQs.slice(0, options.maxQuestions));
|
|
1683
|
+
const clickable = states.filter((s) => kept.has(s.q) && !clickedOnceEver.has(s.q) && !abandonedNeverReclick.has(s.q));
|
|
1684
|
+
const target = clickable.find((s) => !s.done) ?? (seenQs.size < options.maxQuestions ? clickable[0] : void 0);
|
|
1685
|
+
if (!target) {
|
|
1686
|
+
if (seenQs.size >= options.maxQuestions) break;
|
|
1687
|
+
if (++growthWaits > 1) break;
|
|
1688
|
+
const grew = await page.waitForFunction(
|
|
1689
|
+
({ sel, min }) => document.querySelectorAll(sel).length > min,
|
|
1690
|
+
{ sel: PAASelectors.item, min: states.length },
|
|
1691
|
+
{ timeout: 3e3 }
|
|
1692
|
+
).catch(() => null);
|
|
1693
|
+
if (!grew) break;
|
|
1694
|
+
continue;
|
|
1611
1695
|
}
|
|
1696
|
+
growthWaits = 0;
|
|
1697
|
+
this.reporter.onDepth(++round);
|
|
1698
|
+
await this.throwIfCaptcha(page, "Google PAA expansion");
|
|
1699
|
+
clickedOnceEver.add(target.q);
|
|
1700
|
+
if (await expandOneItemSerially(target.q) === "failed") abandonedNeverReclick.add(target.q);
|
|
1612
1701
|
}
|
|
1613
1702
|
const itemMap = new Map((await this.extractVisibleItems(page)).map((i) => [i.question, i]));
|
|
1614
|
-
await this.fillIncompleteItems(page, orderedQs, itemMap, options);
|
|
1703
|
+
await this.fillIncompleteItems(page, orderedQs, itemMap, options, abandonedNeverReclick);
|
|
1615
1704
|
for (const q of orderedQs) {
|
|
1616
1705
|
if (results.length >= options.maxQuestions) break;
|
|
1617
1706
|
const key = this.normalizeQuestion(q);
|
|
@@ -1629,9 +1718,11 @@ var PAAExtractor = class {
|
|
|
1629
1718
|
this.completeness.paaWithoutSource = results.filter((r) => !r.source_title && !r.source_site && !r.source_cite).length;
|
|
1630
1719
|
return results;
|
|
1631
1720
|
}
|
|
1632
|
-
async fillIncompleteItems(page, orderedQs, itemMap, options) {
|
|
1721
|
+
async fillIncompleteItems(page, orderedQs, itemMap, options, abandonedNeverReclick) {
|
|
1633
1722
|
const kept = new Set(orderedQs.slice(0, options.maxQuestions));
|
|
1634
|
-
const missingBefore = new Set(
|
|
1723
|
+
const missingBefore = new Set(
|
|
1724
|
+
[...kept].filter((q) => !itemMap.get(q)?.answer && !abandonedNeverReclick?.has(q))
|
|
1725
|
+
);
|
|
1635
1726
|
if (missingBefore.size === 0) return;
|
|
1636
1727
|
for (let round = 0; round < 2; round++) {
|
|
1637
1728
|
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
@@ -2023,40 +2114,41 @@ var PAAExtractor = class {
|
|
|
2023
2114
|
};
|
|
2024
2115
|
}
|
|
2025
2116
|
async captureAIOverviewShareUrl(page, options) {
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
const
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
return true;
|
|
2038
|
-
}
|
|
2039
|
-
sec = sec.parentElement;
|
|
2117
|
+
if (options?.softDeadlineMs && Date.now() >= options.softDeadlineMs) return null;
|
|
2118
|
+
const clicked = await page.evaluate(() => {
|
|
2119
|
+
const heads = Array.from(document.querySelectorAll('h1,h2,h3,[role="heading"],div')).filter((h) => (h.textContent || "").trim() === "AI Overview");
|
|
2120
|
+
for (const head of heads) {
|
|
2121
|
+
let sec = head;
|
|
2122
|
+
for (let up = 0; up < 8 && sec; up++) {
|
|
2123
|
+
const btn = sec.querySelector('[aria-label="Share"]');
|
|
2124
|
+
if (btn) {
|
|
2125
|
+
btn.scrollIntoView({ block: "center" });
|
|
2126
|
+
btn.click();
|
|
2127
|
+
return true;
|
|
2040
2128
|
}
|
|
2129
|
+
sec = sec.parentElement;
|
|
2041
2130
|
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2131
|
+
}
|
|
2132
|
+
return false;
|
|
2133
|
+
}).catch(() => false);
|
|
2134
|
+
if (!clicked) return null;
|
|
2135
|
+
let url = null;
|
|
2136
|
+
try {
|
|
2137
|
+
const found = await page.waitForFunction(() => {
|
|
2047
2138
|
const w = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
2048
2139
|
while (w.nextNode()) {
|
|
2049
2140
|
const m = (w.currentNode.textContent || "").match(/https:\/\/share\.google\/aimode\/\S+/);
|
|
2050
2141
|
if (m) return m[0];
|
|
2051
2142
|
}
|
|
2052
|
-
return
|
|
2053
|
-
}
|
|
2054
|
-
await
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
await page.waitForTimeout(600);
|
|
2143
|
+
return false;
|
|
2144
|
+
}, void 0, { timeout: 4500, polling: 250 });
|
|
2145
|
+
url = await found.jsonValue();
|
|
2146
|
+
} catch {
|
|
2147
|
+
url = null;
|
|
2058
2148
|
}
|
|
2059
|
-
|
|
2149
|
+
await page.keyboard.press("Escape").catch(() => {
|
|
2150
|
+
});
|
|
2151
|
+
return url;
|
|
2060
2152
|
}
|
|
2061
2153
|
buildTree(flat, _seed) {
|
|
2062
2154
|
const roots = [];
|
|
@@ -3277,4 +3369,4 @@ export {
|
|
|
3277
3369
|
resolveKernelProxyId,
|
|
3278
3370
|
harvest
|
|
3279
3371
|
};
|
|
3280
|
-
//# sourceMappingURL=chunk-
|
|
3372
|
+
//# sourceMappingURL=chunk-RMPPYKUV.js.map
|