← back to Codex Review 2026 04 30
am-recrawl/poke.md
54 lines
**Failure mode:** Chrome/CDP disconnects once, then the scraper keeps processing the queue against a dead browser. `scrapeProductPage()` creates `page` before its `try` at `scrape-andrewmartin.js:173-175`, so `browser.newPage()` / `setUserAgent()` failures escape to `scrapeAllProducts()` and are swallowed at `537-541`. That explains the repeated `Connection closed` work. The final `TargetCloseError` is a pending Puppeteer `evaluate()` after the target is already gone, commonly at `184`.
**Root causes:** `scrape-andrewmartin.js:596-599` calls `closePool()` after `main()` already ran its `finally` and called it at `590-592`. `scraper-utils.js:184-185` wraps raw `pool.end()`, which is not idempotent, hence “Called end on pool more than once”.
**Service model:** one-shot. Header usage says `node scrape-andrewmartin.js`, no server/listener loop, and `main()` exits after final stats. PM2 should use `autorestart:false` / `--no-autorestart`; use cron for scheduling.
```diff
diff --git a/scraper-utils.js b/scraper-utils.js
@@
-async function closePool() {
- await pool.end();
-}
+let poolClosePromise = null;
+async function closePool() {
+ if (!poolClosePromise) poolClosePromise = pool.end();
+ return poolClosePromise;
+}
diff --git a/scrape-andrewmartin.js b/scrape-andrewmartin.js
@@
async function scrapeProductPage(browser, url) {
- const page = await browser.newPage();
- await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
+ let page;
try {
+ if (!browser.isConnected()) throw new Error('Puppeteer browser disconnected');
+ page = await browser.newPage();
+ await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
@@
- await page.close();
+ if (!page.isClosed()) await page.close();
return result;
} catch (err) {
console.error(' Error scraping ' + url + ': ' + err.message);
- await page.close().catch(() => {});
+ if (page && !page.isClosed()) await page.close().catch(() => {});
+ if (/Target closed|Connection closed|browser disconnected/i.test(err.message)) throw err;
return { product: null, variantUrls: [] };
}
}
@@
while (queue.length > 0) {
+ if (!browser.isConnected()) throw new Error('Puppeteer browser disconnected; aborting scrape');
@@
-main().catch(function(err) {
+main().catch(function(err) {
console.error('FATAL:', err);
- closePool().then(function() { process.exit(1); });
+ process.exitCode = 1;
});
```
PM2 patch/config: run as `pm2 start scrape-andrewmartin.js --name scrape-andrewmartin --no-autorestart`.