@cloud411716/fancy-webnovel 0.1.77 → 0.1.79

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 (123) hide show
  1. package/package.json +1 -1
  2. package/plugins/fancy-scan/scripts/run-scan.js +22 -44
  3. package/plugins/fancy-scan/scripts/scan-output-format.md +1 -1
  4. package/plugins/fancy-scan/scripts/scraper-registry.md +15 -17
  5. package/plugins/fancy-scan/scripts/scrapers/cdp-utils.cjs +14 -187
  6. package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.cjs +282 -253
  7. package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.cjs +193 -253
  8. package/plugins/fancy-scan/scripts/scrapers/node_modules/.package-lock.json +20 -0
  9. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/LICENSE +202 -0
  10. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/NOTICE +5 -0
  11. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/README.md +3 -0
  12. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/ThirdPartyNotices.txt +13 -0
  13. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/install_media_pack.ps1 +5 -0
  14. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/install_webkit_wsl.ps1 +33 -0
  15. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh +42 -0
  16. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh +13 -0
  17. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1 +24 -0
  18. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh +42 -0
  19. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh +12 -0
  20. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1 +24 -0
  21. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh +48 -0
  22. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh +11 -0
  23. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1 +23 -0
  24. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh +48 -0
  25. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh +11 -0
  26. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1 +23 -0
  27. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh +48 -0
  28. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh +11 -0
  29. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1 +24 -0
  30. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/browsers.json +75 -0
  31. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/cli.js +21 -0
  32. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/index.d.ts +17 -0
  33. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/index.js +17 -0
  34. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/index.mjs +28 -0
  35. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/bootstrap.js +88 -0
  36. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/coreBundle.js +74830 -0
  37. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/entry/cliDaemon.js +5 -0
  38. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/entry/dashboardApp.js +3 -0
  39. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/entry/mcp.js +10 -0
  40. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/entry/oopBrowserDownload.js +3 -0
  41. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/package.js +50 -0
  42. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/server/chromium/appIcon.png +0 -0
  43. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/server/electron/loader.js +118 -0
  44. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/serverRegistry.js +7347 -0
  45. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/serverRegistry.js.LICENSE +354 -0
  46. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/channelSessions.js +141 -0
  47. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/cli.js +6 -0
  48. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/help.json +708 -0
  49. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/minimist.js +128 -0
  50. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/output.js +343 -0
  51. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/program.js +404 -0
  52. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/registry.js +176 -0
  53. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/session.js +258 -0
  54. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/dashboard/appIcon.png +0 -0
  55. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/SKILL.md +420 -0
  56. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/element-attributes.md +23 -0
  57. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/playwright-tests.md +39 -0
  58. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/request-mocking.md +87 -0
  59. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/running-code.md +241 -0
  60. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/session-management.md +225 -0
  61. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/storage-state.md +275 -0
  62. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/test-generation.md +433 -0
  63. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/tracing.md +139 -0
  64. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/video-recording.md +143 -0
  65. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/SKILL.md +143 -0
  66. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
  67. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/migration.md +85 -0
  68. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/react.md +67 -0
  69. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/vue.md +75 -0
  70. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-trace/SKILL.md +171 -0
  71. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/utils/extension.js +101 -0
  72. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/utils/socketConnection.js +108 -0
  73. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/utilsBundle.js +90764 -0
  74. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/utilsBundle.js.LICENSE +2179 -0
  75. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/assets/codicon-DCmgc-ay.ttf +0 -0
  76. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-1bWoP6pv.svg +1 -0
  77. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-beta-k3eOH_eK.svg +1 -0
  78. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-nightly-Cp5nfeDT.svg +1 -0
  79. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/assets/index-CyWAfh-p.js +11 -0
  80. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/assets/index-DhC616m4.css +1 -0
  81. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/assets/safari-na3_-uQk.svg +1 -0
  82. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/index.html +29 -0
  83. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/playwright-logo.svg +24 -0
  84. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/htmlReport/index.html +16 -0
  85. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/htmlReport/report.css +2 -0
  86. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/htmlReport/report.js +32 -0
  87. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
  88. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-CwYYHbcZ.js +32 -0
  89. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/recorder/assets/codicon-DCmgc-ay.ttf +0 -0
  90. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/recorder/assets/index-DYjdXIbE.js +129 -0
  91. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/recorder/assets/index-l_lX622x.css +1 -0
  92. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/recorder/index.html +29 -0
  93. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/recorder/playwright-logo.svg +9 -0
  94. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-rXmQmLUY.js +32 -0
  95. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-B-dXF5JN.js +181 -0
  96. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
  97. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-BuZfJS5v.js +7 -0
  98. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
  99. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/codicon.DCmgc-ay.ttf +0 -0
  100. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.BLFoOugd.css +1 -0
  101. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
  102. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/index.KZ4wOW1K.js +1 -0
  103. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/index.html +44 -0
  104. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/manifest.webmanifest +16 -0
  105. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/playwright-logo.svg +9 -0
  106. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
  107. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html +10 -0
  108. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js +4 -0
  109. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/uiMode.C7UW1sC9.css +1 -0
  110. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/uiMode.Dzuouizj.js +5 -0
  111. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +18 -0
  112. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
  113. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/webp_codec.LICENSE +173 -0
  114. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/webp_codec.wasm +0 -0
  115. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/xdg-open +1267 -0
  116. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/package.json +34 -0
  117. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/types/protocol.d.ts +24842 -0
  118. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/types/structs.d.ts +52 -0
  119. package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/types/types.d.ts +26188 -0
  120. package/plugins/fancy-scan/scripts/scrapers/package-lock.json +27 -0
  121. package/plugins/fancy-scan/scripts/scrapers/package.json +9 -0
  122. package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.cjs +200 -297
  123. package/plugins/fancy-scan/scripts/scrapers/zhihu-rank-scraper.cjs +80 -80
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloud411716/fancy-webnovel",
3
- "version": "0.1.77",
3
+ "version": "0.1.79",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "exports": {
@@ -174,49 +174,27 @@ function spawnScraper(platform, length, outDir) {
174
174
  return new Promise(async (resolve) => {
175
175
  const { spawn } = await import('child_process');
176
176
  const args = scraperArgs(platform, length, outDir);
177
- const ports = ['9222', '9223'];
178
- let portIdx = 0;
179
- let dots = 0;
180
- const interval = setInterval(() => {
181
- dots++;
182
- process.stderr.write('.');
183
- }, 3000);
184
-
185
- function tryPort(port) {
186
- process.stderr.write(`\n → 端口 ${port} ...`);
187
- const scraperScript = join(dirname(fileURLToPath(import.meta.url)), 'scrapers', args.script);
188
- const fullArgs = ['--port', port, ...args.extra];
189
- const p = spawn('node', [scraperScript, ...fullArgs], {
190
- timeout: 120000,
191
- stdio: ['ignore', 'pipe', 'inherit'],
192
- });
193
- let out = '';
194
- p.stdout.on('data', d => { out += d.toString(); });
195
- p.on('close', (code) => {
196
- clearInterval(interval);
197
- if (code === 0 && out.trim()) {
198
- resolve({ ok: true, output: out, stderr: '' });
199
- } else if (portIdx < ports.length - 1) {
200
- portIdx++;
201
- process.stderr.write(` (失败)\n`);
202
- tryPort(ports[portIdx]);
203
- } else {
204
- process.stderr.write(` (失败)\n`);
205
- const errTail = out.trimEnd().slice(-300);
206
- resolve({ ok: false, output: out, error: errTail || `exit ${code}` });
207
- }
208
- });
209
- p.on('error', (e) => {
210
- clearInterval(interval);
211
- if (portIdx < ports.length - 1) {
212
- portIdx++;
213
- tryPort(ports[portIdx]);
214
- } else {
215
- resolve({ ok: false, output: '', error: e.message });
216
- }
217
- });
218
- }
219
- tryPort(ports[portIdx]);
177
+ const scraperScript = join(dirname(fileURLToPath(import.meta.url)), 'scrapers', args.script);
178
+ const fullArgs = [...args.extra];
179
+
180
+ process.stderr.write(`\n→ 启动采集脚本...`);
181
+ const p = spawn('node', [scraperScript, ...fullArgs], {
182
+ timeout: 300000,
183
+ stdio: ['ignore', 'pipe', 'inherit'],
184
+ });
185
+ let out = '';
186
+ p.stdout.on('data', d => { out += d.toString(); });
187
+ p.on('close', (code) => {
188
+ if (code === 0 && out.trim()) {
189
+ resolve({ ok: true, output: out, stderr: '' });
190
+ } else {
191
+ const errTail = out.trimEnd().slice(-500);
192
+ resolve({ ok: false, output: out, error: errTail || `exit ${code}` });
193
+ }
194
+ });
195
+ p.on('error', (e) => {
196
+ resolve({ ok: false, output: '', error: e.message });
197
+ });
220
198
  });
221
199
  }
222
200
 
@@ -400,7 +378,7 @@ async function main() {
400
378
  console.error(` ❌ 平台 ${platform} 暂不支持采集`);
401
379
  process.exit(1);
402
380
  }
403
- console.error(`→ 采集 ${PLATFORM_CN[platform]}(Chrome CDP)...`);
381
+ console.error(`→ 采集 ${PLATFORM_CN[platform]}...`);
404
382
  const result = await spawnScraper(platform, length, scanDir);
405
383
  if (!result.ok) {
406
384
  console.error(` ❌ ${PLATFORM_CN[platform]} 采集失败:${result.error}`);
@@ -94,7 +94,7 @@
94
94
  **故障排查(书名全是 `bookId:xxx` / `(标题待解析)`)**:
95
95
  - 看文件头 `数据质量`:标 `[标题解析异常]` 说明详情页解码失败率高。
96
96
  - 多为详情页结构变动或被登录/验证页拦截。在已登录的 Chrome 里手动打开任一 `https://fanqienovel.com/page/{bookId}` 确认页面正常、非验证页。
97
- - 控制台若报 `CDP 无响应`,说明 Chrome/CDP 没起来或端口不对,按 browser-cdp skill 重新启动。确认正常后重采。
97
+ - 控制台若报 ` playwright-core 无法启动浏览器`,说明系统缺少 Chrome/Edge 或浏览器路径不对,用 `--login-wait` 等待手动登录后重试。
98
98
 
99
99
  ---
100
100
 
@@ -12,13 +12,11 @@
12
12
 
13
13
  ---
14
14
 
15
- ## Chrome CDP 环境要求
15
+ ## 环境要求
16
16
 
17
- > ⚠️ **除 qidian 外,其他所有 scraper 都需要先启动 Chrome CDP**:
18
- > ```bash
19
- > node {SKILL_DIR}/browser-cdp/scripts/setup-cdp-chrome.js --yes
20
- > ```
21
- > qidian 默认走 mobile-SSR(不需要 Chrome),失败才降级 CDP。
17
+ > ⚠️ **所有 scraper 现在使用 playwright-core 自己管理浏览器,不再依赖 agent-browser 和 Chrome CDP 端口。**
18
+ > 每个 scraper 独立 launch/close 浏览器进程,采集完成后自动关闭。
19
+ > 如需在采集前留时间登录,可传入 `--login-wait <秒>` 参数。
22
20
 
23
21
  ---
24
22
 
@@ -34,20 +32,20 @@
34
32
 
35
33
  | 篇幅 | Scraper | 采集模式 | 示例命令 |
36
34
  |------|---------|---------|---------|
37
- | 长篇 | fanqie-rank-scraper.js | CDP(需要 Chrome)| `node fanqie-rank-scraper.js --channel all --type all --outdir {out}` |
35
+ | 长篇 | fanqie-rank-scraper.cjs | playwright-native | `node fanqie-rank-scraper.cjs --channel all --type all --outdir {out}` |
38
36
 
39
37
  ### 晋江(jinjiang)
40
38
 
41
39
  | 篇幅 | Scraper | 采集模式 | 示例命令 |
42
40
  |------|---------|---------|---------|
43
- | 长篇 | jjwxc-rank-scraper.js | CDP(需要 Chrome)| `node jjwxc-rank-scraper.js --type 12 --outdir {out}` |
41
+ | 长篇 | jjwxc-rank-scraper.cjs | playwright-native | `node jjwxc-rank-scraper.cjs --type 12 --outdir {out}` |
44
42
 
45
43
  ### 知乎(zhihu)
46
44
 
47
45
  | 篇幅 | Scraper | 采集模式 | 示例命令 |
48
46
  |------|---------|---------|---------|
49
- | 长篇 | zhihu-rank-scraper.js | CDP(需要 Chrome)| `node zhihu-rank-scraper.js --length long --outdir {out}` |
50
- | 短篇 | zhihu-rank-scraper.js | CDP(需要 Chrome)| `node zhihu-rank-scraper.js --length short --outdir {out}` |
47
+ | 长篇 | zhihu-rank-scraper.cjs | playwright-native | `node zhihu-rank-scraper.cjs --length long --outdir {out}` |
48
+ | 短篇 | zhihu-rank-scraper.cjs | playwright-native | `node zhihu-rank-scraper.cjs --length short --outdir {out}` |
51
49
 
52
50
  入口 URL:`https://www.zhihu.com/fiore/h5/vip-web`
53
51
 
@@ -60,8 +58,8 @@
60
58
 
61
59
  | 篇幅 | Scraper | 采集模式 | 示例命令 |
62
60
  |------|---------|---------|---------|
63
- | 长篇 | qimao-rank-scraper.js | CDP(需要 Chrome)| `node qimao-rank-scraper.js --type hot --period day --outdir {out}` |
64
- | 短篇 | qimao-rank-scraper.js | CDP(需要 Chrome)| `node qimao-rank-scraper.js --type hot --period day --gender f --outdir {out}` |
61
+ | 长篇 | qimao-rank-scraper.cjs | playwright-native | `node qimao-rank-scraper.cjs --type hot --period day --outdir {out}` |
62
+ | 短篇 | qimao-rank-scraper.cjs | playwright-native | `node qimao-rank-scraper.cjs --type hot --period day --gender f --outdir {out}` |
65
63
 
66
64
  ---
67
65
 
@@ -84,12 +82,12 @@ fancy-scan/
84
82
  ├── run-scan.py ← Python 编排层
85
83
  ├── scraper-registry.md ← 本文件
86
84
  ├── scan-output-format.md
87
- ├── cdp-utils.js
85
+ ├── cdp-utils.cjs
88
86
  ├── qidian-rank-scraper.js
89
- ├── fanqie-rank-scraper.js
90
- ├── jjwxc-rank-scraper.js
91
- ├── zhihu-rank-scraper.js
92
- └── qimao-rank-scraper.js
87
+ ├── fanqie-rank-scraper.cjs
88
+ ├── jjwxc-rank-scraper.cjs
89
+ ├── zhihu-rank-scraper.cjs
90
+ └── qimao-rank-scraper.cjs
93
91
  ```
94
92
 
95
93
  > ⚠️ 如果 `{SKILL_DIR}/references/` 下没有某个 scraper,run-scan.py 回退到"内置知识"模式,输出占位符报告并标注 `[内置知识-未采集]`。
@@ -1,187 +1,24 @@
1
1
  /**
2
- * CDP 工具函数 — 各平台采集脚本的公共依赖
2
+ * 工具函数 — 各平台采集脚本的公共依赖
3
3
  *
4
- * 使用方式:
5
- * const { ab, sleep, evalJSON, evalJSONBase64, scrollLoad, getArg, safeStr, localDateStamp } = require("./cdp-utils");
6
- *
7
- * 前置:
8
- * node {SKILL_DIR}/browser-cdp/scripts/setup-cdp-chrome.js 9222
9
- */
10
-
11
- const { execFileSync } = require("child_process");
12
- const fs = require("fs");
13
- const path = require("path");
14
-
15
- /**
16
- * On Windows `agent-browser` is an npm shim (agent-browser.cmd/.ps1) that
17
- * forwards to the real target — the native agent-browser-win32-*.exe or a
18
- * bundled Node CLI. Node refuses to execFile the `.cmd` without a shell
19
- * (CVE-2024-27980), and routing the argv array through a shell mangles it: the
20
- * `.cmd`'s `%*` is re-tokenized by cmd.exe (splitting on spaces, breaking on
21
- * & | ^), and calling the shim by bare name from powershell.exe collapses the
22
- * whole array into a single space-joined argument. The exact locus differs by
23
- * runtime, so instead of hardening any one shell path we bypass shells entirely:
24
- * read the `.cmd` shim, recover the real program plus its fixed leading args,
25
- * and execFile that target directly with the argv array — verbatim, no shell.
4
+ * 提供:
5
+ * getArg() — 命令行参数解析
6
+ * localDateStamp() — 本地日期戳(YYYYMMDD)
7
+ * runCli() — CLI 入口包装(含进程超时和信号处理)
26
8
  */
27
- function resolveWindowsAgentBrowser(argv) {
28
- const dirs = String(process.env.PATH || "").split(path.delimiter);
29
- let cmdPath = null;
30
- for (const dir of dirs) {
31
- if (!dir) continue;
32
- const candidate = path.join(dir, "agent-browser.cmd");
33
- if (fs.existsSync(candidate)) {
34
- cmdPath = candidate;
35
- break;
36
- }
37
- }
38
- if (!cmdPath) return { file: "agent-browser", args: argv };
39
- const dir = path.dirname(cmdPath);
40
- const forwardLine =
41
- fs
42
- .readFileSync(cmdPath, "utf8")
43
- .split(/\r?\n/)
44
- .find((line) => line.includes("%*")) || "";
45
- const tokens = [...forwardLine.matchAll(/"([^"]*)"/g)]
46
- .map((m) => m[1])
47
- .map((t) =>
48
- t
49
- .replace(/%~dp0/gi, () => dir + path.sep)
50
- .replace(/%dp0%/gi, () => dir + path.sep)
51
- );
52
- const jsIndex = tokens.findIndex((t) => /\.[cm]?js$/i.test(t));
53
- if (jsIndex >= 0) {
54
- return { file: process.execPath, args: [...tokens.slice(jsIndex), ...argv] };
55
- }
56
- if (tokens.length > 0) {
57
- return { file: tokens[0], args: [...tokens.slice(1), ...argv] };
58
- }
59
- return { file: "agent-browser", args: argv };
60
- }
61
-
62
- /**
63
- * Build a shell-free invocation. POSIX runs the native `agent-browser` binary
64
- * directly; Windows resolves the npm `.cmd` shim to that native target so the
65
- * argument array is passed verbatim, never routed through cmd.exe/PowerShell.
66
- */
67
- function buildAgentBrowserInvocation(port, args, platform = process.platform) {
68
- const argv = ["--cdp", String(port), ...args.map(String)];
69
- if (platform !== "win32") {
70
- return { file: "agent-browser", args: argv };
71
- }
72
- return resolveWindowsAgentBrowser(argv);
73
- }
74
-
75
- // ---------------------------------------------------------------------------
76
- // agent-browser 工具函数
77
- // ---------------------------------------------------------------------------
78
-
79
- /**
80
- * 调用 agent-browser CLI
81
- * @param {number} port - CDP 端口
82
- * @param {...string} args - agent-browser 参数
83
- * @returns {string} stdout(trim 后)
84
- */
85
- function ab(port, ...args) {
86
- const invocation = buildAgentBrowserInvocation(port, args);
87
- try {
88
- return execFileSync(
89
- invocation.file,
90
- invocation.args,
91
- {
92
- encoding: "utf-8",
93
- timeout: 20000,
94
- stdio: ["pipe", "pipe", "pipe"],
95
- windowsHide: true,
96
- }
97
- ).trim();
98
- } catch (error) {
99
- const stderr = error && error.stderr ? String(error.stderr).trim() : "";
100
- const stdout = error && error.stdout ? String(error.stdout).trim() : "";
101
- const detail = stderr || stdout || (error && error.message) || "unknown error";
102
- throw new Error(`agent-browser failed: ${detail}`, { cause: error });
103
- }
104
- }
105
-
106
- /** 等待 ms 毫秒(跨平台,不依赖系统 sleep 命令) */
107
- function sleep(ms) {
108
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
109
- }
110
-
111
- function parseJSONResult(raw) {
112
- if (!raw || raw === "ERR") {
113
- throw new Error("agent-browser returned no JSON result");
114
- }
115
- try {
116
- let parsed = JSON.parse(raw);
117
- if (typeof parsed === "string") {
118
- try { parsed = JSON.parse(parsed); } catch {}
119
- }
120
- return parsed;
121
- } catch (error) {
122
- throw new Error(`agent-browser returned invalid JSON: ${String(raw).slice(0, 160)}`, {
123
- cause: error,
124
- });
125
- }
126
- }
127
-
128
- /**
129
- * 在浏览器内执行 JS,并解析 JSON 返回值。
130
- * 一律走 base64(-b):正文提取用的 JS 常含引号、反斜杠等,作为命令行参数时在 Windows 上
131
- * 无法逐字透传(.cmd 的 %* 与 PowerShell 都会二次解析)。base64 让参数只含 [A-Za-z0-9+/=],
132
- * 和各采集脚本已在用的 evalJSONBase64 走同一条安全通道。
133
- */
134
- function evalJSON(port, js) {
135
- return evalJSONBase64(port, js);
136
- }
137
-
138
- /**
139
- * 通过 agent-browser 的 base64 参数执行复杂 JS,避免命令行转义和参数边界问题。
140
- */
141
- function evalJSONBase64(port, js) {
142
- const encoded = Buffer.from(String(js), "utf8").toString("base64");
143
- return parseJSONResult(ab(port, "eval", "-b", encoded));
144
- }
145
-
146
- /**
147
- * 安全地将值插入浏览器 eval 字符串。
148
- * 使用 JSON.stringify 确保值不会因特殊字符(引号、反斜杠等)破坏 eval 字符串。
149
- * @param {*} val - 要插入的值
150
- * @returns {string} JSON 字符串表示(含引号)
151
- */
152
- function safeStr(val) {
153
- return JSON.stringify(String(val));
154
- }
155
-
156
- /**
157
- * 滚动页面加载更多内容
158
- * @param {number} port - CDP 端口
159
- * @param {number} times - 滚动次数
160
- * @param {number} [interval=1000] - 每次滚动间隔(ms)
161
- */
162
- function scrollLoad(port, times, interval = 1000) {
163
- for (let i = 0; i < times; i++) {
164
- ab(port, "eval", "window.scrollBy(0, window.innerHeight)");
165
- sleep(interval);
166
- }
167
- }
168
9
 
169
10
  /** 解析 --xxx 参数 */
170
11
  function getArg(args, name) {
171
12
  const i = args.indexOf(name);
172
13
  if (i >= 0) return i + 1 < args.length ? args[i + 1] : null;
173
14
  const prefix = `${name}=`;
174
- const inline = args.find((arg) => String(arg).startsWith(prefix));
15
+ const inline = args.find(arg => String(arg).startsWith(prefix));
175
16
  return inline === undefined ? null : String(inline).slice(prefix.length);
176
17
  }
177
18
 
178
19
  /**
179
- * 输出文件名用的日期戳(YYYYMMDD),一律取**本地日历日**。
20
+ * 输出文件名用的日期戳(YYYYMMDD),一律取本地日历日。
180
21
  * 不能用 new Date().toISOString().slice(0,10):那是 UTC 日期,比 UTC+8 晚 8 小时。
181
- * 文件名是各采集脚本唯一的去重键(一个榜单一天一份),北京时间 00:00-08:00 之间的采集
182
- * 会退回「昨天」的文件名,静默覆盖前一晚采到的同名报告,且这份数据被标成前一天。
183
- * @param {Date} [date] - 默认当前时间
184
- * @returns {string} YYYYMMDD
185
22
  */
186
23
  function localDateStamp(date) {
187
24
  const d = date instanceof Date ? date : new Date();
@@ -192,14 +29,15 @@ function localDateStamp(date) {
192
29
  }
193
30
 
194
31
  /**
195
- * Run a scraper entrypoint and turn empty/partial output into machine-readable
196
- * CLI status. Legacy entrypoints may return an integer; multi-target scrapers
197
- * return {planned,written,failed,partial,partialReasons}.
32
+ * Run a scraper entrypoint 并将结果转换为机器可读的退出码。
33
+ * 返回值支持:
34
+ * 整数 直接作为 written 计数
35
+ * 对象 — { planned, written, failed, partial, partialReasons }
198
36
  */
199
37
  function runCli(main, label) {
200
38
  Promise.resolve()
201
39
  .then(main)
202
- .then((result) => {
40
+ .then(result => {
203
41
  const outcome = Number.isInteger(result)
204
42
  ? { planned: result, written: result, failed: 0, partial: false, partialReasons: [] }
205
43
  : result;
@@ -221,22 +59,11 @@ function runCli(main, label) {
221
59
  process.exitCode = 2;
222
60
  }
223
61
  })
224
- .catch((error) => {
62
+ .catch(error => {
225
63
  const message = error && error.message ? error.message : String(error);
226
64
  console.error(`${label} failed: ${message}`);
227
65
  process.exitCode = 1;
228
66
  });
229
67
  }
230
68
 
231
- module.exports = {
232
- ab,
233
- sleep,
234
- evalJSON,
235
- evalJSONBase64,
236
- buildAgentBrowserInvocation,
237
- safeStr,
238
- scrollLoad,
239
- getArg,
240
- localDateStamp,
241
- runCli,
242
- };
69
+ module.exports = { getArg, localDateStamp, runCli };