recker 1.0.79 → 1.0.80-next.70ea84d
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/browser/browser/mini.d.ts +2 -2
- package/dist/browser/browser/recker-mini.d.ts +8 -8
- package/dist/browser/browser/recker.d.ts +11 -8
- package/dist/browser/browser/recker.js +54 -6
- package/dist/browser/core/client.d.ts +15 -10
- package/dist/browser/core/client.js +54 -38
- package/dist/browser/index.iife.min.js +129 -130
- package/dist/browser/index.min.js +129 -130
- package/dist/browser/index.mini.iife.js +5697 -636
- package/dist/browser/index.mini.iife.min.js +47 -48
- package/dist/browser/index.mini.min.js +47 -48
- package/dist/browser/index.mini.umd.js +5697 -636
- package/dist/browser/index.mini.umd.min.js +47 -48
- package/dist/browser/index.umd.min.js +129 -130
- package/dist/browser/mini.d.ts +2 -2
- package/dist/browser/plugins/proxy-rotator.d.ts +2 -2
- package/dist/browser/plugins/proxy-rotator.js +6 -28
- package/dist/browser/recker-mini.d.ts +8 -8
- package/dist/browser/recker.d.ts +11 -8
- package/dist/browser/recker.js +54 -6
- package/dist/browser/scrape/document.js +2 -2
- package/dist/browser/scrape/element.js +7 -1
- package/dist/browser/scrape/parser/nodes/html.js +1 -1
- package/dist/browser/scrape/spider.d.ts +52 -0
- package/dist/browser/scrape/spider.js +620 -38
- package/dist/browser/scrape/types.d.ts +2 -0
- package/dist/browser/search/google.d.ts +26 -1
- package/dist/browser/search/google.js +427 -45
- package/dist/browser/seo/analyzer.d.ts +1 -0
- package/dist/browser/seo/analyzer.js +144 -1
- package/dist/browser/seo/index.d.ts +1 -1
- package/dist/browser/seo/keyword-campaign-analyzer.d.ts +2 -0
- package/dist/browser/seo/keyword-campaign-analyzer.js +538 -0
- package/dist/browser/seo/keyword-campaign-seed-advanced.d.ts +17 -0
- package/dist/browser/seo/keyword-campaign-seed-advanced.js +269 -0
- package/dist/browser/seo/keyword-campaign-seed-core.d.ts +29 -0
- package/dist/browser/seo/keyword-campaign-seed-core.js +525 -0
- package/dist/browser/seo/keyword-campaign-shared.d.ts +165 -0
- package/dist/browser/seo/keyword-campaign-shared.js +59 -0
- package/dist/browser/seo/keyword-campaign.d.ts +4 -107
- package/dist/browser/seo/keyword-campaign.js +2 -380
- package/dist/browser/seo/keywords.js +5 -22
- package/dist/browser/seo/types.d.ts +19 -0
- package/dist/browser/transport/curl.d.ts +5 -1
- package/dist/browser/transport/curl.js +207 -50
- package/dist/browser/transport/undici.d.ts +4 -3
- package/dist/browser/transport/undici.js +123 -49
- package/dist/browser/types/index.d.ts +9 -3
- package/dist/browser/utils/binary-manager.js +26 -3
- package/dist/browser/utils/block-detector.d.ts +8 -0
- package/dist/browser/utils/block-detector.js +542 -7
- package/dist/cli/commands/hls-runner.js +5 -4
- package/dist/cli/commands/live-runner.js +5 -4
- package/dist/cli/commands/loadtest-runner.js +3 -2
- package/dist/cli/commands/search.d.ts +2 -0
- package/dist/cli/commands/search.js +105 -0
- package/dist/cli/commands/seo-runner.js +9 -7
- package/dist/cli/commands/seo.js +140 -1
- package/dist/cli/commands/serve.js +75 -131
- package/dist/cli/commands/server-runner.js +59 -82
- package/dist/cli/commands/spider-runner.d.ts +37 -1
- package/dist/cli/commands/spider-runner.js +134 -10
- package/dist/cli/commands/spider.d.ts +18 -1
- package/dist/cli/commands/spider.js +457 -27
- package/dist/cli/events/handlers/cli.js +30 -1
- package/dist/cli/events/handlers/tui.js +26 -0
- package/dist/cli/events/types.d.ts +27 -0
- package/dist/cli/handler.d.ts +2 -12
- package/dist/cli/handler.js +20 -15
- package/dist/cli/handlers/protocols.js +39 -12
- package/dist/cli/handlers/search.d.ts +2 -0
- package/dist/cli/handlers/search.js +171 -0
- package/dist/cli/handlers/seo-analyze.d.ts +1 -0
- package/dist/cli/handlers/seo-analyze.js +666 -0
- package/dist/cli/handlers/seo-robots.d.ts +1 -0
- package/dist/cli/handlers/seo-robots.js +76 -0
- package/dist/cli/handlers/seo-serp.d.ts +54 -0
- package/dist/cli/handlers/seo-serp.js +243 -0
- package/dist/cli/handlers/seo-sitemap.d.ts +1 -0
- package/dist/cli/handlers/seo-sitemap.js +55 -0
- package/dist/cli/handlers/seo-spider.d.ts +1 -0
- package/dist/cli/handlers/seo-spider.js +334 -0
- package/dist/cli/handlers/seo.d.ts +8 -4
- package/dist/cli/handlers/seo.js +294 -442
- package/dist/cli/handlers/spider.js +94 -17
- package/dist/cli/handlers/streaming.js +5 -1
- package/dist/cli/index.js +11 -2
- package/dist/cli/presets.d.ts +1 -1
- package/dist/cli/presets.js +15 -4
- package/dist/cli/tui/app.js +6 -1
- package/dist/cli/tui/components/rich-response.d.ts +72 -0
- package/dist/cli/tui/components/rich-response.js +117 -0
- package/dist/cli/tui/executor-commands/background.js +30 -24
- package/dist/cli/tui/executor-commands/testing.js +3 -2
- package/dist/cli/tui/hooks/useDomains.d.ts +17 -0
- package/dist/cli/tui/hooks/useHelp.js +15 -2
- package/dist/cli/tui/job-manager.d.ts +4 -4
- package/dist/cli/tui/job-manager.js +5 -1
- package/dist/cli/tui/spider-tui.d.ts +63 -0
- package/dist/cli/tui/spider-tui.js +120 -2
- package/dist/cli/types.d.ts +12 -0
- package/dist/cli/types.js +1 -0
- package/dist/cli/utils/option-helpers.d.ts +10 -0
- package/dist/cli/utils/option-helpers.js +63 -0
- package/dist/cli/utils/score-color.d.ts +11 -0
- package/dist/cli/utils/score-color.js +11 -0
- package/dist/cli/utils/serp-campaign.d.ts +53 -0
- package/dist/cli/utils/serp-campaign.js +53 -0
- package/dist/cli/utils/serp-config.d.ts +26 -0
- package/dist/cli/utils/serp-config.js +125 -0
- package/dist/core/client.d.ts +15 -10
- package/dist/core/client.js +54 -38
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/cli.js +35 -34
- package/dist/mcp/client.d.ts +2 -2
- package/dist/mcp/client.js +20 -2
- package/dist/mcp/contract.d.ts +1 -1
- package/dist/mcp/profiles.js +5 -1
- package/dist/mcp/prompts/index.js +8 -4
- package/dist/mcp/resources/index.js +46 -23
- package/dist/mcp/server.js +9 -6
- package/dist/mcp/tools/protocols.js +9 -2
- package/dist/mcp/tools/registry.js +13 -3
- package/dist/mcp/tools/seo.js +427 -2
- package/dist/mcp/types.d.ts +5 -1
- package/dist/plugins/proxy-rotator.d.ts +2 -2
- package/dist/plugins/proxy-rotator.js +6 -28
- package/dist/raffel/client.d.ts +38 -0
- package/dist/raffel/client.js +282 -0
- package/dist/raffel/index.d.ts +2 -0
- package/dist/raffel/index.js +2 -0
- package/dist/raffel/types.d.ts +40 -0
- package/dist/raffel/types.js +14 -0
- package/dist/recker.d.ts +13 -7
- package/dist/recker.js +58 -6
- package/dist/scrape/document.js +2 -2
- package/dist/scrape/element.js +7 -1
- package/dist/scrape/parser/nodes/html.js +1 -1
- package/dist/scrape/spider.d.ts +52 -0
- package/dist/scrape/spider.js +620 -38
- package/dist/scrape/types.d.ts +2 -0
- package/dist/search/google.d.ts +26 -1
- package/dist/search/google.js +427 -45
- package/dist/search/index.d.ts +1 -1
- package/dist/seo/analyzer.d.ts +1 -0
- package/dist/seo/analyzer.js +144 -1
- package/dist/seo/index.d.ts +1 -1
- package/dist/seo/keyword-campaign-analyzer.d.ts +2 -0
- package/dist/seo/keyword-campaign-analyzer.js +538 -0
- package/dist/seo/keyword-campaign-seed-advanced.d.ts +17 -0
- package/dist/seo/keyword-campaign-seed-advanced.js +269 -0
- package/dist/seo/keyword-campaign-seed-core.d.ts +29 -0
- package/dist/seo/keyword-campaign-seed-core.js +525 -0
- package/dist/seo/keyword-campaign-shared.d.ts +165 -0
- package/dist/seo/keyword-campaign-shared.js +59 -0
- package/dist/seo/keyword-campaign.d.ts +4 -107
- package/dist/seo/keyword-campaign.js +2 -380
- package/dist/seo/keywords.js +5 -22
- package/dist/seo/types.d.ts +19 -0
- package/dist/template/index.d.ts +1 -1
- package/dist/template/types.d.ts +0 -2
- package/dist/testing/index.d.ts +1 -22
- package/dist/testing/index.js +1 -11
- package/dist/transport/curl.d.ts +5 -1
- package/dist/transport/curl.js +207 -50
- package/dist/transport/undici.d.ts +4 -3
- package/dist/transport/undici.js +123 -49
- package/dist/types/index.d.ts +9 -3
- package/dist/utils/binary-manager.js +26 -3
- package/dist/utils/block-detector.d.ts +8 -0
- package/dist/utils/block-detector.js +542 -7
- package/dist/version.js +1 -1
- package/package.json +12 -1
- package/dist/testing/mock-dns-server.d.ts +0 -69
- package/dist/testing/mock-dns-server.js +0 -269
- package/dist/testing/mock-ftp-server.d.ts +0 -89
- package/dist/testing/mock-ftp-server.js +0 -562
- package/dist/testing/mock-hls-server.d.ts +0 -80
- package/dist/testing/mock-hls-server.js +0 -381
- package/dist/testing/mock-http-server.d.ts +0 -124
- package/dist/testing/mock-http-server.js +0 -343
- package/dist/testing/mock-proxy-server.d.ts +0 -108
- package/dist/testing/mock-proxy-server.js +0 -615
- package/dist/testing/mock-sse-server.d.ts +0 -76
- package/dist/testing/mock-sse-server.js +0 -291
- package/dist/testing/mock-telnet-server.d.ts +0 -59
- package/dist/testing/mock-telnet-server.js +0 -274
- package/dist/testing/mock-udp-server.d.ts +0 -43
- package/dist/testing/mock-udp-server.js +0 -188
- package/dist/testing/mock-websocket-server.d.ts +0 -76
- package/dist/testing/mock-websocket-server.js +0 -334
- package/dist/testing/mock-whois-server.d.ts +0 -56
- package/dist/testing/mock-whois-server.js +0 -234
- package/dist/testing/proxy-certs.d.ts +0 -19
- package/dist/testing/proxy-certs.js +0 -208
|
@@ -1,6 +1,151 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import colors from '../../utils/colors.js';
|
|
3
3
|
import { summarizeErrors, formatErrorSummary } from '../helpers.js';
|
|
4
|
+
import { parseSpiderSerpConfig } from '../utils/serp-config.js';
|
|
5
|
+
import { runSpiderKeywordCampaign, toSerpPayload } from '../utils/serp-campaign.js';
|
|
6
|
+
import { getOptionValue, toBoolean, toNonNegativeInt, toOptionString, toSpiderTransport, } from '../utils/option-helpers.js';
|
|
7
|
+
import { getScoreColor } from '../utils/score-color.js';
|
|
8
|
+
const FOCUS_MODES = ['all', 'links', 'duplicates', 'security', 'ai', 'resources'];
|
|
9
|
+
function normalizeSpiderFocus(value) {
|
|
10
|
+
const raw = toOptionString(value)?.toLowerCase();
|
|
11
|
+
return raw && FOCUS_MODES.includes(raw) ? raw : 'all';
|
|
12
|
+
}
|
|
13
|
+
function toStringOrStringArray(value) {
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
return value.map((item) => String(item)).filter(Boolean);
|
|
16
|
+
}
|
|
17
|
+
return toOptionString(value);
|
|
18
|
+
}
|
|
19
|
+
function printSerpCampaignSummary(run, color) {
|
|
20
|
+
const campaign = run.campaign;
|
|
21
|
+
const summary = campaign.summary;
|
|
22
|
+
const foundRate = summary.queriesRequested > 0
|
|
23
|
+
? Math.round((summary.queriesFound / summary.queriesRequested) * 100)
|
|
24
|
+
: 0;
|
|
25
|
+
console.log(color.bold('\n SERP campaign:'));
|
|
26
|
+
console.log(` ${color.green('Queries')}: ${summary.queriesExecuted}/${summary.queriesRequested} searched | ${color.green(String(summary.queriesFound))} found (${foundRate}%)`);
|
|
27
|
+
console.log(` ${color.blue('Top3')}: ${summary.top3Count} | ${color.blue('Top10')}: ${summary.top10Count}`);
|
|
28
|
+
console.log(` ${color.yellow('Avg position')}: ${summary.avgTopPosition !== null ? String(Math.round(summary.avgTopPosition)) : 'n/a'}`);
|
|
29
|
+
if (campaign.results.length > 0) {
|
|
30
|
+
console.log(` ${color.blue('Keywords')}:`);
|
|
31
|
+
for (const result of campaign.results.slice(0, 15)) {
|
|
32
|
+
const icon = result.found ? color.green('✔') : color.red('✗');
|
|
33
|
+
const pos = result.bestPosition !== null ? color.gray(` #${result.bestPosition}`) : color.gray(' -');
|
|
34
|
+
const url = result.matchedUrl ? color.gray(` ${result.matchedUrl.replace(/^https?:\/\/[^/]+/, '').slice(0, 40)}`) : '';
|
|
35
|
+
console.log(` ${icon} ${result.keyword.padEnd(28)}${pos}${url}`);
|
|
36
|
+
}
|
|
37
|
+
if (campaign.results.length > 15) {
|
|
38
|
+
console.log(color.gray(` ... and ${campaign.results.length - 15} more keywords`));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (summary.topOrganicCompetitors.length > 0) {
|
|
42
|
+
console.log(` ${color.blue('Top competitors')}:`);
|
|
43
|
+
for (const competitor of summary.topOrganicCompetitors.slice(0, 3)) {
|
|
44
|
+
const wins = competitor.totalOutperformedQueries ?? 0;
|
|
45
|
+
console.log(` ${color.gray(`${competitor.domain}`)} (${wins} wins / ${competitor.matchedKeywords} matched)`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (campaign.pageComparison.length > 0) {
|
|
49
|
+
console.log(` ${color.blue('Best pages')}:`);
|
|
50
|
+
for (const page of campaign.pageComparison
|
|
51
|
+
.sort((a, b) => b.found - a.found || b.tracked - a.tracked)
|
|
52
|
+
.slice(0, 3)) {
|
|
53
|
+
console.log(` ${color.gray(`${page.pageUrl}`)} ${page.found}/${page.tracked} (${page.appearanceRate.toFixed(1)}%)`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function summarizePageSecurity(pages) {
|
|
58
|
+
let blockedPages = 0;
|
|
59
|
+
let captchaPages = 0;
|
|
60
|
+
const captchaProviders = {};
|
|
61
|
+
let attempts = 0;
|
|
62
|
+
let retries = 0;
|
|
63
|
+
let undici = 0;
|
|
64
|
+
let curl = 0;
|
|
65
|
+
let ttfbSum = 0;
|
|
66
|
+
let totalSum = 0;
|
|
67
|
+
let downloadSum = 0;
|
|
68
|
+
let ttfbCount = 0;
|
|
69
|
+
let totalCount = 0;
|
|
70
|
+
let downloadCount = 0;
|
|
71
|
+
const totalPages = pages.length;
|
|
72
|
+
for (const page of pages) {
|
|
73
|
+
const security = page.security;
|
|
74
|
+
attempts += Number.isFinite(security?.attempts ?? NaN) ? (security?.attempts || 0) : 1;
|
|
75
|
+
retries += security?.retryCount ?? 0;
|
|
76
|
+
if (security?.blocked)
|
|
77
|
+
blockedPages += 1;
|
|
78
|
+
if (security?.captchaDetected)
|
|
79
|
+
captchaPages += 1;
|
|
80
|
+
if (security?.captchaDetected) {
|
|
81
|
+
const provider = (security.captchaProvider || '').trim().toLowerCase();
|
|
82
|
+
const key = provider.length > 0 ? provider : 'unknown';
|
|
83
|
+
captchaProviders[key] = (captchaProviders[key] || 0) + 1;
|
|
84
|
+
}
|
|
85
|
+
if (security?.transport === 'undici') {
|
|
86
|
+
undici += 1;
|
|
87
|
+
}
|
|
88
|
+
else if (security?.transport === 'curl') {
|
|
89
|
+
curl += 1;
|
|
90
|
+
}
|
|
91
|
+
const ttfb = page.timings?.ttfb;
|
|
92
|
+
const total = page.timings?.total;
|
|
93
|
+
const download = page.timings?.download;
|
|
94
|
+
if (typeof ttfb === 'number' && Number.isFinite(ttfb)) {
|
|
95
|
+
ttfbSum += ttfb;
|
|
96
|
+
ttfbCount += 1;
|
|
97
|
+
}
|
|
98
|
+
if (typeof total === 'number' && Number.isFinite(total)) {
|
|
99
|
+
totalSum += total;
|
|
100
|
+
totalCount += 1;
|
|
101
|
+
}
|
|
102
|
+
if (typeof download === 'number' && Number.isFinite(download)) {
|
|
103
|
+
downloadSum += download;
|
|
104
|
+
downloadCount += 1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
pages: totalPages,
|
|
109
|
+
blockedPages,
|
|
110
|
+
captchaPages,
|
|
111
|
+
captchaProviders,
|
|
112
|
+
attempts: attempts || totalPages || 1,
|
|
113
|
+
retries,
|
|
114
|
+
transportUsage: {
|
|
115
|
+
undici,
|
|
116
|
+
curl,
|
|
117
|
+
},
|
|
118
|
+
avgAttempts: totalPages > 0 ? Number((attempts / totalPages).toFixed(2)) : 0,
|
|
119
|
+
avgTtfbMs: ttfbCount > 0 ? Math.round(ttfbSum / ttfbCount) : undefined,
|
|
120
|
+
avgTotalMs: totalCount > 0 ? Math.round(totalSum / totalCount) : undefined,
|
|
121
|
+
avgDownloadMs: downloadCount > 0 ? Math.round(downloadSum / downloadCount) : undefined,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function toNumber(value) {
|
|
125
|
+
const parsed = parseFloat(value);
|
|
126
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
127
|
+
}
|
|
128
|
+
function printSecuritySummary(summary, securityPages, colors) {
|
|
129
|
+
const blockedRate = securityPages > 0 ? Math.round((summary.blockedPages / securityPages) * 100) : 0;
|
|
130
|
+
const captchaRate = securityPages > 0 ? Math.round((summary.captchaPages / securityPages) * 100) : 0;
|
|
131
|
+
const transportTotal = summary.transportUsage.undici + summary.transportUsage.curl;
|
|
132
|
+
const transportLine = transportTotal > 0
|
|
133
|
+
? `curl ${summary.transportUsage.curl} (${Math.round((summary.transportUsage.curl / transportTotal) * 100)}%) / undici ${summary.transportUsage.undici} (${Math.round((summary.transportUsage.undici / transportTotal) * 100)}%)`
|
|
134
|
+
: 'transport usage n/a';
|
|
135
|
+
console.log(colors.bold('\n Anti-bot signal:'));
|
|
136
|
+
console.log(` ${colors.red('Blocked')}: ${summary.blockedPages}/${securityPages} (${blockedRate}%)`);
|
|
137
|
+
console.log(` ${colors.yellow('Captcha')}: ${summary.captchaPages}/${securityPages} (${captchaRate}%)`);
|
|
138
|
+
console.log(` ${colors.blue('Attempts')}: avg ${summary.avgAttempts}, retries ${summary.retries}`);
|
|
139
|
+
console.log(` ${colors.cyan('Timings')}: ttfb ${summary.avgTtfbMs ?? 'n/a'}ms · total ${summary.avgTotalMs ?? 'n/a'}ms · download ${summary.avgDownloadMs ?? 'n/a'}ms`);
|
|
140
|
+
console.log(` ${colors.magenta('Transport')}: ${transportLine}`);
|
|
141
|
+
if (Object.keys(summary.captchaProviders).length > 0) {
|
|
142
|
+
const providers = Object.entries(summary.captchaProviders)
|
|
143
|
+
.sort((a, b) => b[1] - a[1])
|
|
144
|
+
.map(([provider, count]) => `${provider}: ${count}`)
|
|
145
|
+
.join(', ');
|
|
146
|
+
console.log(` ${colors.gray('Captcha providers')}: ${providers}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
4
149
|
export async function runSpider(opts) {
|
|
5
150
|
const url = opts.url;
|
|
6
151
|
const formatJson = !!opts.json;
|
|
@@ -10,9 +155,29 @@ export async function runSpider(opts) {
|
|
|
10
155
|
const focusMode = opts.focus || 'all';
|
|
11
156
|
const respectRobotsTxt = !!opts.robots;
|
|
12
157
|
const useSitemap = seoEnabled && !opts.noSitemap;
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
158
|
+
const serpConfig = opts.serpConfig || parseSpiderSerpConfig(opts);
|
|
159
|
+
const shouldRunSerp = seoEnabled && serpConfig.enabled;
|
|
160
|
+
let startUrl = url;
|
|
161
|
+
if (!startUrl.startsWith('http'))
|
|
162
|
+
startUrl = `https://${startUrl}`;
|
|
163
|
+
if (serpConfig.enabled && !seoEnabled) {
|
|
164
|
+
console.log(colors.yellow('\nSERP campaign requires --seo. Skipping SERP checks.'));
|
|
165
|
+
}
|
|
166
|
+
const depth = toNonNegativeInt(opts.depth, 10);
|
|
167
|
+
const limit = toNonNegativeInt(opts.limit, 1000);
|
|
168
|
+
const concurrency = toNonNegativeInt(opts.concurrency, 5);
|
|
169
|
+
const transport = toSpiderTransport(opts.transport) ?? 'auto';
|
|
170
|
+
const preferCurlFirst = toBoolean(opts.preferCurlFirst, true);
|
|
171
|
+
const timeout = toNonNegativeInt(opts.timeout, 10_000);
|
|
172
|
+
const delay = toNonNegativeInt(opts.delay, 100);
|
|
173
|
+
const maxRetryAttempts = toNonNegativeInt(opts.maxRetryAttempts, 3);
|
|
174
|
+
const baseRetryDelayMs = toNonNegativeInt(opts.baseRetryDelayMs, 1_000);
|
|
175
|
+
const maxRetryDelayMs = toNonNegativeInt(opts.maxRetryDelayMs, 12_000);
|
|
176
|
+
const retryBackoffMultiplier = toNonNegativeInt(opts.retryBackoffMultiplier, 2);
|
|
177
|
+
const retryJitterMs = toNonNegativeInt(opts.retryJitterMs, 250);
|
|
178
|
+
const maxDomainBlockStrikes = toNonNegativeInt(opts.maxDomainBlockStrikes, 2);
|
|
179
|
+
const rotateUserAgent = toBoolean(opts.rotateUserAgent, true);
|
|
180
|
+
const randomizeHeaders = toBoolean(opts.randomizeHeaders, true);
|
|
16
181
|
const parseList = (val) => {
|
|
17
182
|
if (!val)
|
|
18
183
|
return undefined;
|
|
@@ -26,7 +191,7 @@ export async function runSpider(opts) {
|
|
|
26
191
|
if (formatJsonl) {
|
|
27
192
|
const { runSpiderWithEvents } = await import('./spider-runner.js');
|
|
28
193
|
try {
|
|
29
|
-
await runSpiderWithEvents(url, {
|
|
194
|
+
const crawlResult = await runSpiderWithEvents(url, {
|
|
30
195
|
depth,
|
|
31
196
|
limit,
|
|
32
197
|
concurrency,
|
|
@@ -34,16 +199,47 @@ export async function runSpider(opts) {
|
|
|
34
199
|
seo: seoEnabled,
|
|
35
200
|
useSitemap,
|
|
36
201
|
focus: focusMode,
|
|
202
|
+
transport,
|
|
203
|
+
preferCurlFirst,
|
|
204
|
+
timeout,
|
|
205
|
+
delay,
|
|
206
|
+
maxRetryAttempts,
|
|
207
|
+
baseRetryDelayMs,
|
|
208
|
+
maxRetryDelayMs,
|
|
209
|
+
retryBackoffMultiplier,
|
|
210
|
+
retryJitterMs,
|
|
211
|
+
maxDomainBlockStrikes,
|
|
212
|
+
rotateUserAgent,
|
|
213
|
+
randomizeHeaders,
|
|
37
214
|
extract: extractSelectors,
|
|
38
215
|
include: includePatterns,
|
|
39
216
|
exclude: excludePatterns,
|
|
40
217
|
jsonl: true,
|
|
41
218
|
jsonlOutput: outputFile,
|
|
42
219
|
});
|
|
220
|
+
if (shouldRunSerp && crawlResult.pages?.length > 0) {
|
|
221
|
+
const serpCampaign = await runSpiderKeywordCampaign(startUrl, crawlResult.pages, serpConfig);
|
|
222
|
+
if (serpCampaign) {
|
|
223
|
+
const outputRecord = {
|
|
224
|
+
type: 'serp',
|
|
225
|
+
...(toSerpPayload(serpCampaign)),
|
|
226
|
+
};
|
|
227
|
+
if (!outputFile) {
|
|
228
|
+
console.log(JSON.stringify(outputRecord));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
await fs.appendFile(outputFile, `${JSON.stringify(outputRecord)}\n`);
|
|
232
|
+
}
|
|
233
|
+
else if (!outputFile) {
|
|
234
|
+
console.log(colors.yellow('SERP enabled but no valid keyword seeds were found in crawl pages.'));
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
43
238
|
return;
|
|
44
239
|
}
|
|
45
240
|
catch (error) {
|
|
46
|
-
|
|
241
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
242
|
+
console.error(colors.red(`\nSpider failed: ${message}`));
|
|
47
243
|
process.exit(1);
|
|
48
244
|
}
|
|
49
245
|
}
|
|
@@ -55,9 +251,6 @@ export async function runSpider(opts) {
|
|
|
55
251
|
resources: ['resources', 'performance'],
|
|
56
252
|
all: [],
|
|
57
253
|
};
|
|
58
|
-
let startUrl = url;
|
|
59
|
-
if (!startUrl.startsWith('http'))
|
|
60
|
-
startUrl = `https://${startUrl}`;
|
|
61
254
|
if (!formatJson) {
|
|
62
255
|
const modeLabel = seoEnabled ? colors.magenta(' + SEO') : '';
|
|
63
256
|
const focusLabel = focusMode !== 'all' ? colors.cyan(` [focus: ${focusMode}]`) : '';
|
|
@@ -85,13 +278,24 @@ Spider starting: ${startUrl}`));
|
|
|
85
278
|
maxPages: limit,
|
|
86
279
|
concurrency: concurrency,
|
|
87
280
|
sameDomain: true,
|
|
88
|
-
delay
|
|
281
|
+
delay,
|
|
282
|
+
timeout,
|
|
283
|
+
transport,
|
|
284
|
+
preferCurlFirst,
|
|
285
|
+
maxRetryAttempts,
|
|
286
|
+
baseRetryDelayMs,
|
|
287
|
+
maxRetryDelayMs,
|
|
288
|
+
retryBackoffMultiplier,
|
|
289
|
+
retryJitterMs,
|
|
290
|
+
maxDomainBlockStrikes,
|
|
291
|
+
rotateUserAgent,
|
|
292
|
+
randomizeHeaders,
|
|
89
293
|
seo: true,
|
|
90
294
|
respectRobotsTxt,
|
|
91
295
|
useSitemap,
|
|
92
296
|
output: outputFile || undefined,
|
|
93
297
|
focusCategories: focusCategories[focusMode],
|
|
94
|
-
focusMode
|
|
298
|
+
focusMode,
|
|
95
299
|
onProgress: (progress) => {
|
|
96
300
|
currentCrawled = progress.crawled;
|
|
97
301
|
if (tui) {
|
|
@@ -111,24 +315,66 @@ Spider starting: ${startUrl}`));
|
|
|
111
315
|
if (tui) {
|
|
112
316
|
const bytes = seoPage.metrics?.htmlSize;
|
|
113
317
|
const timings = seoPage.timings || { total: seoPage.duration };
|
|
318
|
+
const securityTransport = seoPage.security?.transport === 'curl' || seoPage.security?.transport === 'undici'
|
|
319
|
+
? seoPage.security.transport
|
|
320
|
+
: undefined;
|
|
114
321
|
tui.updateSeo(seoPage.url, seoPage.seoReport.score, bytes, timings);
|
|
322
|
+
tui.updateSecurity({
|
|
323
|
+
blocked: seoPage.security?.blocked,
|
|
324
|
+
captchaDetected: seoPage.security?.captchaDetected,
|
|
325
|
+
captchaProvider: seoPage.security?.captchaProvider,
|
|
326
|
+
attempts: seoPage.security?.attempts,
|
|
327
|
+
retryCount: seoPage.security?.retryCount,
|
|
328
|
+
transport: securityTransport,
|
|
329
|
+
ttfb: timings.ttfb,
|
|
330
|
+
total: timings.total,
|
|
331
|
+
download: timings.download,
|
|
332
|
+
});
|
|
115
333
|
}
|
|
116
334
|
}
|
|
117
335
|
},
|
|
118
336
|
});
|
|
119
337
|
const result = await seoSpider.crawl(startUrl);
|
|
338
|
+
const serpCampaign = shouldRunSerp ? await runSpiderKeywordCampaign(startUrl, result.pages, serpConfig) : undefined;
|
|
339
|
+
const serpPayload = serpCampaign ? toSerpPayload(serpCampaign) : undefined;
|
|
120
340
|
if (tui) {
|
|
341
|
+
const securitySummary = summarizePageSecurity(result.pages);
|
|
121
342
|
tui.setComplete({
|
|
343
|
+
security: securitySummary,
|
|
122
344
|
duplicateTitles: result.summary.duplicateTitles,
|
|
123
345
|
duplicateDescriptions: result.summary.duplicateDescriptions,
|
|
124
346
|
orphanPages: result.summary.orphanPages,
|
|
125
347
|
pagesWithErrors: result.summary.pagesWithErrors,
|
|
126
348
|
});
|
|
349
|
+
tui.setSecuritySummary(securitySummary);
|
|
350
|
+
if (serpPayload) {
|
|
351
|
+
tui.setSerpSummary({
|
|
352
|
+
queriesRequested: serpPayload.summary.queriesRequested,
|
|
353
|
+
queriesFound: serpPayload.summary.queriesFound,
|
|
354
|
+
avgTopPosition: serpPayload.summary.avgTopPosition,
|
|
355
|
+
top3Count: serpPayload.summary.top3Count,
|
|
356
|
+
top10Count: serpPayload.summary.top10Count,
|
|
357
|
+
topOrganicCompetitors: (serpPayload.summary.topOrganicCompetitors || [])
|
|
358
|
+
.slice(0, 4)
|
|
359
|
+
.map((item) => ({
|
|
360
|
+
domain: item.domain,
|
|
361
|
+
matchedKeywords: item.matchedKeywords,
|
|
362
|
+
totalOutperformedQueries: item.totalOutperformedQueries || 0,
|
|
363
|
+
})),
|
|
364
|
+
topPages: serpPayload.pageComparison.slice(0, 5).map((item) => ({
|
|
365
|
+
pageUrl: item.pageUrl,
|
|
366
|
+
found: item.found,
|
|
367
|
+
tracked: item.tracked,
|
|
368
|
+
appearanceRate: toNumber(item.appearanceRate),
|
|
369
|
+
})),
|
|
370
|
+
});
|
|
371
|
+
}
|
|
127
372
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
128
373
|
tui.stop();
|
|
129
374
|
await tui.waitUntilExit();
|
|
130
375
|
}
|
|
131
376
|
if (formatJson) {
|
|
377
|
+
const securitySummary = summarizePageSecurity(result.pages);
|
|
132
378
|
const responseTimes = result.pages.filter(p => p.duration > 0).map(p => p.duration);
|
|
133
379
|
const avgResponseTime = responseTimes.length > 0
|
|
134
380
|
? Math.round(responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length)
|
|
@@ -172,7 +418,9 @@ Spider starting: ${startUrl}`));
|
|
|
172
418
|
duplicateDescriptions: result.summary.duplicateDescriptions,
|
|
173
419
|
duplicateH1s: result.summary.duplicateH1s,
|
|
174
420
|
orphanPages: result.summary.orphanPages,
|
|
421
|
+
security: securitySummary,
|
|
175
422
|
},
|
|
423
|
+
...(serpPayload ? { serp: serpPayload } : {}),
|
|
176
424
|
discovery: result.discovery ? {
|
|
177
425
|
humans: result.discovery.humans.found,
|
|
178
426
|
llms: result.discovery.llms.found,
|
|
@@ -227,10 +475,12 @@ Spider starting: ${startUrl}`));
|
|
|
227
475
|
return;
|
|
228
476
|
}
|
|
229
477
|
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
478
|
+
const securitySummary = summarizePageSecurity(result.pages);
|
|
230
479
|
console.log(colors.green(`\n✔ SEO Spider complete`) + colors.gray(` (${(result.duration / 1000).toFixed(1)}s)`));
|
|
231
480
|
console.log(` ${colors.cyan('Pages crawled')}: ${result.pages.length}`);
|
|
232
481
|
console.log(` ${colors.cyan('Unique URLs')}: ${result.visited.size}`);
|
|
233
482
|
console.log(` ${colors.cyan('Avg SEO Score')}: ${result.summary.avgScore}/100`);
|
|
483
|
+
printSecuritySummary(securitySummary, securitySummary.pages, colors);
|
|
234
484
|
const responseTimes = result.pages.filter(p => p.duration > 0).map(p => p.duration);
|
|
235
485
|
const avgResponseTime = responseTimes.length > 0
|
|
236
486
|
? Math.round(responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length)
|
|
@@ -381,13 +631,16 @@ Spider starting: ${startUrl}`));
|
|
|
381
631
|
const score = page.seoReport?.score || 0;
|
|
382
632
|
const grade = page.seoReport?.grade || '?';
|
|
383
633
|
const path = new URL(page.url).pathname;
|
|
384
|
-
const scoreColor = score
|
|
634
|
+
const scoreColor = getScoreColor(score, colors);
|
|
385
635
|
console.log(` ${scoreColor(`${score.toString().padStart(3)}`)} ${colors.gray(`[${grade}]`)} ${path.slice(0, 50)}`);
|
|
386
636
|
}
|
|
387
637
|
if (uniquePages.length > 5) {
|
|
388
638
|
console.log(colors.gray(` ... and ${uniquePages.length - 5} more pages`));
|
|
389
639
|
}
|
|
390
640
|
}
|
|
641
|
+
if (serpPayload && serpCampaign) {
|
|
642
|
+
printSerpCampaignSummary(serpCampaign, colors);
|
|
643
|
+
}
|
|
391
644
|
if (outputFile) {
|
|
392
645
|
console.log(colors.green(`\n Report saved to: ${outputFile}`));
|
|
393
646
|
}
|
|
@@ -399,7 +652,18 @@ Spider starting: ${startUrl}`));
|
|
|
399
652
|
maxPages: limit,
|
|
400
653
|
concurrency: concurrency,
|
|
401
654
|
sameDomain: true,
|
|
402
|
-
|
|
655
|
+
timeout,
|
|
656
|
+
delay,
|
|
657
|
+
transport,
|
|
658
|
+
preferCurlFirst,
|
|
659
|
+
maxRetryAttempts,
|
|
660
|
+
baseRetryDelayMs,
|
|
661
|
+
maxRetryDelayMs,
|
|
662
|
+
retryBackoffMultiplier,
|
|
663
|
+
retryJitterMs,
|
|
664
|
+
maxDomainBlockStrikes,
|
|
665
|
+
rotateUserAgent,
|
|
666
|
+
randomizeHeaders,
|
|
403
667
|
respectRobotsTxt,
|
|
404
668
|
extract: extractSelectors,
|
|
405
669
|
include: includePatterns?.map(p => new RegExp(p)),
|
|
@@ -410,6 +674,7 @@ Spider starting: ${startUrl}`));
|
|
|
410
674
|
});
|
|
411
675
|
const result = await spider.crawl(startUrl);
|
|
412
676
|
if (formatJson) {
|
|
677
|
+
const securitySummary = summarizePageSecurity(result.pages);
|
|
413
678
|
const jsonOutput = {
|
|
414
679
|
startUrl: result.startUrl,
|
|
415
680
|
startTime: result.startTime,
|
|
@@ -425,6 +690,7 @@ Spider starting: ${startUrl}`));
|
|
|
425
690
|
successCount: result.pages.filter(p => !p.error).length,
|
|
426
691
|
errorCount: result.errors.length,
|
|
427
692
|
uniqueUrls: result.visited.size,
|
|
693
|
+
security: securitySummary,
|
|
428
694
|
},
|
|
429
695
|
pages: result.pages.map(p => ({
|
|
430
696
|
url: p.url,
|
|
@@ -464,10 +730,12 @@ Spider starting: ${startUrl}`));
|
|
|
464
730
|
return;
|
|
465
731
|
}
|
|
466
732
|
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
733
|
+
const securitySummary = summarizePageSecurity(result.pages);
|
|
467
734
|
console.log(colors.green(`\n✔ Spider complete`) + colors.gray(` (${(result.duration / 1000).toFixed(1)}s)`));
|
|
468
735
|
console.log(` ${colors.cyan('Pages crawled')}: ${result.pages.length}`);
|
|
469
736
|
console.log(` ${colors.cyan('Unique URLs')}: ${result.visited.size}`);
|
|
470
737
|
console.log(` ${colors.cyan('Errors')}: ${result.errors.length}`);
|
|
738
|
+
printSecuritySummary(securitySummary, securitySummary.pages, colors);
|
|
471
739
|
const byDepth = new Map();
|
|
472
740
|
for (const page of result.pages) {
|
|
473
741
|
byDepth.set(page.depth, (byDepth.get(page.depth) || 0) + 1);
|
|
@@ -544,6 +812,7 @@ Spider starting: ${startUrl}`));
|
|
|
544
812
|
successCount: result.pages.filter(p => !p.error).length,
|
|
545
813
|
errorCount: result.errors.length,
|
|
546
814
|
uniqueUrls: result.visited.size,
|
|
815
|
+
security: securitySummary,
|
|
547
816
|
},
|
|
548
817
|
pages: result.pages.map(p => ({
|
|
549
818
|
url: p.url,
|
|
@@ -586,7 +855,8 @@ Spider starting: ${startUrl}`));
|
|
|
586
855
|
console.log('');
|
|
587
856
|
}
|
|
588
857
|
catch (error) {
|
|
589
|
-
|
|
858
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
859
|
+
console.error(colors.red(`\nSpider failed: ${message}`));
|
|
590
860
|
process.exit(1);
|
|
591
861
|
}
|
|
592
862
|
}
|
|
@@ -667,6 +937,150 @@ export function registerSpiderCommand(program) {
|
|
|
667
937
|
.option('no-sitemap', {
|
|
668
938
|
short: 'N',
|
|
669
939
|
description: 'Disable sitemap.xml crawling (sitemap is used by default in SEO mode)',
|
|
940
|
+
})
|
|
941
|
+
.option('transport', {
|
|
942
|
+
type: 'string',
|
|
943
|
+
default: 'auto',
|
|
944
|
+
description: 'HTTP transport for crawler (auto | undici | curl)',
|
|
945
|
+
})
|
|
946
|
+
.option('prefer-curl-first', {
|
|
947
|
+
type: 'boolean',
|
|
948
|
+
default: true,
|
|
949
|
+
description: 'Prefer curl-impersonate before undici in auto mode',
|
|
950
|
+
})
|
|
951
|
+
.option('timeout', {
|
|
952
|
+
type: 'number',
|
|
953
|
+
default: 10000,
|
|
954
|
+
description: 'Request timeout in ms',
|
|
955
|
+
})
|
|
956
|
+
.option('delay', {
|
|
957
|
+
type: 'number',
|
|
958
|
+
default: 100,
|
|
959
|
+
description: 'Request delay in ms',
|
|
960
|
+
})
|
|
961
|
+
.option('max-retry-attempts', {
|
|
962
|
+
type: 'number',
|
|
963
|
+
default: 3,
|
|
964
|
+
description: 'Max retries for failed or blocked requests',
|
|
965
|
+
})
|
|
966
|
+
.option('base-retry-delay-ms', {
|
|
967
|
+
type: 'number',
|
|
968
|
+
default: 1000,
|
|
969
|
+
description: 'Base retry delay in ms',
|
|
970
|
+
})
|
|
971
|
+
.option('max-retry-delay-ms', {
|
|
972
|
+
type: 'number',
|
|
973
|
+
default: 12000,
|
|
974
|
+
description: 'Maximum retry delay in ms',
|
|
975
|
+
})
|
|
976
|
+
.option('retry-backoff-multiplier', {
|
|
977
|
+
type: 'number',
|
|
978
|
+
default: 2,
|
|
979
|
+
description: 'Retry backoff multiplier',
|
|
980
|
+
})
|
|
981
|
+
.option('retry-jitter-ms', {
|
|
982
|
+
type: 'number',
|
|
983
|
+
default: 250,
|
|
984
|
+
description: 'Random retry jitter in ms',
|
|
985
|
+
})
|
|
986
|
+
.option('max-domain-block-strikes', {
|
|
987
|
+
type: 'number',
|
|
988
|
+
default: 2,
|
|
989
|
+
description: 'Number of strikes before forcing curl in auto mode',
|
|
990
|
+
})
|
|
991
|
+
.option('rotate-user-agent', {
|
|
992
|
+
type: 'boolean',
|
|
993
|
+
default: true,
|
|
994
|
+
description: 'Rotate user-agent per request',
|
|
995
|
+
})
|
|
996
|
+
.option('randomize-headers', {
|
|
997
|
+
type: 'boolean',
|
|
998
|
+
default: true,
|
|
999
|
+
description: 'Randomize request headers',
|
|
1000
|
+
})
|
|
1001
|
+
.option('serp', {
|
|
1002
|
+
type: 'boolean',
|
|
1003
|
+
description: 'Enable SERP campaign for top extracted keywords',
|
|
1004
|
+
})
|
|
1005
|
+
.option('serp-top-keywords', {
|
|
1006
|
+
type: 'number',
|
|
1007
|
+
description: 'Top keywords per page to use as campaign seeds',
|
|
1008
|
+
example: '20',
|
|
1009
|
+
})
|
|
1010
|
+
.option('serp-query-limit', {
|
|
1011
|
+
type: 'number',
|
|
1012
|
+
description: 'Number of SERP queries to execute',
|
|
1013
|
+
example: '10',
|
|
1014
|
+
})
|
|
1015
|
+
.option('serp-results-per-query', {
|
|
1016
|
+
type: 'number',
|
|
1017
|
+
description: 'Results fetched per SERP query',
|
|
1018
|
+
example: '10',
|
|
1019
|
+
})
|
|
1020
|
+
.option('serp-concurrency', {
|
|
1021
|
+
type: 'number',
|
|
1022
|
+
description: 'Number of parallel SERP queries',
|
|
1023
|
+
example: '4',
|
|
1024
|
+
})
|
|
1025
|
+
.option('serp-delay-ms', {
|
|
1026
|
+
type: 'number',
|
|
1027
|
+
description: 'Base delay in ms between SERP queries',
|
|
1028
|
+
example: '450',
|
|
1029
|
+
})
|
|
1030
|
+
.option('serp-delay-jitter-ms', {
|
|
1031
|
+
type: 'number',
|
|
1032
|
+
description: 'Random delay jitter in ms between SERP queries',
|
|
1033
|
+
example: '250',
|
|
1034
|
+
})
|
|
1035
|
+
.option('serp-captcha-cooldown-ms', {
|
|
1036
|
+
type: 'number',
|
|
1037
|
+
description: 'Cooldown in ms after captcha before continuing SERP queries',
|
|
1038
|
+
example: '1200',
|
|
1039
|
+
})
|
|
1040
|
+
.option('serp-retry-count', {
|
|
1041
|
+
type: 'number',
|
|
1042
|
+
description: 'Retry count per SERP query',
|
|
1043
|
+
example: '1',
|
|
1044
|
+
})
|
|
1045
|
+
.option('serp-retry-delay-ms', {
|
|
1046
|
+
type: 'number',
|
|
1047
|
+
description: 'Base delay in ms for SERP query retries',
|
|
1048
|
+
example: '900',
|
|
1049
|
+
})
|
|
1050
|
+
.option('serp-transport', {
|
|
1051
|
+
type: 'string',
|
|
1052
|
+
default: 'curl',
|
|
1053
|
+
description: 'Search transport (auto | undici | curl)',
|
|
1054
|
+
example: 'curl',
|
|
1055
|
+
})
|
|
1056
|
+
.option('serp-source', {
|
|
1057
|
+
type: 'string',
|
|
1058
|
+
default: 'google',
|
|
1059
|
+
description: 'Search source/provider. Current options: google',
|
|
1060
|
+
example: 'google',
|
|
1061
|
+
})
|
|
1062
|
+
.option('serp-timeout', {
|
|
1063
|
+
type: 'number',
|
|
1064
|
+
description: 'Search timeout in ms',
|
|
1065
|
+
example: '15000',
|
|
1066
|
+
})
|
|
1067
|
+
.option('serp-country', {
|
|
1068
|
+
type: 'string',
|
|
1069
|
+
description: 'Search country/region',
|
|
1070
|
+
example: 'br',
|
|
1071
|
+
})
|
|
1072
|
+
.option('serp-gl', {
|
|
1073
|
+
type: 'string',
|
|
1074
|
+
description: 'Google GL parameter',
|
|
1075
|
+
})
|
|
1076
|
+
.option('serp-hl', {
|
|
1077
|
+
type: 'string',
|
|
1078
|
+
description: 'Google HL parameter',
|
|
1079
|
+
})
|
|
1080
|
+
.option('serp-human-profile', {
|
|
1081
|
+
type: 'string',
|
|
1082
|
+
default: 'chrome',
|
|
1083
|
+
description: 'Human-like SERP query profile: chrome or off',
|
|
670
1084
|
})
|
|
671
1085
|
.example('rek spider example.com', 'Basic crawl')
|
|
672
1086
|
.example('rek spider example.com -d 3 -l 50', 'Depth 3, max 50 pages')
|
|
@@ -677,25 +1091,41 @@ export function registerSpiderCommand(program) {
|
|
|
677
1091
|
.example('rek spider example.com -E "a:href" --json', 'Extract all links as JSON')
|
|
678
1092
|
.example('rek spider example.com --include "^/blog/"', 'Only crawl /blog/ paths')
|
|
679
1093
|
.example('rek spider example.com --exclude "/admin/"', 'Skip /admin/ paths')
|
|
1094
|
+
.example('rek spider example.com --seo --serp', 'Run SEO + SERP campaign')
|
|
1095
|
+
.example('rek spider example.com --seo --serp --serp-top-keywords 12 --serp-query-limit 10 --serp-results-per-query 10', 'Tuned SERP campaign')
|
|
680
1096
|
.example('rek spider example.com --jsonl -o crawl.jsonl', 'Stream to JSONL file')
|
|
681
1097
|
.example('rek spider example.com -L | jq -c', 'Stream and process with jq')
|
|
682
|
-
.action(async (url,
|
|
1098
|
+
.action(async (url, _args, cmdObj) => {
|
|
683
1099
|
const options = cmdObj.opts ? cmdObj.opts() : {};
|
|
1100
|
+
const optionBag = options;
|
|
684
1101
|
await runSpider({
|
|
685
1102
|
url,
|
|
686
|
-
depth:
|
|
687
|
-
limit:
|
|
688
|
-
concurrency:
|
|
689
|
-
output:
|
|
690
|
-
focus:
|
|
691
|
-
seo:
|
|
692
|
-
robots:
|
|
693
|
-
json:
|
|
694
|
-
jsonl:
|
|
695
|
-
extract:
|
|
696
|
-
include:
|
|
697
|
-
exclude:
|
|
698
|
-
noSitemap:
|
|
1103
|
+
depth: toNonNegativeInt(getOptionValue(optionBag, 'depth'), 10),
|
|
1104
|
+
limit: toNonNegativeInt(getOptionValue(optionBag, 'limit'), 1000),
|
|
1105
|
+
concurrency: toNonNegativeInt(getOptionValue(optionBag, 'concurrency'), 5),
|
|
1106
|
+
output: toOptionString(getOptionValue(optionBag, 'output')),
|
|
1107
|
+
focus: normalizeSpiderFocus(getOptionValue(optionBag, 'focus')),
|
|
1108
|
+
seo: toBoolean(getOptionValue(optionBag, 'seo')),
|
|
1109
|
+
robots: toBoolean(getOptionValue(optionBag, 'robots')),
|
|
1110
|
+
json: toBoolean(getOptionValue(optionBag, 'json')),
|
|
1111
|
+
jsonl: toBoolean(getOptionValue(optionBag, 'jsonl')),
|
|
1112
|
+
extract: toStringOrStringArray(getOptionValue(optionBag, 'extract')),
|
|
1113
|
+
include: toStringOrStringArray(getOptionValue(optionBag, 'include')),
|
|
1114
|
+
exclude: toStringOrStringArray(getOptionValue(optionBag, 'exclude')),
|
|
1115
|
+
noSitemap: toBoolean(getOptionValue(optionBag, 'noSitemap'), false) || toBoolean(getOptionValue(optionBag, 'no-sitemap'), false),
|
|
1116
|
+
transport: toSpiderTransport(getOptionValue(optionBag, 'transport')) || 'auto',
|
|
1117
|
+
preferCurlFirst: toBoolean(getOptionValue(optionBag, 'preferCurlFirst', 'prefer-curl-first'), true),
|
|
1118
|
+
timeout: toNonNegativeInt(getOptionValue(optionBag, 'timeout'), 10_000),
|
|
1119
|
+
delay: toNonNegativeInt(getOptionValue(optionBag, 'delay'), 100),
|
|
1120
|
+
maxRetryAttempts: toNonNegativeInt(getOptionValue(optionBag, 'maxRetryAttempts', 'max-retry-attempts'), 3),
|
|
1121
|
+
baseRetryDelayMs: toNonNegativeInt(getOptionValue(optionBag, 'baseRetryDelayMs', 'base-retry-delay-ms'), 1_000),
|
|
1122
|
+
maxRetryDelayMs: toNonNegativeInt(getOptionValue(optionBag, 'maxRetryDelayMs', 'max-retry-delay-ms'), 12_000),
|
|
1123
|
+
retryBackoffMultiplier: toNonNegativeInt(getOptionValue(optionBag, 'retryBackoffMultiplier', 'retry-backoff-multiplier'), 2),
|
|
1124
|
+
retryJitterMs: toNonNegativeInt(getOptionValue(optionBag, 'retryJitterMs', 'retry-jitter-ms'), 250),
|
|
1125
|
+
maxDomainBlockStrikes: toNonNegativeInt(getOptionValue(optionBag, 'maxDomainBlockStrikes', 'max-domain-block-strikes'), 2),
|
|
1126
|
+
rotateUserAgent: toBoolean(getOptionValue(optionBag, 'rotateUserAgent', 'rotate-user-agent'), true),
|
|
1127
|
+
randomizeHeaders: toBoolean(getOptionValue(optionBag, 'randomizeHeaders', 'randomize-headers'), true),
|
|
1128
|
+
serpConfig: parseSpiderSerpConfig(options),
|
|
699
1129
|
});
|
|
700
1130
|
});
|
|
701
1131
|
}
|