mcp-scraper 0.5.0 → 0.7.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 +675 -214
- 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 +322 -11
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +3 -3
- 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-EGWJ74EX.js +7 -0
- package/dist/chunk-EGWJ74EX.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-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/{chunk-J4T5OSCF.js → chunk-YGRZU7IR.js} +322 -11
- package/dist/chunk-YGRZU7IR.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-Z4MEISH5.js → server-JNY4XPZE.js} +138 -81
- package/dist/server-JNY4XPZE.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 +2 -2
- package/dist/chunk-3RTPEQJC.js +0 -7
- package/dist/chunk-3RTPEQJC.js.map +0 -1
- package/dist/chunk-AUCXKRRH.js.map +0 -1
- package/dist/chunk-J4T5OSCF.js.map +0 -1
- package/dist/server-Z4MEISH5.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-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
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
registerMemoryMcpTools,
|
|
8
8
|
registerPaaExtractorMcpTools,
|
|
9
9
|
registerSerpIntelligenceCaptureTools
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-YGRZU7IR.js";
|
|
11
11
|
import "../chunk-GWRIO6JT.js";
|
|
12
12
|
import "../chunk-HPV4VOQX.js";
|
|
13
13
|
import {
|
|
@@ -16,8 +16,8 @@ import {
|
|
|
16
16
|
import "../chunk-XGIPATLV.js";
|
|
17
17
|
import {
|
|
18
18
|
PACKAGE_VERSION
|
|
19
|
-
} from "../chunk-
|
|
20
|
-
import "../chunk-
|
|
19
|
+
} from "../chunk-EGWJ74EX.js";
|
|
20
|
+
import "../chunk-HL33CGJF.js";
|
|
21
21
|
import "../chunk-M2S27J6Z.js";
|
|
22
22
|
|
|
23
23
|
// bin/mcp-stdio-server.ts
|
package/dist/bin/paa-harvest.cjs
CHANGED
|
@@ -1637,13 +1637,41 @@ var PAAExtractor = class {
|
|
|
1637
1637
|
}
|
|
1638
1638
|
return parts.join(" ").replace(/\s+/g, " ").trim();
|
|
1639
1639
|
}
|
|
1640
|
-
return Array.from(document.querySelectorAll(selectors.item)).map((pair) =>
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1640
|
+
return Array.from(document.querySelectorAll(selectors.item)).map((pair) => {
|
|
1641
|
+
let sourceTitle = pair.querySelector(selectors.sourceTitle)?.innerText?.trim() || void 0;
|
|
1642
|
+
let sourceSite = pair.querySelector(selectors.sourceSite)?.innerText?.trim() || void 0;
|
|
1643
|
+
let sourceCite = pair.querySelector(selectors.sourceCite)?.innerText?.trim() || void 0;
|
|
1644
|
+
if (!sourceCite) {
|
|
1645
|
+
const seenHrefs = /* @__PURE__ */ new Set();
|
|
1646
|
+
const anchors = [];
|
|
1647
|
+
for (const a of Array.from(pair.querySelectorAll("a[href]"))) {
|
|
1648
|
+
const href = a.href;
|
|
1649
|
+
if (!/^https?:\/\//.test(href) || seenHrefs.has(href)) continue;
|
|
1650
|
+
let host = "";
|
|
1651
|
+
try {
|
|
1652
|
+
host = new URL(href).hostname;
|
|
1653
|
+
} catch {
|
|
1654
|
+
continue;
|
|
1655
|
+
}
|
|
1656
|
+
if (/(^|\.)google(usercontent)?\.[a-z.]+$/.test(host)) continue;
|
|
1657
|
+
seenHrefs.add(href);
|
|
1658
|
+
anchors.push({ text: (a.textContent || "").trim(), href, host });
|
|
1659
|
+
}
|
|
1660
|
+
const primary = anchors.find((x) => x.text.length > 2) ?? anchors[0];
|
|
1661
|
+
if (primary) {
|
|
1662
|
+
sourceTitle = sourceTitle || primary.text || void 0;
|
|
1663
|
+
sourceSite = sourceSite || primary.host.replace(/^www\./, "");
|
|
1664
|
+
sourceCite = primary.href;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
return {
|
|
1668
|
+
question: pair.getAttribute(selectors.itemDataQ) || pair.getAttribute(selectors.itemDataInitQ) || pair.querySelector(selectors.itemQuestionEl)?.innerText?.trim() || "",
|
|
1669
|
+
answer: cleanText(pair.querySelector(selectors.answerContainer)) || void 0,
|
|
1670
|
+
sourceTitle,
|
|
1671
|
+
sourceSite,
|
|
1672
|
+
sourceCite
|
|
1673
|
+
};
|
|
1674
|
+
});
|
|
1647
1675
|
}, sels);
|
|
1648
1676
|
return raw.flatMap((item) => {
|
|
1649
1677
|
const cleaned = {
|
|
@@ -1685,54 +1713,115 @@ var PAAExtractor = class {
|
|
|
1685
1713
|
const seenQs = /* @__PURE__ */ new Set();
|
|
1686
1714
|
const orderedQs = [];
|
|
1687
1715
|
const results = [];
|
|
1688
|
-
const
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1716
|
+
const abandonedNeverReclick = /* @__PURE__ */ new Set();
|
|
1717
|
+
const clickedOnceEver = /* @__PURE__ */ new Set();
|
|
1718
|
+
const itemArgs = {
|
|
1719
|
+
sel: PAASelectors.item,
|
|
1720
|
+
dataQ: PAASelectors.itemDataQ,
|
|
1721
|
+
dataInitQ: PAASelectors.itemDataInitQ,
|
|
1722
|
+
questionEl: PAASelectors.itemQuestionEl,
|
|
1723
|
+
answerSel: PAASelectors.answerContainer,
|
|
1724
|
+
expandedClass: PAASelectors.expandedClass,
|
|
1725
|
+
clickTarget: PAASelectors.clickTarget
|
|
1726
|
+
};
|
|
1727
|
+
const readItemStates = () => page.evaluate(
|
|
1728
|
+
({ sel, dataQ, dataInitQ, questionEl, expandedClass, answerSel }) => {
|
|
1729
|
+
const answerText = (root) => {
|
|
1730
|
+
if (!root) return "";
|
|
1731
|
+
let out = "";
|
|
1732
|
+
const walk = (n) => {
|
|
1733
|
+
for (const c of Array.from(n.childNodes)) {
|
|
1734
|
+
if (c.nodeType === Node.TEXT_NODE) out += c.textContent || "";
|
|
1735
|
+
else if (c.nodeType === Node.ELEMENT_NODE && c.tagName !== "STYLE" && c.tagName !== "SCRIPT") walk(c);
|
|
1736
|
+
}
|
|
1737
|
+
};
|
|
1738
|
+
walk(root);
|
|
1739
|
+
return out.replace(/\s+/g, " ").trim();
|
|
1740
|
+
};
|
|
1741
|
+
return Array.from(document.querySelectorAll(sel)).map((el) => ({
|
|
1742
|
+
q: el.getAttribute(dataQ) || el.getAttribute(dataInitQ) || el.querySelector(questionEl)?.innerText?.trim() || "",
|
|
1743
|
+
done: el.classList.contains(expandedClass) || answerText(el.querySelector(answerSel)).length > 30
|
|
1744
|
+
})).filter((s) => s.q);
|
|
1745
|
+
},
|
|
1746
|
+
itemArgs
|
|
1693
1747
|
);
|
|
1748
|
+
const expandOneItemSerially = async (q) => {
|
|
1749
|
+
const clicked = await page.evaluate(
|
|
1750
|
+
({ sel, dataQ, dataInitQ, clickTarget, q: target }) => {
|
|
1751
|
+
const item = Array.from(document.querySelectorAll(sel)).find((el) => (el.getAttribute(dataQ) || el.getAttribute(dataInitQ)) === target);
|
|
1752
|
+
const btn = item?.querySelector(clickTarget);
|
|
1753
|
+
if (!btn) return false;
|
|
1754
|
+
btn.scrollIntoView({ block: "center" });
|
|
1755
|
+
btn.click();
|
|
1756
|
+
return true;
|
|
1757
|
+
},
|
|
1758
|
+
{ ...itemArgs, q }
|
|
1759
|
+
).catch(() => false);
|
|
1760
|
+
if (!clicked) return "failed";
|
|
1761
|
+
try {
|
|
1762
|
+
const outcome = await page.waitForFunction(
|
|
1763
|
+
({ sel, dataQ, dataInitQ, answerSel, q: target }) => {
|
|
1764
|
+
const answerText = (root) => {
|
|
1765
|
+
if (!root) return "";
|
|
1766
|
+
let out = "";
|
|
1767
|
+
const walk = (n) => {
|
|
1768
|
+
for (const c of Array.from(n.childNodes)) {
|
|
1769
|
+
if (c.nodeType === Node.TEXT_NODE) out += c.textContent || "";
|
|
1770
|
+
else if (c.nodeType === Node.ELEMENT_NODE && c.tagName !== "STYLE" && c.tagName !== "SCRIPT") walk(c);
|
|
1771
|
+
}
|
|
1772
|
+
};
|
|
1773
|
+
walk(root);
|
|
1774
|
+
return out.replace(/\s+/g, " ").trim();
|
|
1775
|
+
};
|
|
1776
|
+
const item = Array.from(document.querySelectorAll(sel)).find((el) => (el.getAttribute(dataQ) || el.getAttribute(dataInitQ)) === target);
|
|
1777
|
+
if (!item) return "gone";
|
|
1778
|
+
const text = answerText(item.querySelector(answerSel));
|
|
1779
|
+
if (/an error has occurred/i.test(text.slice(0, 80))) return "error";
|
|
1780
|
+
if (text.length > 30) return "ok";
|
|
1781
|
+
return false;
|
|
1782
|
+
},
|
|
1783
|
+
{ ...itemArgs, q },
|
|
1784
|
+
{ timeout: 6500, polling: 200 }
|
|
1785
|
+
);
|
|
1786
|
+
return await outcome.jsonValue() === "ok" ? "ok" : "failed";
|
|
1787
|
+
} catch {
|
|
1788
|
+
return "failed";
|
|
1789
|
+
}
|
|
1790
|
+
};
|
|
1694
1791
|
let round = 0;
|
|
1695
|
-
|
|
1792
|
+
let growthWaits = 0;
|
|
1793
|
+
while (true) {
|
|
1696
1794
|
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
1697
1795
|
this.throwIfAborted(signal);
|
|
1698
|
-
|
|
1699
|
-
const
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
if (unexpandedCount === 0) break;
|
|
1704
|
-
this.reporter.onDepth(++round);
|
|
1705
|
-
for (let ci = 0; ci < unexpandedCount; ci++) {
|
|
1706
|
-
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
1707
|
-
this.throwIfAborted(signal);
|
|
1708
|
-
try {
|
|
1709
|
-
const btn = page.locator(unexpandedSel).first();
|
|
1710
|
-
await btn.scrollIntoViewIfNeeded();
|
|
1711
|
-
await btn.hover({ force: true });
|
|
1712
|
-
await page.waitForTimeout(100);
|
|
1713
|
-
await btn.click({ force: true });
|
|
1714
|
-
await page.waitForTimeout(500);
|
|
1715
|
-
} catch {
|
|
1796
|
+
const states = await readItemStates();
|
|
1797
|
+
for (const s of states) {
|
|
1798
|
+
if (!seenQs.has(s.q)) {
|
|
1799
|
+
seenQs.add(s.q);
|
|
1800
|
+
orderedQs.push(s.q);
|
|
1716
1801
|
}
|
|
1717
1802
|
}
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
}
|
|
1803
|
+
const kept = new Set(orderedQs.slice(0, options.maxQuestions));
|
|
1804
|
+
const clickable = states.filter((s) => kept.has(s.q) && !clickedOnceEver.has(s.q) && !abandonedNeverReclick.has(s.q));
|
|
1805
|
+
const target = clickable.find((s) => !s.done) ?? (seenQs.size < options.maxQuestions ? clickable[0] : void 0);
|
|
1806
|
+
if (!target) {
|
|
1807
|
+
if (seenQs.size >= options.maxQuestions) break;
|
|
1808
|
+
if (++growthWaits > 1) break;
|
|
1809
|
+
const grew = await page.waitForFunction(
|
|
1810
|
+
({ sel, min }) => document.querySelectorAll(sel).length > min,
|
|
1811
|
+
{ sel: PAASelectors.item, min: states.length },
|
|
1812
|
+
{ timeout: 3e3 }
|
|
1813
|
+
).catch(() => null);
|
|
1814
|
+
if (!grew) break;
|
|
1815
|
+
continue;
|
|
1732
1816
|
}
|
|
1817
|
+
growthWaits = 0;
|
|
1818
|
+
this.reporter.onDepth(++round);
|
|
1819
|
+
await this.throwIfCaptcha(page, "Google PAA expansion");
|
|
1820
|
+
clickedOnceEver.add(target.q);
|
|
1821
|
+
if (await expandOneItemSerially(target.q) === "failed") abandonedNeverReclick.add(target.q);
|
|
1733
1822
|
}
|
|
1734
1823
|
const itemMap = new Map((await this.extractVisibleItems(page)).map((i) => [i.question, i]));
|
|
1735
|
-
await this.fillIncompleteItems(page, orderedQs, itemMap, options);
|
|
1824
|
+
await this.fillIncompleteItems(page, orderedQs, itemMap, options, abandonedNeverReclick);
|
|
1736
1825
|
for (const q of orderedQs) {
|
|
1737
1826
|
if (results.length >= options.maxQuestions) break;
|
|
1738
1827
|
const key = this.normalizeQuestion(q);
|
|
@@ -1750,9 +1839,11 @@ var PAAExtractor = class {
|
|
|
1750
1839
|
this.completeness.paaWithoutSource = results.filter((r) => !r.source_title && !r.source_site && !r.source_cite).length;
|
|
1751
1840
|
return results;
|
|
1752
1841
|
}
|
|
1753
|
-
async fillIncompleteItems(page, orderedQs, itemMap, options) {
|
|
1842
|
+
async fillIncompleteItems(page, orderedQs, itemMap, options, abandonedNeverReclick) {
|
|
1754
1843
|
const kept = new Set(orderedQs.slice(0, options.maxQuestions));
|
|
1755
|
-
const missingBefore = new Set(
|
|
1844
|
+
const missingBefore = new Set(
|
|
1845
|
+
[...kept].filter((q) => !itemMap.get(q)?.answer && !abandonedNeverReclick?.has(q))
|
|
1846
|
+
);
|
|
1756
1847
|
if (missingBefore.size === 0) return;
|
|
1757
1848
|
for (let round = 0; round < 2; round++) {
|
|
1758
1849
|
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
@@ -2144,40 +2235,41 @@ var PAAExtractor = class {
|
|
|
2144
2235
|
};
|
|
2145
2236
|
}
|
|
2146
2237
|
async captureAIOverviewShareUrl(page, options) {
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
const
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
return true;
|
|
2159
|
-
}
|
|
2160
|
-
sec = sec.parentElement;
|
|
2238
|
+
if (options?.softDeadlineMs && Date.now() >= options.softDeadlineMs) return null;
|
|
2239
|
+
const clicked = await page.evaluate(() => {
|
|
2240
|
+
const heads = Array.from(document.querySelectorAll('h1,h2,h3,[role="heading"],div')).filter((h) => (h.textContent || "").trim() === "AI Overview");
|
|
2241
|
+
for (const head of heads) {
|
|
2242
|
+
let sec = head;
|
|
2243
|
+
for (let up = 0; up < 8 && sec; up++) {
|
|
2244
|
+
const btn = sec.querySelector('[aria-label="Share"]');
|
|
2245
|
+
if (btn) {
|
|
2246
|
+
btn.scrollIntoView({ block: "center" });
|
|
2247
|
+
btn.click();
|
|
2248
|
+
return true;
|
|
2161
2249
|
}
|
|
2250
|
+
sec = sec.parentElement;
|
|
2162
2251
|
}
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2252
|
+
}
|
|
2253
|
+
return false;
|
|
2254
|
+
}).catch(() => false);
|
|
2255
|
+
if (!clicked) return null;
|
|
2256
|
+
let url = null;
|
|
2257
|
+
try {
|
|
2258
|
+
const found = await page.waitForFunction(() => {
|
|
2168
2259
|
const w = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
2169
2260
|
while (w.nextNode()) {
|
|
2170
2261
|
const m = (w.currentNode.textContent || "").match(/https:\/\/share\.google\/aimode\/\S+/);
|
|
2171
2262
|
if (m) return m[0];
|
|
2172
2263
|
}
|
|
2173
|
-
return
|
|
2174
|
-
}
|
|
2175
|
-
await
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
await page.waitForTimeout(600);
|
|
2264
|
+
return false;
|
|
2265
|
+
}, void 0, { timeout: 4500, polling: 250 });
|
|
2266
|
+
url = await found.jsonValue();
|
|
2267
|
+
} catch {
|
|
2268
|
+
url = null;
|
|
2179
2269
|
}
|
|
2180
|
-
|
|
2270
|
+
await page.keyboard.press("Escape").catch(() => {
|
|
2271
|
+
});
|
|
2272
|
+
return url;
|
|
2181
2273
|
}
|
|
2182
2274
|
buildTree(flat, _seed) {
|
|
2183
2275
|
const roots = [];
|