doc2vec 2.15.0 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -485,6 +485,42 @@ The script will then:
|
|
|
485
485
|
7. Cleanup obsolete chunks.
|
|
486
486
|
8. Output detailed logs.
|
|
487
487
|
|
|
488
|
+
## Docker Images and the Browser
|
|
489
|
+
|
|
490
|
+
Website sources are rendered with a real browser (Puppeteer). The `Dockerfile` builds two images so you can choose where that browser runs:
|
|
491
|
+
|
|
492
|
+
| Target | Contents | Use it when |
|
|
493
|
+
|---|---|---|
|
|
494
|
+
| `runtime` | doc2vec only, no browser | You run a browser elsewhere and point doc2vec at it (see below). Recommended for clusters: the browser is the one component that renders untrusted input, and it can be updated on its own schedule. |
|
|
495
|
+
| `runtime-chrome` | doc2vec plus a bundled, version-matched Chrome | You want a single self-contained container. `linux/amd64` only. |
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
docker build --target runtime -t doc2vec .
|
|
499
|
+
docker build --target runtime-chrome -t doc2vec:chrome .
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Both images are based on Wolfi (Chainguard) and run as the non-root user `doc2vec` (UID 14000).
|
|
503
|
+
|
|
504
|
+
### Using a remote browser (`BROWSER_WS_ENDPOINT`)
|
|
505
|
+
|
|
506
|
+
Set `BROWSER_WS_ENDPOINT` to a Chrome DevTools Protocol WebSocket URL and doc2vec connects to that browser instead of launching one. Any CDP-speaking browser works; [browserless](https://github.com/browserless/browserless) is a convenient packaged one:
|
|
507
|
+
|
|
508
|
+
```bash
|
|
509
|
+
docker run -d --name browser -e TOKEN=secret -e TIMEOUT=0 --shm-size=1g ghcr.io/browserless/chromium
|
|
510
|
+
docker run --rm --link browser \
|
|
511
|
+
-e BROWSER_WS_ENDPOINT='ws://browser:3000?token=secret' \
|
|
512
|
+
-e OPENAI_API_KEY \
|
|
513
|
+
-v "$PWD/config.yaml:/config/config.yaml:ro" \
|
|
514
|
+
doc2vec node dist/doc2vec.js /config/config.yaml
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
Notes:
|
|
518
|
+
|
|
519
|
+
- A crawl holds a browser for as long as it runs, so disable or raise the browser service's session timeout (browserless: `TIMEOUT=0`).
|
|
520
|
+
- Each connection gets its own browser session; doc2vec closes it when the crawl finishes and every 50 pages.
|
|
521
|
+
- Launch flags such as `--no-sandbox` belong to the process that starts the browser and are not sent to a remote one.
|
|
522
|
+
- With `BROWSER_WS_ENDPOINT` unset, doc2vec launches a local Chrome: `PUPPETEER_EXECUTABLE_PATH` if set, else Puppeteer's downloaded Chrome for Testing, else a system `chromium`.
|
|
523
|
+
|
|
488
524
|
## Controller Mode
|
|
489
525
|
|
|
490
526
|
Besides the one-shot sync, doc2vec can run as a **long-lived controller** that schedules sync jobs, records run history and statistics in Postgres, and serves a web UI for monitoring and managing configs:
|
|
@@ -591,12 +627,17 @@ spec:
|
|
|
591
627
|
- name: OPENAI_API_KEY
|
|
592
628
|
valueFrom:
|
|
593
629
|
secretKeyRef: { name: doc2vec-secrets, key: openai-api-key }
|
|
630
|
+
# The browser runs in its own Deployment (e.g. ghcr.io/browserless/chromium
|
|
631
|
+
# with TIMEOUT=0); see "Docker Images and the Browser". Drop this variable
|
|
632
|
+
# and use the `-chrome` image to bundle the browser instead.
|
|
633
|
+
- name: BROWSER_WS_ENDPOINT
|
|
634
|
+
value: ws://browserless:3000?token=$(BROWSERLESS_TOKEN)
|
|
594
635
|
volumeMounts:
|
|
595
636
|
- name: configs
|
|
596
637
|
mountPath: /etc/doc2vec
|
|
597
638
|
resources:
|
|
598
639
|
requests: { memory: 1Gi }
|
|
599
|
-
limits: { memory:
|
|
640
|
+
limits: { memory: 2Gi }
|
|
600
641
|
volumes:
|
|
601
642
|
- name: configs
|
|
602
643
|
configMap: { name: doc2vec-configs }
|
|
@@ -36,7 +36,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.ContentProcessor = exports.BrowserLaunchError = void 0;
|
|
39
|
+
exports.ContentProcessor = exports.BROWSER_WS_ENDPOINT_ENV = exports.BrowserLaunchError = void 0;
|
|
40
|
+
exports.remoteBrowserEndpoint = remoteBrowserEndpoint;
|
|
41
|
+
exports.openBrowser = openBrowser;
|
|
40
42
|
exports.resolveBrowserExecutablePath = resolveBrowserExecutablePath;
|
|
41
43
|
const readability_1 = require("@mozilla/readability");
|
|
42
44
|
const axios_1 = __importDefault(require("axios"));
|
|
@@ -62,6 +64,56 @@ class BrowserLaunchError extends Error {
|
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
exports.BrowserLaunchError = BrowserLaunchError;
|
|
67
|
+
/**
|
|
68
|
+
* Where the browser comes from.
|
|
69
|
+
*
|
|
70
|
+
* Unset (the default): this process launches its own Chromium, which the image
|
|
71
|
+
* has to carry. Set: this process CONNECTS to a browser running elsewhere over
|
|
72
|
+
* the Chrome DevTools Protocol, and the image needs no browser at all. The
|
|
73
|
+
* value is a full CDP WebSocket URL, e.g.
|
|
74
|
+
* ws://browserless.qdrant.svc.cluster.local:3000?token=<token>
|
|
75
|
+
*
|
|
76
|
+
* A remote browser is the one that keeps the app image small and lets the
|
|
77
|
+
* browser be updated independently of the app (a browser image tracks Chrome
|
|
78
|
+
* stable; a distro chromium is frozen at whatever the distro rebuilt last).
|
|
79
|
+
*/
|
|
80
|
+
exports.BROWSER_WS_ENDPOINT_ENV = 'BROWSER_WS_ENDPOINT';
|
|
81
|
+
function remoteBrowserEndpoint() {
|
|
82
|
+
const raw = process.env[exports.BROWSER_WS_ENDPOINT_ENV]?.trim();
|
|
83
|
+
return raw ? raw : undefined;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Chromium flags for a browser THIS process launches. They are not sent to a
|
|
87
|
+
* remote browser: launch flags belong to the process that starts the browser.
|
|
88
|
+
*/
|
|
89
|
+
const LOCAL_LAUNCH_ARGS = [
|
|
90
|
+
'--no-sandbox',
|
|
91
|
+
'--disable-setuid-sandbox',
|
|
92
|
+
'--disable-dev-shm-usage', // Use /tmp instead of /dev/shm (critical in Docker, default 64MB)
|
|
93
|
+
'--disable-gpu',
|
|
94
|
+
'--disable-extensions',
|
|
95
|
+
];
|
|
96
|
+
const BROWSER_PROTOCOL_TIMEOUT_MS = 60000;
|
|
97
|
+
/**
|
|
98
|
+
* Open a browser: connect to the remote one named by BROWSER_WS_ENDPOINT, or
|
|
99
|
+
* launch a local one. Callers close it with browser.close() in both cases —
|
|
100
|
+
* a browserless-style service gives each connection its own browser, so
|
|
101
|
+
* closing ends only this caller's session.
|
|
102
|
+
*/
|
|
103
|
+
async function openBrowser() {
|
|
104
|
+
const endpoint = remoteBrowserEndpoint();
|
|
105
|
+
if (endpoint) {
|
|
106
|
+
return puppeteer_1.default.connect({
|
|
107
|
+
browserWSEndpoint: endpoint,
|
|
108
|
+
protocolTimeout: BROWSER_PROTOCOL_TIMEOUT_MS,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return puppeteer_1.default.launch({
|
|
112
|
+
executablePath: await resolveBrowserExecutablePath(),
|
|
113
|
+
args: LOCAL_LAUNCH_ARGS,
|
|
114
|
+
protocolTimeout: BROWSER_PROTOCOL_TIMEOUT_MS,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
65
117
|
/**
|
|
66
118
|
* Resolve which browser binary to launch, in order of preference:
|
|
67
119
|
* 1. PUPPETEER_EXECUTABLE_PATH (explicit override)
|
|
@@ -421,21 +473,9 @@ class ContentProcessor {
|
|
|
421
473
|
let browser = null;
|
|
422
474
|
let page = null;
|
|
423
475
|
const launchBrowser = async () => {
|
|
424
|
-
const executablePath = await resolveBrowserExecutablePath();
|
|
425
|
-
const launchOptions = {
|
|
426
|
-
executablePath,
|
|
427
|
-
args: [
|
|
428
|
-
'--no-sandbox',
|
|
429
|
-
'--disable-setuid-sandbox',
|
|
430
|
-
'--disable-dev-shm-usage', // Use /tmp instead of /dev/shm (critical in Docker, default 64MB)
|
|
431
|
-
'--disable-gpu',
|
|
432
|
-
'--disable-extensions',
|
|
433
|
-
],
|
|
434
|
-
protocolTimeout: 60000,
|
|
435
|
-
};
|
|
436
476
|
let b;
|
|
437
477
|
try {
|
|
438
|
-
b = await
|
|
478
|
+
b = await openBrowser();
|
|
439
479
|
}
|
|
440
480
|
catch (firstError) {
|
|
441
481
|
// One retry after a pause — the failure may be transient (e.g.
|
|
@@ -445,7 +485,7 @@ class ContentProcessor {
|
|
|
445
485
|
logger.warn(`Browser launch failed (${memoryDiagnostics()}), retrying once in 5s...`, firstError);
|
|
446
486
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
447
487
|
try {
|
|
448
|
-
b = await
|
|
488
|
+
b = await openBrowser();
|
|
449
489
|
}
|
|
450
490
|
catch (secondError) {
|
|
451
491
|
throw new BrowserLaunchError(`browser failed to launch twice (${memoryDiagnostics()}): ${secondError instanceof Error ? secondError.message : String(secondError)}`);
|
|
@@ -962,18 +1002,8 @@ class ContentProcessor {
|
|
|
962
1002
|
page = existingPage;
|
|
963
1003
|
}
|
|
964
1004
|
else {
|
|
965
|
-
// Standalone mode:
|
|
966
|
-
browser = await
|
|
967
|
-
executablePath: await resolveBrowserExecutablePath(),
|
|
968
|
-
args: [
|
|
969
|
-
'--no-sandbox',
|
|
970
|
-
'--disable-setuid-sandbox',
|
|
971
|
-
'--disable-dev-shm-usage',
|
|
972
|
-
'--disable-gpu',
|
|
973
|
-
'--disable-extensions',
|
|
974
|
-
],
|
|
975
|
-
protocolTimeout: 60000,
|
|
976
|
-
});
|
|
1005
|
+
// Standalone mode: open a browser for this single page
|
|
1006
|
+
browser = await openBrowser();
|
|
977
1007
|
page = await browser.newPage();
|
|
978
1008
|
}
|
|
979
1009
|
logger.debug(`Navigating to ${url}`);
|
|
@@ -1062,7 +1092,7 @@ class ContentProcessor {
|
|
|
1062
1092
|
classesToPreserve: ['article-content', 'original-h1'],
|
|
1063
1093
|
});
|
|
1064
1094
|
const article = reader.parse();
|
|
1065
|
-
if (!article) {
|
|
1095
|
+
if (!article || article.content == null) {
|
|
1066
1096
|
logger.warn(`Failed to parse article content with Readability for ${url}`);
|
|
1067
1097
|
return { content: null, links, finalUrl, etag };
|
|
1068
1098
|
}
|