File size: 3,460 Bytes
9b4263f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#!/usr/bin/env node
/**
 * 无头 Chromium 提取正文:复用扩展 Readability → articleRoot → collectTextMap。
 *
 * 用法(项目根目录):
 *   node scripts/extract_page_text.mjs <url> -o scripts/cases/论文.txt
 *
 * 依赖:playwright(用本机 Chrome,channel=chrome)
 *   npm i --prefix scripts playwright
 */
import fs from 'node:fs';
import path from 'node:path';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, '..');
const require = createRequire(import.meta.url);

function loadPlaywright() {
  const candidates = [
    path.join(__dirname, 'node_modules', 'playwright'),
    path.join(ROOT, 'node_modules', 'playwright'),
  ];
  for (const p of candidates) {
    try {
      return require(p);
    } catch {
      /* try next */
    }
  }
  console.error(
    'playwright not found. Install with:\n  npm i --prefix scripts playwright',
  );
  process.exit(1);
}

function parseArgs(argv) {
  const args = { url: null, out: null };
  for (let i = 0; i < argv.length; i++) {
    const a = argv[i];
    if (a === '-o' || a === '--output') args.out = argv[++i];
    else if (a.startsWith('-')) {
      console.error(`Unknown flag: ${a}`);
      process.exit(2);
    } else if (!args.url) args.url = a;
    else {
      console.error(`Unexpected arg: ${a}`);
      process.exit(2);
    }
  }
  if (!args.url || !args.out) {
    console.error('Usage: node scripts/extract_page_text.mjs <url> -o <out.txt>');
    process.exit(2);
  }
  return args;
}

async function main() {
  const args = parseArgs(process.argv.slice(2));
  const { chromium } = loadPlaywright();

  const browser = await chromium.launch({ channel: 'chrome', headless: true });
  try {
    const page = await browser.newPage();
    await page.goto(args.url, { waitUntil: 'networkidle', timeout: 90_000 });

    for (const rel of [
      'extension/vendor/Readability.js',
      'extension/articleRoot.js',
      'extension/collectTextMap.js',
    ]) {
      await page.addScriptTag({ path: path.join(ROOT, rel) });
    }

    const result = await page.evaluate(() => {
      const find = globalThis.IL_findArticleRoot;
      const collect = globalThis.IL_collectTextMap;
      if (typeof find !== 'function') throw new Error('IL_findArticleRoot missing');
      if (typeof collect !== 'function') throw new Error('IL_collectTextMap missing');
      const root = find(document);
      const mapped = collect(root);
      return {
        text: mapped.text,
        length: mapped.text.length,
        pieces: mapped.pieces.length,
        rootTag: root.tagName,
        rootClass: root.className,
        sample: mapped.text.slice(0, 120),
      };
    });

    if (result.text.includes('\r')) {
      throw new Error('Extracted text contains \\r (CR); refuse to write');
    }

    const outAbs = path.resolve(args.out);
    fs.mkdirSync(path.dirname(outAbs), { recursive: true });
    fs.writeFileSync(outAbs, result.text, 'utf8');

    console.error(
      `Wrote ${result.length} chars, ${result.pieces} pieces → ${path.relative(ROOT, outAbs) || outAbs}`,
    );
    console.error(`root=<${result.rootTag} class="${result.rootClass}">`);
    console.error(`sample: ${JSON.stringify(result.sample)}`);
  } finally {
    await browser.close();
  }
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});