@imenam/simple-scraper 1.0.29 → 1.0.30
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 +2 -2
- package/dist/capture.js +52 -29
- package/dist/keep-alive.js +11 -1
- package/dist/site-check.js +28 -5
- package/dist/timeout.js +16 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -355,7 +355,7 @@ Capture a screenshot of a page. Use `session_id` to capture an active session in
|
|
|
355
355
|
|
|
356
356
|
The capture area is resolved by precedence: `selector` → `clip` → `full_page` → the visible viewport.
|
|
357
357
|
|
|
358
|
-
Captures run one at a time across the whole server (this tool and the GUI views alike), each after bringing its page to the front: with several pages open, headless Chrome stalls the capture of a page that is not in front. A capture that gets no answer within 20 s fails without holding up the next ones.
|
|
358
|
+
Captures run one at a time across the whole server (this tool and the GUI views alike), each after bringing its page to the front: with several pages open, headless Chrome stalls the capture of a page that is not in front. A capture of this tool that gets no answer within 20 s fails without holding up the next ones. The GUI views capture through a CDP session of their own, outside Puppeteer's screenshot lock, with a 4 s limit: a stalled capture is dropped along with its session and never blocks the following ones.
|
|
359
359
|
|
|
360
360
|
| Parameter | Type | Required | Description |
|
|
361
361
|
|-----------|------|----------|-------------|
|
|
@@ -412,7 +412,7 @@ Every browser of the scraper starts with Chromium's sandbox disabled (`--no-sand
|
|
|
412
412
|
|
|
413
413
|
### Keep-alive
|
|
414
414
|
|
|
415
|
-
When enabled, the keep-alive visits the site's **Page visitée** at a fixed interval (5 min to 1 h), day and night, so that the session does not expire from inactivity. Each visit is also a check: if the login sign no longer appears, the site is marked **À reconnecter**, shown in the banner of the Sites page (unless disabled for that site), and the keep-alive stops visiting it until the next login. The visited page is also used by **Vérifier maintenant**.
|
|
415
|
+
When enabled, the keep-alive visits the site's **Page visitée** at a fixed interval (5 min to 1 h), day and night, so that the session does not expire from inactivity. Each visit is also a check: if the login sign no longer appears, the site is marked **À reconnecter**, shown in the banner of the Sites page (unless disabled for that site), and the keep-alive stops visiting it until the next login. The visited page is also used by **Vérifier maintenant**. Each check is bounded in time (navigation timeout, plus the wait for the sign and a margin), and the keep-alive checks every site on its own: a site that fails, hangs or must be reconnected never stops the others from being checked.
|
|
416
416
|
|
|
417
417
|
The keep-alive cannot prevent a logout the site imposes on its own schedule (e.g. every night): it only avoids inactivity timeouts. After such a logout, the assisted login takes a few seconds per site.
|
|
418
418
|
|
package/dist/capture.js
CHANGED
|
@@ -1,37 +1,60 @@
|
|
|
1
|
-
|
|
1
|
+
import { withTimeout } from './timeout.js';
|
|
2
|
+
/** Délai des captures de l'outil `screenshot`. */
|
|
3
|
+
const TOOL_CAPTURE_TIMEOUT_MS = 20_000;
|
|
4
|
+
/** Délai des captures de la GUI, plus court que les 5 s au bout desquelles la GUI abandonne sa requête. */
|
|
5
|
+
const VIEW_CAPTURE_TIMEOUT_MS = 4_000;
|
|
2
6
|
let queue = Promise.resolve();
|
|
3
7
|
/**
|
|
4
|
-
* Exécute
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Une capture qui dépasse le délai échoue sans bloquer les suivantes.
|
|
8
|
+
* Exécute les captures une seule à la fois pour tout le process. Chrome sans
|
|
9
|
+
* fenêtre bloque la capture d'une page qui n'est pas au premier plan dès que
|
|
10
|
+
* plusieurs pages sont ouvertes, et deux mises au premier plan simultanées se
|
|
11
|
+
* contrarient : chaque capture met donc sa page au premier plan, seule.
|
|
9
12
|
*/
|
|
10
|
-
|
|
11
|
-
const run = async () => {
|
|
12
|
-
let timer;
|
|
13
|
-
const timeout = new Promise((_, reject) => {
|
|
14
|
-
timer = setTimeout(() => reject(new Error(`Capture d'écran sans réponse après ${CAPTURE_TIMEOUT_MS / 1000} s.`)), CAPTURE_TIMEOUT_MS);
|
|
15
|
-
});
|
|
16
|
-
try {
|
|
17
|
-
return await Promise.race([
|
|
18
|
-
(async () => {
|
|
19
|
-
await page.bringToFront();
|
|
20
|
-
return capture();
|
|
21
|
-
})(),
|
|
22
|
-
timeout,
|
|
23
|
-
]);
|
|
24
|
-
}
|
|
25
|
-
finally {
|
|
26
|
-
clearTimeout(timer);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
13
|
+
function enqueue(run) {
|
|
29
14
|
const next = queue.then(run, run);
|
|
30
15
|
queue = next.catch(() => undefined);
|
|
31
16
|
return next;
|
|
32
17
|
}
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Capture de l'outil `screenshot`, avec les options de Puppeteer (élément, zone,
|
|
20
|
+
* page entière). Une capture qui dépasse le délai échoue sans retenir la file.
|
|
21
|
+
*/
|
|
22
|
+
export function runCapture(page, capture) {
|
|
23
|
+
return enqueue(() => withTimeout((async () => {
|
|
24
|
+
await page.bringToFront();
|
|
25
|
+
return capture();
|
|
26
|
+
})(), TOOL_CAPTURE_TIMEOUT_MS, `Capture d'écran sans réponse après ${TOOL_CAPTURE_TIMEOUT_MS / 1000} s.`));
|
|
27
|
+
}
|
|
28
|
+
const viewSessions = new WeakMap();
|
|
29
|
+
async function viewSessionFor(page) {
|
|
30
|
+
const existing = viewSessions.get(page);
|
|
31
|
+
if (existing && !existing.detached)
|
|
32
|
+
return existing;
|
|
33
|
+
const session = await page.createCDPSession();
|
|
34
|
+
viewSessions.set(page, session);
|
|
35
|
+
return session;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Capture JPEG de la partie visible de la page pour la GUI, encodée en base64.
|
|
39
|
+
* Elle passe par une session CDP propre à la page plutôt que par
|
|
40
|
+
* `page.screenshot()`, dont le verrou reste pris tant qu'une capture n'a pas
|
|
41
|
+
* abouti. Après un dépassement de délai, la session est détachée et la suivante
|
|
42
|
+
* repart d'une session neuve : une capture abandonnée ne bloque rien.
|
|
43
|
+
*/
|
|
44
|
+
export function captureJpegBase64(page, quality) {
|
|
45
|
+
return enqueue(async () => {
|
|
46
|
+
const session = await viewSessionFor(page);
|
|
47
|
+
try {
|
|
48
|
+
return await withTimeout((async () => {
|
|
49
|
+
await session.send('Page.bringToFront');
|
|
50
|
+
const { data } = await session.send('Page.captureScreenshot', { format: 'jpeg', quality });
|
|
51
|
+
return data;
|
|
52
|
+
})(), VIEW_CAPTURE_TIMEOUT_MS, `Capture d'écran sans réponse après ${VIEW_CAPTURE_TIMEOUT_MS / 1000} s.`);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
viewSessions.delete(page);
|
|
56
|
+
await withTimeout(session.detach(), 1_000, 'Détachement sans réponse.').catch(() => undefined);
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
37
60
|
}
|
package/dist/keep-alive.js
CHANGED
|
@@ -21,6 +21,11 @@ export function nextKeepAliveAt(now = Date.now()) {
|
|
|
21
21
|
return times.length ? Math.min(...times) : null;
|
|
22
22
|
}
|
|
23
23
|
let ticking = false;
|
|
24
|
+
/**
|
|
25
|
+
* Vérifie, l'un après l'autre, les sites dont le passage est dû. Chaque site est
|
|
26
|
+
* traité à part : son échec n'empêche pas les suivants d'être vérifiés, et la
|
|
27
|
+
* durée bornée de chaque vérification empêche un site bloqué de figer le tour.
|
|
28
|
+
*/
|
|
24
29
|
async function tick() {
|
|
25
30
|
if (ticking)
|
|
26
31
|
return;
|
|
@@ -34,7 +39,12 @@ async function tick() {
|
|
|
34
39
|
const next = nextVisitAt(site, now);
|
|
35
40
|
if (next === null || next > now)
|
|
36
41
|
continue;
|
|
37
|
-
|
|
42
|
+
try {
|
|
43
|
+
await checkSite(site.id);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
console.error(`[KeepAlive] ${site.name} : ${err instanceof Error ? err.message : String(err)}`);
|
|
47
|
+
}
|
|
38
48
|
}
|
|
39
49
|
}
|
|
40
50
|
catch (err) {
|
package/dist/site-check.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getBrowser, getDefaultTimeout } from './browser.js';
|
|
2
|
-
import {
|
|
2
|
+
import { scheduleJarSync } from './cookie-sync.js';
|
|
3
3
|
import { requireStore } from './site-store.js';
|
|
4
|
+
import { withTimeout } from './timeout.js';
|
|
4
5
|
const SELECTOR_WAIT_MS = 5_000;
|
|
5
6
|
/** L'adresse, sans paramètres ni ancre, avec ou sans son port, contient la valeur attendue. */
|
|
6
7
|
export function urlMatchesSignal(url, value) {
|
|
@@ -39,10 +40,20 @@ const running = new Set();
|
|
|
39
40
|
export function isChecking(siteId) {
|
|
40
41
|
return running.has(siteId);
|
|
41
42
|
}
|
|
43
|
+
/** Ferme la page sans attendre : une page bloquée ne doit pas retenir la vérification. */
|
|
44
|
+
async function closeLater(page) {
|
|
45
|
+
await page?.close().catch(() => undefined);
|
|
46
|
+
}
|
|
47
|
+
/** Durée maximale d'une vérification : navigation, attente du signe et marge. */
|
|
48
|
+
function checkTimeoutMs() {
|
|
49
|
+
return getDefaultTimeout() + SELECTOR_WAIT_MS + 10_000;
|
|
50
|
+
}
|
|
42
51
|
/**
|
|
43
52
|
* Visite la page de vérification du site avec ses cookies et met à jour son
|
|
44
53
|
* état : connecté si le signe de connexion apparaît, déconnecté sinon. Une
|
|
45
|
-
* page injoignable
|
|
54
|
+
* page injoignable, ou une vérification qui dépasse sa durée maximale, laisse
|
|
55
|
+
* l'état inchangé et enregistre l'erreur ; un résultat arrivé après ce délai
|
|
56
|
+
* est ignoré.
|
|
46
57
|
*/
|
|
47
58
|
export async function checkSite(siteId) {
|
|
48
59
|
const store = requireStore();
|
|
@@ -54,10 +65,17 @@ export async function checkSite(siteId) {
|
|
|
54
65
|
return site;
|
|
55
66
|
running.add(siteId);
|
|
56
67
|
let page = null;
|
|
57
|
-
|
|
68
|
+
let settled = false;
|
|
69
|
+
const visit = async () => {
|
|
58
70
|
page = await (await getBrowser()).newPage();
|
|
71
|
+
if (settled) {
|
|
72
|
+
void closeLater(page);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
59
75
|
await page.goto(site.keepAlive.pageUrl, { waitUntil: 'networkidle2', timeout: getDefaultTimeout() });
|
|
60
76
|
const connected = await pageShowsSignal(page, site.signal, SELECTOR_WAIT_MS);
|
|
77
|
+
if (settled)
|
|
78
|
+
return;
|
|
61
79
|
const now = Date.now();
|
|
62
80
|
if (connected) {
|
|
63
81
|
store.setState(siteId, { status: 'connected', lostAt: null, lastCheckAt: now, lastCheckError: null });
|
|
@@ -73,6 +91,10 @@ export async function checkSite(siteId) {
|
|
|
73
91
|
});
|
|
74
92
|
store.addEvent(siteId, 'check-lost', `redirigé vers ${describePath(page.url())}`);
|
|
75
93
|
}
|
|
94
|
+
};
|
|
95
|
+
try {
|
|
96
|
+
const limit = checkTimeoutMs();
|
|
97
|
+
await withTimeout(visit(), limit, `Vérification sans réponse après ${Math.round(limit / 1000)} s.`);
|
|
76
98
|
}
|
|
77
99
|
catch (err) {
|
|
78
100
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -80,9 +102,10 @@ export async function checkSite(siteId) {
|
|
|
80
102
|
store.addEvent(siteId, 'check-error', message);
|
|
81
103
|
}
|
|
82
104
|
finally {
|
|
83
|
-
|
|
105
|
+
settled = true;
|
|
84
106
|
running.delete(siteId);
|
|
107
|
+
void closeLater(page);
|
|
85
108
|
}
|
|
86
|
-
|
|
109
|
+
scheduleJarSync();
|
|
87
110
|
return store.get(siteId);
|
|
88
111
|
}
|
package/dist/timeout.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Résout comme `work`, ou échoue avec `message` si `work` dépasse `ms`. L'opération
|
|
3
|
+
* dépassée n'est pas interrompue : l'appelant libère lui-même ce qu'elle retient.
|
|
4
|
+
*/
|
|
5
|
+
export async function withTimeout(work, ms, message) {
|
|
6
|
+
let timer;
|
|
7
|
+
const timeout = new Promise((_, reject) => {
|
|
8
|
+
timer = setTimeout(() => reject(new Error(message)), ms);
|
|
9
|
+
});
|
|
10
|
+
try {
|
|
11
|
+
return await Promise.race([work, timeout]);
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
clearTimeout(timer);
|
|
15
|
+
}
|
|
16
|
+
}
|