@lmcc-dev/mult-fetch-mcp-server 1.0.1

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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +512 -0
  3. package/README.zh.md +510 -0
  4. package/dist/index.js +21 -0
  5. package/dist/src/cli-language.js +78 -0
  6. package/dist/src/client.js +284 -0
  7. package/dist/src/index.js +21 -0
  8. package/dist/src/lib/BrowserFetcher.js +753 -0
  9. package/dist/src/lib/NodeFetcher.js +428 -0
  10. package/dist/src/lib/example.js +93 -0
  11. package/dist/src/lib/i18n/index.js +36 -0
  12. package/dist/src/lib/i18n/locales/en/browser.js +53 -0
  13. package/dist/src/lib/i18n/locales/en/client.js +40 -0
  14. package/dist/src/lib/i18n/locales/en/errors.js +18 -0
  15. package/dist/src/lib/i18n/locales/en/fetcher.js +67 -0
  16. package/dist/src/lib/i18n/locales/en/index.js +26 -0
  17. package/dist/src/lib/i18n/locales/en/node.js +42 -0
  18. package/dist/src/lib/i18n/locales/en/prompts.js +72 -0
  19. package/dist/src/lib/i18n/locales/en/resources.js +47 -0
  20. package/dist/src/lib/i18n/locales/en/server.js +32 -0
  21. package/dist/src/lib/i18n/locales/en/tools.js +25 -0
  22. package/dist/src/lib/i18n/locales/zh/browser.js +53 -0
  23. package/dist/src/lib/i18n/locales/zh/client.js +39 -0
  24. package/dist/src/lib/i18n/locales/zh/errors.js +18 -0
  25. package/dist/src/lib/i18n/locales/zh/fetcher.js +67 -0
  26. package/dist/src/lib/i18n/locales/zh/index.js +26 -0
  27. package/dist/src/lib/i18n/locales/zh/node.js +42 -0
  28. package/dist/src/lib/i18n/locales/zh/prompts.js +72 -0
  29. package/dist/src/lib/i18n/locales/zh/resources.js +47 -0
  30. package/dist/src/lib/i18n/locales/zh/server.js +32 -0
  31. package/dist/src/lib/i18n/locales/zh/tools.js +25 -0
  32. package/dist/src/lib/i18n/logger.js +57 -0
  33. package/dist/src/lib/logger.js +126 -0
  34. package/dist/src/lib/server/browser.js +86 -0
  35. package/dist/src/lib/server/fetcher.js +130 -0
  36. package/dist/src/lib/server/index.js +73 -0
  37. package/dist/src/lib/server/logger.js +23 -0
  38. package/dist/src/lib/server/prompts.js +207 -0
  39. package/dist/src/lib/server/resources.js +171 -0
  40. package/dist/src/lib/server/tools.js +346 -0
  41. package/dist/src/lib/server/types.js +6 -0
  42. package/dist/src/lib/types.js +35 -0
  43. package/dist/src/mcp-server.js +22 -0
  44. package/dist/tests/test-direct-client.js +129 -0
  45. package/dist/tests/test-mcp-methods.js +114 -0
  46. package/dist/tests/test-mcp.js +145 -0
  47. package/dist/tests/test-mini4k.js +147 -0
  48. package/images/example_en.png +0 -0
  49. package/images/example_zh.png +0 -0
  50. package/package.json +80 -0
@@ -0,0 +1,753 @@
1
+ /**
2
+ * Author: Martin <lmccc.dev@gmail.com>
3
+ * Co-Author: AI Assistant (Claude)
4
+ * Description: This code was collaboratively developed by Martin and AI Assistant.
5
+ */
6
+ import puppeteerExtra from 'puppeteer-extra';
7
+ import TurndownService from 'turndown';
8
+ import { execSync } from 'child_process';
9
+ import { t } from './i18n/index.js';
10
+ import StealthPlugin from 'puppeteer-extra-plugin-stealth';
11
+ import { log, COMPONENTS } from './logger.js';
12
+ import { JSDOM } from 'jsdom';
13
+ // 添加stealth插件
14
+ // @ts-ignore - 忽略puppeteer-extra的类型错误
15
+ puppeteerExtra.use(StealthPlugin());
16
+ /**
17
+ * 浏览器模式获取器类 (Browser mode fetcher class)
18
+ * 使用Puppeteer实现浏览器模式的网页获取 (Implements webpage fetching in browser mode using Puppeteer)
19
+ */
20
+ export class BrowserFetcher {
21
+ /**
22
+ * 浏览器实例 (Browser instance)
23
+ * 静态单例,避免重复创建 (Static singleton to avoid repeated creation)
24
+ */
25
+ static browser = null;
26
+ /**
27
+ * 浏览器启动状态标志 (Browser startup status flag)
28
+ * 防止并发启动多个浏览器实例 (Prevents concurrent startup of multiple browser instances)
29
+ */
30
+ static browserStarting = false;
31
+ /**
32
+ * 浏览器启动Promise (Browser startup Promise)
33
+ * 用于等待浏览器启动完成 (Used to wait for browser startup completion)
34
+ */
35
+ static browserStartPromise = null;
36
+ /**
37
+ * Cookie存储 (Cookie storage)
38
+ * 存储不同域名的Cookie (Store cookies for different domains)
39
+ */
40
+ static cookieStore = {};
41
+ /**
42
+ * 上次内存检查时间 (Last memory check time)
43
+ */
44
+ static lastMemoryCheck = 0;
45
+ /**
46
+ * 内存检查间隔(毫秒) (Memory check interval in milliseconds)
47
+ */
48
+ static memoryCheckInterval = 60000;
49
+ /**
50
+ * 检查内存使用情况 (Check memory usage)
51
+ * 定期监控浏览器内存使用,必要时关闭浏览器释放资源 (Periodically monitor browser memory usage, close browser to release resources when necessary)
52
+ * @param debug 是否启用调试输出 (Whether to enable debug output)
53
+ */
54
+ static checkMemoryUsage(debug = false) {
55
+ const now = Date.now();
56
+ // 每隔一段时间检查一次内存 (Check memory at regular intervals)
57
+ if (now - this.lastMemoryCheck < this.memoryCheckInterval) {
58
+ return;
59
+ }
60
+ this.lastMemoryCheck = now;
61
+ try {
62
+ const memoryUsage = process.memoryUsage();
63
+ const heapUsed = Math.round(memoryUsage.heapUsed / 1024 / 1024);
64
+ const heapTotal = Math.round(memoryUsage.heapTotal / 1024 / 1024);
65
+ const rss = Math.round(memoryUsage.rss / 1024 / 1024);
66
+ // 记录内存使用情况
67
+ log('browser.memoryUsage', debug, { heapUsed, heapTotal, rss }, COMPONENTS.BROWSER_FETCH);
68
+ // 如果内存使用过高,记录警告
69
+ if (heapUsed > 500 || rss > 1000) {
70
+ log('browser.memoryTooHigh', debug, { heapUsed, heapTotal, rss }, COMPONENTS.BROWSER_FETCH);
71
+ }
72
+ }
73
+ catch (error) {
74
+ log('browser.memoryCheckError', debug, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
75
+ }
76
+ }
77
+ /**
78
+ * 获取系统代理设置 (Get system proxy settings)
79
+ * @param useSystemProxy 是否使用系统代理 (Whether to use system proxy)
80
+ * @returns 系统代理URL或undefined (System proxy URL or undefined)
81
+ */
82
+ static getSystemProxy(useSystemProxy = true, debug = false) {
83
+ // 如果不使用系统代理,直接返回undefined (If not using system proxy, return undefined directly)
84
+ if (!useSystemProxy) {
85
+ log('fetcher.systemProxyDisabled', debug, {}, COMPONENTS.BROWSER_FETCH);
86
+ return undefined;
87
+ }
88
+ // 检查所有可能的代理环境变量 (Check all possible proxy environment variables)
89
+ const proxyVars = [
90
+ 'HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy',
91
+ 'ALL_PROXY', 'all_proxy', 'NO_PROXY', 'no_proxy'
92
+ ];
93
+ // 输出所有环境变量的值,帮助调试 (Output all environment variable values to help debugging)
94
+ log('fetcher.checkingProxyEnv', debug, {}, COMPONENTS.BROWSER_FETCH);
95
+ for (const varName of proxyVars) {
96
+ log('fetcher.envVarValue', debug, {
97
+ envVar: varName,
98
+ value: process.env[varName] || t('fetcher.notSet')
99
+ }, COMPONENTS.BROWSER_FETCH);
100
+ }
101
+ // 尝试获取代理设置 (Try to get proxy settings)
102
+ const proxyUrl = process.env.HTTP_PROXY || process.env.HTTPS_PROXY ||
103
+ process.env.http_proxy || process.env.https_proxy ||
104
+ process.env.ALL_PROXY || process.env.all_proxy;
105
+ if (proxyUrl) {
106
+ log('fetcher.foundSystemProxy', debug, { proxy: proxyUrl }, COMPONENTS.BROWSER_FETCH);
107
+ return proxyUrl;
108
+ }
109
+ // 尝试从系统命令获取代理设置
110
+ try {
111
+ let shellOutput = '';
112
+ if (process.platform === 'win32') {
113
+ // Windows 系统使用 set 命令,忽略错误 (Windows systems use set command, ignore errors)
114
+ try {
115
+ shellOutput = execSync('set http_proxy & set https_proxy & set HTTP_PROXY & set HTTPS_PROXY', { encoding: 'utf8' });
116
+ }
117
+ catch (winError) {
118
+ log('fetcher.errorGettingWindowsEnvVars', debug, { error: winError.toString() }, COMPONENTS.BROWSER_FETCH);
119
+ }
120
+ }
121
+ else {
122
+ // Unix 系统使用 env 命令,忽略错误 (Unix systems use env command, ignore errors)
123
+ try {
124
+ // 使用 env 命令获取所有环境变量,不使用 grep 过滤,避免在没有匹配时返回非零退出码
125
+ shellOutput = execSync('env', { encoding: 'utf8' });
126
+ // 在代码中过滤代理相关的环境变量
127
+ shellOutput = shellOutput.split('\n')
128
+ .filter(line => line.toLowerCase().includes('proxy'))
129
+ .join('\n');
130
+ }
131
+ catch (unixError) {
132
+ log('fetcher.errorGettingUnixEnvVars', debug, { error: unixError.toString() }, COMPONENTS.BROWSER_FETCH);
133
+ }
134
+ }
135
+ if (shellOutput.trim()) {
136
+ log('fetcher.systemCommandProxySettings', debug, { output: shellOutput.trim() }, COMPONENTS.BROWSER_FETCH);
137
+ // 解析输出找到代理 URL
138
+ const proxyMatch = shellOutput.match(/(?:HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy)=([^\s]+)/i);
139
+ if (proxyMatch && proxyMatch[1]) {
140
+ const systemProxyUrl = proxyMatch[1];
141
+ log('fetcher.foundProxyFromCommand', debug, { proxy: systemProxyUrl }, COMPONENTS.BROWSER_FETCH);
142
+ return systemProxyUrl;
143
+ }
144
+ }
145
+ else {
146
+ log('fetcher.noSystemProxyFound', debug, {}, COMPONENTS.BROWSER_FETCH);
147
+ }
148
+ }
149
+ catch (error) {
150
+ log('fetcher.errorGettingSystemEnvVars', debug, { error: error.toString() }, COMPONENTS.BROWSER_FETCH);
151
+ }
152
+ // 检查是否设置了 NO_PROXY
153
+ const noProxy = process.env.NO_PROXY || process.env.no_proxy;
154
+ if (noProxy) {
155
+ log('fetcher.foundNoProxy', debug, { noProxy: noProxy }, COMPONENTS.BROWSER_FETCH);
156
+ }
157
+ return undefined;
158
+ }
159
+ // 获取或启动浏览器实例
160
+ static async getBrowser({ proxy, debug = false, useSystemProxy = true }) {
161
+ // 如果浏览器正在启动,等待启动完成
162
+ if (this.browserStarting) {
163
+ log('browser.waiting', debug, {}, COMPONENTS.BROWSER_FETCH);
164
+ return this.browserStartPromise;
165
+ }
166
+ // 如果浏览器已经启动,直接返回
167
+ if (this.browser) {
168
+ log('browser.starting', debug, { debug }, COMPONENTS.BROWSER_FETCH);
169
+ return this.browser;
170
+ }
171
+ this.browserStarting = true;
172
+ this.browserStartPromise = (async () => {
173
+ try {
174
+ // 获取代理设置 - 优先使用用户指定的代理,其次使用系统代理
175
+ const proxyServer = proxy || (useSystemProxy ? this.getSystemProxy(useSystemProxy, debug) : undefined);
176
+ // 详细记录代理设置情况
177
+ if (proxy) {
178
+ log('fetcher.usingSpecifiedProxy', debug, { proxy }, COMPONENTS.BROWSER_FETCH);
179
+ }
180
+ else if (useSystemProxy) {
181
+ log('fetcher.attemptingToUseSystemProxy', debug, {}, COMPONENTS.BROWSER_FETCH);
182
+ }
183
+ else {
184
+ log('fetcher.notUsingProxy', debug, {}, COMPONENTS.BROWSER_FETCH);
185
+ }
186
+ log('fetcher.finalProxyUsed', debug, { proxy: proxyServer || t('fetcher.none') }, COMPONENTS.BROWSER_FETCH);
187
+ // 启动浏览器
188
+ const args = [
189
+ '--no-sandbox',
190
+ '--disable-setuid-sandbox',
191
+ '--disable-dev-shm-usage',
192
+ '--disable-accelerated-2d-canvas',
193
+ '--no-first-run',
194
+ '--no-zygote',
195
+ '--single-process',
196
+ '--disable-gpu',
197
+ '--disable-infobars',
198
+ '--window-position=0,0',
199
+ '--ignore-certifcate-errors',
200
+ '--ignore-certifcate-errors-spki-list',
201
+ '--disable-features=site-per-process',
202
+ '--disable-web-security',
203
+ '--disable-features=IsolateOrigins,site-per-process',
204
+ '--allow-running-insecure-content',
205
+ '--disable-blink-features=AutomationControlled',
206
+ ];
207
+ // 如果有代理,添加代理参数
208
+ if (proxyServer) {
209
+ args.push(`--proxy-server=${proxyServer}`);
210
+ log('browser.usingProxy', debug, { proxy: proxyServer }, COMPONENTS.BROWSER_FETCH);
211
+ }
212
+ // 启动浏览器
213
+ // @ts-ignore - 忽略puppeteer-extra的类型错误
214
+ const browser = await puppeteerExtra.launch({
215
+ headless: 'new',
216
+ args,
217
+ ignoreHTTPSErrors: true,
218
+ defaultViewport: {
219
+ width: 1920,
220
+ height: 1080
221
+ }
222
+ });
223
+ this.browser = browser;
224
+ log('browser.startupSuccess', debug, {}, COMPONENTS.BROWSER_FETCH);
225
+ // 设置浏览器关闭事件
226
+ browser.on('disconnected', () => {
227
+ this.browser = null;
228
+ log('browser.closed', debug, {}, COMPONENTS.BROWSER_FETCH);
229
+ });
230
+ return browser;
231
+ }
232
+ catch (error) {
233
+ this.browser = null;
234
+ log('browser.startingFailed', debug, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
235
+ throw error;
236
+ }
237
+ finally {
238
+ this.browserStarting = false;
239
+ }
240
+ })();
241
+ return this.browserStartPromise;
242
+ }
243
+ // 关闭浏览器
244
+ static async closeBrowser(debug = false) {
245
+ log('browser.closing', debug, { debug }, COMPONENTS.BROWSER_FETCH);
246
+ try {
247
+ if (this.browser) {
248
+ await this.browser.close();
249
+ this.browser = null;
250
+ log('browser.closed', debug, { debug }, COMPONENTS.BROWSER_FETCH);
251
+ }
252
+ }
253
+ catch (error) {
254
+ log('browser.closingError', debug, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
255
+ }
256
+ }
257
+ // 从URL中提取域名
258
+ static getDomain(url) {
259
+ try {
260
+ const urlObj = new URL(url);
261
+ return urlObj.hostname;
262
+ }
263
+ catch (error) {
264
+ return '';
265
+ }
266
+ }
267
+ // 保存Cookie到存储
268
+ static saveCookies(url, cookies) {
269
+ const domain = this.getDomain(url);
270
+ if (domain) {
271
+ this.cookieStore[domain] = cookies;
272
+ }
273
+ }
274
+ // 从存储获取Cookie
275
+ static getCookies(url) {
276
+ const domain = this.getDomain(url);
277
+ return domain ? this.cookieStore[domain] : undefined;
278
+ }
279
+ // 自动滚动页面到底部
280
+ static async autoScroll(page, debug) {
281
+ log('browser.scrolling', debug, { debug }, COMPONENTS.BROWSER_FETCH);
282
+ try {
283
+ await page.evaluate(async () => {
284
+ await new Promise((resolve) => {
285
+ let totalHeight = 0;
286
+ const distance = 100;
287
+ const timer = setInterval(() => {
288
+ const scrollHeight = document.body.scrollHeight;
289
+ window.scrollBy(0, distance);
290
+ totalHeight += distance;
291
+ if (totalHeight >= scrollHeight) {
292
+ clearInterval(timer);
293
+ resolve();
294
+ }
295
+ }, 100);
296
+ });
297
+ });
298
+ log('browser.scrollCompleted', debug, { debug }, COMPONENTS.BROWSER_FETCH);
299
+ }
300
+ catch (error) {
301
+ // 忽略滚动错误,不影响主要功能
302
+ }
303
+ }
304
+ // 检查是否存在Cloudflare保护并尝试绕过
305
+ static async handleCloudflareProtection(page, url, debug) {
306
+ log('browser.checkingCloudflare', debug, { debug }, COMPONENTS.BROWSER_FETCH);
307
+ try {
308
+ // 检查是否存在Cloudflare挑战页面的特征
309
+ const isCloudflarePage = await page.evaluate(() => {
310
+ return document.title.includes('Cloudflare') ||
311
+ document.title.includes('Security Check') ||
312
+ document.body.textContent?.includes('Checking your browser') ||
313
+ document.body.textContent?.includes('Please wait') ||
314
+ document.querySelector('#challenge-form') !== null;
315
+ });
316
+ if (isCloudflarePage) {
317
+ log('browser.cloudflareDetected', debug, { debug }, COMPONENTS.BROWSER_FETCH);
318
+ // 模拟人类行为以通过Cloudflare检查
319
+ log('browser.simulatingHuman', debug, {}, COMPONENTS.BROWSER_FETCH);
320
+ await this.simulateHumanBehavior(page, debug);
321
+ // 等待页面加载完成
322
+ await page.waitForNavigation({
323
+ waitUntil: 'networkidle2',
324
+ timeout: 30000
325
+ }).catch(() => { });
326
+ // 再次检查是否仍在Cloudflare页面
327
+ const stillOnCloudflare = await page.evaluate(() => {
328
+ return document.title.includes('Cloudflare') ||
329
+ document.title.includes('Security Check') ||
330
+ document.body.textContent?.includes('Checking your browser') ||
331
+ document.querySelector('#challenge-form') !== null;
332
+ });
333
+ if (stillOnCloudflare) {
334
+ log('browser.stillOnCloudflare', debug, { debug }, COMPONENTS.BROWSER_FETCH);
335
+ // 再次尝试模拟人类行为
336
+ log('browser.simulatingHuman', debug, {}, COMPONENTS.BROWSER_FETCH);
337
+ await this.simulateHumanBehavior(page, debug);
338
+ // 再次等待页面加载
339
+ await page.waitForNavigation({
340
+ waitUntil: 'networkidle2',
341
+ timeout: 30000
342
+ }).catch(() => { });
343
+ // 最终检查
344
+ const bypassFailed = await page.evaluate(() => {
345
+ return document.title.includes('Cloudflare') ||
346
+ document.title.includes('Security Check') ||
347
+ document.body.textContent?.includes('Checking your browser') ||
348
+ document.querySelector('#challenge-form') !== null;
349
+ });
350
+ if (bypassFailed) {
351
+ log('browser.bypassFailed', debug, { debug }, COMPONENTS.BROWSER_FETCH);
352
+ return false;
353
+ }
354
+ }
355
+ return true;
356
+ }
357
+ return false;
358
+ }
359
+ catch (error) {
360
+ log('browser.cloudflareError', debug, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
361
+ return false;
362
+ }
363
+ }
364
+ // 模拟人类行为以绕过Cloudflare保护
365
+ static async simulateHumanBehavior(page, debug) {
366
+ log('browser.simulatingHuman', debug, {}, COMPONENTS.BROWSER_FETCH);
367
+ try {
368
+ // 随机移动鼠标
369
+ for (let i = 0; i < 5; i++) {
370
+ const x = Math.floor(Math.random() * 500);
371
+ const y = Math.floor(Math.random() * 500);
372
+ await page.mouse.move(x, y);
373
+ await new Promise(resolve => setTimeout(resolve, Math.random() * 500));
374
+ }
375
+ // 随机点击页面
376
+ await page.mouse.click(Math.floor(Math.random() * 500), Math.floor(Math.random() * 500));
377
+ // 随机滚动
378
+ await page.evaluate(() => {
379
+ window.scrollBy(0, Math.floor(Math.random() * 200));
380
+ });
381
+ // 等待一段时间
382
+ await new Promise(resolve => setTimeout(resolve, 2000 + Math.random() * 2000));
383
+ }
384
+ catch (error) {
385
+ log('browser.simulatingHumanError', debug, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
386
+ }
387
+ }
388
+ /**
389
+ * 使用Puppeteer获取网页内容 (Get webpage content using Puppeteer)
390
+ * 浏览器模式获取的底层实现方法 (Low-level implementation method for browser mode fetching)
391
+ * @param params 请求参数 (Request parameters)
392
+ * @returns 获取结果 (Fetch result)
393
+ */
394
+ static async fetch({ url, headers = {}, proxy, timeout = 30000, waitForSelector = 'body', waitForTimeout = 5000, scrollToBottom = false, saveCookies = true, closeBrowser = false, useSystemProxy = true, debug = false, }) {
395
+ log('browser.fetchRequest', debug, {
396
+ url,
397
+ waitForSelector,
398
+ waitForTimeout,
399
+ scrollToBottom,
400
+ saveCookies,
401
+ closeBrowser,
402
+ useSystemProxy
403
+ }, COMPONENTS.BROWSER_FETCH);
404
+ // 检查内存使用情况
405
+ this.checkMemoryUsage(debug);
406
+ // 获取代理设置 - 优先使用用户指定的代理,其次使用系统代理
407
+ const effectiveProxy = proxy || (useSystemProxy ? this.getSystemProxy(useSystemProxy, debug) : undefined);
408
+ // 详细记录代理设置情况
409
+ if (proxy) {
410
+ log('fetcher.usingSpecifiedProxy', debug, { proxy }, COMPONENTS.BROWSER_FETCH);
411
+ }
412
+ else if (useSystemProxy) {
413
+ log('fetcher.attemptingToUseSystemProxy', debug, {}, COMPONENTS.BROWSER_FETCH);
414
+ }
415
+ else {
416
+ log('fetcher.notUsingProxy', debug, {}, COMPONENTS.BROWSER_FETCH);
417
+ }
418
+ log('fetcher.finalProxyUsed', debug, { proxy: effectiveProxy || t('fetcher.none') }, COMPONENTS.BROWSER_FETCH);
419
+ try {
420
+ // 如果只是要关闭浏览器,不需要获取内容
421
+ if (url === 'about:blank' && closeBrowser) {
422
+ await this.closeBrowser(debug);
423
+ return {
424
+ content: [{ type: 'text', text: 'Browser closed successfully' }],
425
+ isError: false
426
+ };
427
+ }
428
+ // 获取浏览器实例
429
+ const browser = await this.getBrowser({ proxy: effectiveProxy, debug, useSystemProxy });
430
+ // 创建新页面
431
+ const page = await browser.newPage();
432
+ // 设置页面超时
433
+ page.setDefaultTimeout(timeout);
434
+ // 设置用户代理
435
+ await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36');
436
+ // 设置请求头
437
+ await page.setExtraHTTPHeaders(headers);
438
+ // 如果有存储的cookies,使用它们
439
+ const domain = this.getDomain(url);
440
+ const storedCookies = this.getCookies(domain);
441
+ if (storedCookies && saveCookies) {
442
+ log('browser.usingStoredCookies', debug, { domain: this.getDomain(url) }, COMPONENTS.BROWSER_FETCH);
443
+ await page.setExtraHTTPHeaders({
444
+ 'Cookie': storedCookies
445
+ });
446
+ }
447
+ // 如果使用代理,记录日志
448
+ if (effectiveProxy) {
449
+ log('browser.usingProxy', debug, { proxy: effectiveProxy }, COMPONENTS.BROWSER_FETCH);
450
+ }
451
+ // 导航到URL
452
+ log('browser.navigating', debug, { debug }, COMPONENTS.BROWSER_FETCH);
453
+ await page.goto(url, {
454
+ waitUntil: 'networkidle2',
455
+ timeout: timeout
456
+ });
457
+ // 尝试处理Cloudflare保护
458
+ const bypassedCloudflare = await this.handleCloudflareProtection(page, url, debug);
459
+ if (!bypassedCloudflare) {
460
+ log('browser.continuingWithoutBypass', debug, { debug }, COMPONENTS.BROWSER_FETCH);
461
+ }
462
+ // 等待选择器
463
+ log('browser.waitingForSelector', debug, { selector: waitForSelector, debug }, COMPONENTS.BROWSER_FETCH);
464
+ await page.waitForSelector(waitForSelector, {
465
+ timeout: timeout
466
+ }).catch(() => { });
467
+ // 等待额外时间
468
+ log('browser.waitingForTimeout', debug, { timeout: waitForTimeout, debug }, COMPONENTS.BROWSER_FETCH);
469
+ await new Promise(resolve => setTimeout(resolve, waitForTimeout));
470
+ // 如果需要滚动到底部
471
+ if (scrollToBottom) {
472
+ log('browser.scrolling', debug, { debug }, COMPONENTS.BROWSER_FETCH);
473
+ await this.autoScroll(page, debug);
474
+ }
475
+ // 获取页面内容
476
+ log('browser.gettingContent', debug, { debug }, COMPONENTS.BROWSER_FETCH);
477
+ const content = await page.content();
478
+ // 如果需要保存cookies
479
+ if (saveCookies) {
480
+ log('browser.savingCookies', debug, { debug }, COMPONENTS.BROWSER_FETCH);
481
+ const cookies = await page.evaluate(() => document.cookie);
482
+ if (cookies) {
483
+ this.saveCookies(url, cookies);
484
+ }
485
+ }
486
+ // 记录内容长度
487
+ const contentLength = content.length;
488
+ log('browser.contentLength', debug, { length: contentLength, debug }, COMPONENTS.BROWSER_FETCH);
489
+ // 如果内容太长,截断它
490
+ let finalContent = content;
491
+ if (contentLength > 10 * 1024 * 1024) { // 10MB
492
+ finalContent = content.substring(0, 10 * 1024 * 1024);
493
+ log('browser.contentTruncated', debug, { debug }, COMPONENTS.BROWSER_FETCH);
494
+ }
495
+ // 关闭页面
496
+ await page.close();
497
+ log('browser.pageClosed', debug, { debug }, COMPONENTS.BROWSER_FETCH);
498
+ // 如果需要关闭浏览器
499
+ if (closeBrowser) {
500
+ await this.closeBrowser(debug);
501
+ }
502
+ return {
503
+ content: [{ type: 'text', text: finalContent }],
504
+ isError: false
505
+ };
506
+ }
507
+ catch (error) {
508
+ log('browser.fetchError', debug, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
509
+ // 如果需要关闭浏览器
510
+ if (closeBrowser) {
511
+ await this.closeBrowser(debug);
512
+ }
513
+ return {
514
+ content: [{ type: 'text', text: `Error fetching ${url}: ${error}` }],
515
+ isError: true
516
+ };
517
+ }
518
+ }
519
+ /**
520
+ * 使用Puppeteer获取HTML内容 (Get HTML content using Puppeteer)
521
+ * 通过浏览器模式获取网页HTML (Fetch webpage HTML in browser mode)
522
+ * @param requestPayload 请求参数 (Request parameters)
523
+ * @returns 获取结果 (Fetch result)
524
+ */
525
+ static async html(requestPayload) {
526
+ const { url, debug = false, closeBrowser = false } = requestPayload;
527
+ // 如果只是要关闭浏览器,不需要获取内容
528
+ if (url === 'about:blank' && closeBrowser) {
529
+ log('browser.closingInstance', debug, { debug }, COMPONENTS.BROWSER_FETCH);
530
+ await this.closeBrowser(debug);
531
+ return {
532
+ content: [{ type: 'text', text: 'Browser closed successfully' }],
533
+ isError: false
534
+ };
535
+ }
536
+ // 设置最大重试次数
537
+ const maxRetries = 2;
538
+ const fetchWithRetry = async (retryCount = 0) => {
539
+ log('browser.fetchingWithRetry', debug, { attempt: retryCount + 1, maxAttempts: maxRetries, url }, COMPONENTS.BROWSER_FETCH);
540
+ try {
541
+ return await this.fetch(requestPayload);
542
+ }
543
+ catch (error) {
544
+ if (retryCount < maxRetries) {
545
+ // 计算延迟时间,随着重试次数增加而增加
546
+ const delayMs = 1000 * (retryCount + 1);
547
+ log('browser.retryingAfterDelay', debug, { delayMs }, COMPONENTS.BROWSER_FETCH);
548
+ // 等待一段时间后重试
549
+ await new Promise(resolve => setTimeout(resolve, delayMs));
550
+ return fetchWithRetry(retryCount + 1);
551
+ }
552
+ // 超过最大重试次数,返回错误
553
+ return {
554
+ content: [{ type: 'text', text: `Error fetching ${url} after ${maxRetries + 1} attempts: ${error}` }],
555
+ isError: true
556
+ };
557
+ }
558
+ };
559
+ try {
560
+ return await fetchWithRetry();
561
+ }
562
+ finally {
563
+ // 如果需要关闭浏览器
564
+ if (closeBrowser) {
565
+ log('browser.closingInstance', debug, {}, COMPONENTS.BROWSER_FETCH);
566
+ await this.closeBrowser(debug);
567
+ }
568
+ }
569
+ }
570
+ /**
571
+ * 使用Puppeteer获取JSON内容 (Get JSON content using Puppeteer)
572
+ * 通过浏览器模式获取JSON数据 (Fetch JSON data in browser mode)
573
+ * @param requestPayload 请求参数 (Request parameters)
574
+ * @returns 获取结果 (Fetch result)
575
+ */
576
+ static async json(requestPayload) {
577
+ const { url, debug = false, closeBrowser = false } = requestPayload;
578
+ // 如果只是要关闭浏览器,不需要获取内容
579
+ if (url === 'about:blank' && closeBrowser) {
580
+ log('browser.closingInstance', debug, {}, COMPONENTS.BROWSER_FETCH);
581
+ await this.closeBrowser(debug);
582
+ return {
583
+ content: [{ type: 'text', text: 'Browser closed successfully' }],
584
+ isError: false
585
+ };
586
+ }
587
+ // 设置最大重试次数
588
+ const maxRetries = 2;
589
+ const fetchWithRetry = async (retryCount = 0) => {
590
+ log('browser.fetchingWithRetry', debug, { attempt: retryCount + 1, maxAttempts: maxRetries + 1, url }, COMPONENTS.BROWSER_FETCH);
591
+ try {
592
+ const result = await this.fetch(requestPayload);
593
+ // 如果获取成功,尝试解析JSON
594
+ if (!result.isError) {
595
+ try {
596
+ const jsonText = result.content[0].text;
597
+ // 尝试解析JSON
598
+ JSON.parse(jsonText);
599
+ // 如果解析成功,直接返回结果
600
+ return result;
601
+ }
602
+ catch (error) {
603
+ // JSON解析失败
604
+ log('browser.failedToParseJSON', debug, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
605
+ return {
606
+ content: [{ type: 'text', text: `Error parsing JSON from ${url}: ${error}` }],
607
+ isError: true
608
+ };
609
+ }
610
+ }
611
+ return result;
612
+ }
613
+ catch (error) {
614
+ log('browser.fetchErrorWithAttempt', debug, { error: String(error), attempt: retryCount + 1, maxAttempts: maxRetries + 1 }, COMPONENTS.BROWSER_FETCH);
615
+ if (retryCount < maxRetries) {
616
+ // 计算延迟时间,随着重试次数增加而增加
617
+ const delayMs = 1000 * (retryCount + 1);
618
+ log('browser.retryingAfterDelay', debug, { delayMs }, COMPONENTS.BROWSER_FETCH);
619
+ // 等待一段时间后重试
620
+ await new Promise(resolve => setTimeout(resolve, delayMs));
621
+ return fetchWithRetry(retryCount + 1);
622
+ }
623
+ // 超过最大重试次数,返回错误
624
+ return {
625
+ content: [{ type: 'text', text: `Error fetching JSON from ${url} after ${maxRetries + 1} attempts: ${error}` }],
626
+ isError: true
627
+ };
628
+ }
629
+ };
630
+ try {
631
+ return await fetchWithRetry();
632
+ }
633
+ finally {
634
+ // 如果需要关闭浏览器
635
+ if (closeBrowser) {
636
+ log('browser.closingInstance', debug, {}, COMPONENTS.BROWSER_FETCH);
637
+ await this.closeBrowser(debug);
638
+ }
639
+ }
640
+ }
641
+ /**
642
+ * 使用Puppeteer获取纯文本内容 (Get plain text content using Puppeteer)
643
+ * 通过浏览器模式获取网页纯文本 (Fetch webpage plain text in browser mode)
644
+ * @param requestPayload 请求参数 (Request parameters)
645
+ * @returns 获取结果 (Fetch result)
646
+ */
647
+ static async txt(requestPayload) {
648
+ const { url, debug = false, closeBrowser = false } = requestPayload;
649
+ // 如果只是要关闭浏览器,不需要获取内容
650
+ if (url === 'about:blank' && closeBrowser) {
651
+ log('browser.closingInstance', debug, {}, COMPONENTS.BROWSER_FETCH);
652
+ await this.closeBrowser(debug);
653
+ return {
654
+ content: [{ type: 'text', text: 'Browser closed successfully' }],
655
+ isError: false
656
+ };
657
+ }
658
+ // 设置最大重试次数
659
+ const maxRetries = 2;
660
+ const fetchWithRetry = async (retryCount = 0) => {
661
+ log('browser.fetchingWithRetry', debug, { attempt: retryCount + 1, maxAttempts: maxRetries + 1, url }, COMPONENTS.BROWSER_FETCH);
662
+ try {
663
+ const result = await this.fetch(requestPayload);
664
+ // 如果获取成功,提取纯文本
665
+ if (!result.isError) {
666
+ const htmlContent = result.content[0].text;
667
+ // 使用JSDOM提取纯文本
668
+ const dom = new JSDOM(htmlContent);
669
+ const textContent = dom.window.document.body.textContent || '';
670
+ // 清理文本(移除多余空白)
671
+ const cleanedText = textContent
672
+ .replace(/\s+/g, ' ')
673
+ .trim();
674
+ return {
675
+ content: [{ type: 'text', text: cleanedText }],
676
+ isError: false
677
+ };
678
+ }
679
+ return result;
680
+ }
681
+ catch (error) {
682
+ log('browser.fetchErrorWithAttempt', debug, { error: String(error), attempt: retryCount + 1, maxAttempts: maxRetries + 1 }, COMPONENTS.BROWSER_FETCH);
683
+ if (retryCount < maxRetries) {
684
+ // 计算延迟时间,随着重试次数增加而增加
685
+ const delayMs = 1000 * (retryCount + 1);
686
+ log('browser.retryingAfterDelay', debug, { delayMs }, COMPONENTS.BROWSER_FETCH);
687
+ // 等待一段时间后重试
688
+ await new Promise(resolve => setTimeout(resolve, delayMs));
689
+ return fetchWithRetry(retryCount + 1);
690
+ }
691
+ // 超过最大重试次数,返回错误
692
+ return {
693
+ content: [{ type: 'text', text: `Error fetching text from ${url} after ${maxRetries + 1} attempts: ${error}` }],
694
+ isError: true
695
+ };
696
+ }
697
+ };
698
+ try {
699
+ return await fetchWithRetry();
700
+ }
701
+ finally {
702
+ // 如果需要关闭浏览器
703
+ if (closeBrowser) {
704
+ log('browser.closingInstance', debug, {}, COMPONENTS.BROWSER_FETCH);
705
+ await this.closeBrowser(debug);
706
+ }
707
+ }
708
+ }
709
+ /**
710
+ * 使用Puppeteer获取Markdown内容 (Get Markdown content using Puppeteer)
711
+ * 通过浏览器模式获取HTML并转换为Markdown (Fetch HTML in browser mode and convert to Markdown)
712
+ * @param requestPayload 请求参数 (Request parameters)
713
+ * @returns 获取结果 (Fetch result)
714
+ */
715
+ static async markdown(requestPayload) {
716
+ const { url, debug = false } = requestPayload;
717
+ log('browser.startingBrowserFetchForMarkdown', debug, { url }, COMPONENTS.BROWSER_FETCH);
718
+ try {
719
+ // 获取HTML内容
720
+ const htmlResult = await this.html(requestPayload);
721
+ if (htmlResult.isError) {
722
+ return htmlResult;
723
+ }
724
+ // 将HTML转换为Markdown
725
+ const turndownService = new TurndownService({
726
+ headingStyle: 'atx',
727
+ codeBlockStyle: 'fenced',
728
+ bulletListMarker: '-'
729
+ });
730
+ // 添加表格支持
731
+ turndownService.addRule('tables', {
732
+ filter: ['table'],
733
+ replacement: function (content, node) {
734
+ const tableContent = content.trim();
735
+ return '\n\n' + tableContent + '\n\n';
736
+ }
737
+ });
738
+ // 转换HTML为Markdown
739
+ const markdown = turndownService.turndown(htmlResult.content[0].text);
740
+ return {
741
+ content: [{ type: 'text', text: markdown }],
742
+ isError: false
743
+ };
744
+ }
745
+ catch (error) {
746
+ log('browser.errorInBrowserFetchForMarkdown', debug, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
747
+ return {
748
+ content: [{ type: 'text', text: `Error converting HTML to Markdown for ${url}: ${error}` }],
749
+ isError: true
750
+ };
751
+ }
752
+ }
753
+ }