fix: cookie clone from pristine profile, pool cleanup, intelligent sitemap optimization, download/403 handling#789
Draft
younglim wants to merge 15 commits into
Draft
Conversation
…ed pools
Previously the first browser in each pool used userDataDirectory
directly, allowing Chrome to modify the cookie database. All subsequent
pool rotations then copied from this tainted base rather than the
original user's cookies, causing 403s after ~3700 pages.
Changes:
- Every browser (including the first) gets its own _pool{N} directory
- Cookies are always copied from the untouched userDataDirectory clone
- Use Crawlee's postPageCloseHook to delete retired pool directories
once their browser has no active pages, preventing disk bloat
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…crawls Each browser pool instance accumulates HTTP/GPU/code caches that can grow to 100-500MB. On a 3700-page crawl with 7+ pool rotations, this can exhaust small Docker storage provisions. Capping at 10MB retains cache benefit for repeated assets while keeping each pool directory small. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix --disk-cache-size from 100MB to 10MB (extra zero in value) - Move mid-scan pool cleanup to getPreLaunchHook with 35-40s delay (postPageCloseHook fires between pages, not just on retirement) - Add retry loop (10x at 5s) in cleanUp() for Windows file locks - Harden postPageCloseHook with 5s delay on Windows and 3 retries .
…ent sitemap Pool directory cleanup: - Fix --disk-cache-size from 100MB to 10MB (extra zero in value) - Move mid-scan pool cleanup to getPreLaunchHook with 35-40s delay (postPageCloseHook fires between pages, not just on retirement) - Harden postPageCloseHook with 5s delay on Windows and 3 retries - Add retry loop (10x at 5s) in cleanUp() for Windows file locks Intelligent sitemap optimization: - crawlSitemap now performs enqueueLinks on each scanned page when fromCrawlIntelligentSitemap is true, discovering <a> links without additional page loads (RequestQueue items processed after RequestList) - Skip second-pass click-discovery loop in crawlDomain when called from intelligent sitemap (prevents 10+ hour hang on 3000+ page sites) - No behavior change for standalone sitemap or website scans
```
fix: glob backslash escape on Windows preventing pool dir cleanup
globSync interprets backslashes as escape characters, not path
separators. On Windows, `${userDataDirectory}_pool*` contains
backslashes (e.g. `C:\Users\...\oobee-..._pool*`) which causes
the pattern to match nothing. The retry loop exits immediately
with "nothing left to clean" and pool dirs are never deleted.
Fix: normalize backslashes to forward slashes before globbing.
```
``` fix: handle download navigation errors and retry rate-limited 403 URLs - Detect "Download is starting" errors in failedRequestHandler (Playwright throws when page.goto hits a file download URL instead of a page). If PDF scanning enabled: re-enqueue with skipNavigation for PDF download. If disabled: classify as "Not A Supported Document" instead of errored. - Re-enqueue 403 URLs once with rateLimitRetried flag so they get another chance after adaptive concurrency recovers. Prevents permanent loss of accessible pages that failed during a rate-limit burst window. - Fix glob backslash escape on Windows preventing pool dir cleanup (backslashes in path interpreted as escape chars, pattern matched nothing) ```
… breaker count on 403 retry - Emit SKIPPED log when download URLs are classified as unsupported (previously silent — user couldn't see these in progress output) - Remove onFailure() call from the 403 re-enqueue path so each URL only counts once toward the circuit breaker (on final failure), not twice (once on first fail, once on retry fail)
… breaker count on 403 retry - Emit SKIPPED log when download URLs are classified as unsupported (previously silent — user couldn't see these in progress output) - Remove onFailure() call from the 403 re-enqueue path so each URL only counts once toward the circuit breaker (on final failure), not twice (once on first fail, once on retry fail)
…ling Previously requestQueue was only created for intelligent sitemap mode. This meant standalone sitemap scans with isScanPdfs=true would miss dynamic download URLs (e.g. Salesforce /download/ endpoints) that trigger "Download is starting" errors — they were classified as unsupported documents instead of being re-enqueued for PDF scanning. Now requestQueue is always created. An empty queue has zero impact on crawl behavior (Crawlee processes RequestList first). Enables download re-enqueue and 403 rate-limit retry for all crawlSitemap modes.
Crawlee Lifecycle: Added "Download is starting" error handling and 403 rate-limit retry documentation Intelligent Sitemap section: Removed outdated && requestQueue guard reference, added note that requestQueue is always created (enables download/403 handling in all modes)
… Not Scanned Image URLs (.png, .jpg) from sitemaps and URLs that redirect to PDFs were classified with STATUS_CODE_METADATA[200] and httpStatusCode: 200, which the report UI shows as "Pages Not Scanned". The fix: - crawlSitemap preNavigationHook: detect known non-scannable extensions and skip navigation entirely (avoids wasting browser resources) - crawlSitemap else block: check content-type after navigation — if non-HTML, classify as STATUS_CODE_METADATA[1] with httpStatusCode: 1 - crawlDomain: uncomment classification code in shouldSkipDueToUnsupportedContent and isBlacklistedFileExtensions (previously silently dropped these URLs) No impact to HTML page scanning — text/html pages never reach these paths and continue through the full lifecycle unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dFileExtensions Expand the shared constant with additional non-scannable extensions (woff2, ico, bmp, tiff, avi, mov, doc, docx, xls, xlsx, ppt, pptx, etc.) and use it in both crawlers' preNavigationHooks to skip navigation entirely for known non-HTML URLs. - crawlSitemap: replace inline array with blackListedFileExtensions - crawlDomain: add preNavigationHook that skips navigation for blacklisted extensions (previously relied on post-navigation check) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…vigationHook usage Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix: cookie clone from pristine profile, pool cleanup, intelligent sitemap optimization, download/403 handling, unsupported document classification
Summary
<a>links during sitemap phase, skip second-pass click-discovery in domain phaseProblem
403 errors after ~3700 pages in intelligent crawl
The first browser (
launchCount === 1) useduserDataDirectorydirectly — Chrome modified the Cookies DB, added journal files, and tainted the session. All subsequent pool rotations copied from this corrupted base instead of the user's original authenticated cookies.Out of storage on Docker / disk bloat locally
Each pool rotation created a Chrome profile with unbounded cache (100-500MB each). A 3700-page crawl with 7+ rotations could accumulate 700MB-3.5GB on disk, all held until scan end. This exhausts small Docker storage provisions mid-crawl.
Additionally, mid-scan cleanup was unreliable:
postPageCloseHookfired too early: The hook fires on every page close whereactivePages === 0, but between pages the pool immediately assigns new pages to the same browser. The 2ssetTimeoutfired while Chrome still had file locks,rmfailed silently, and directories survived.cleanUp()sweep broken on Windows:globSyncinterprets backslashes as escape characters, not path separators. The patternC:\...\oobee-..._pool*matched nothing — pool dirs were never found for deletion. No retry logic existed for Chrome's async shutdown file locks.Intelligent sitemap scan hangs for hours after completing page scans
After
crawlDomain's firstcrawler.run()completes, a second-pass loop re-visits ALL scanned same-hostname pages forcustomEnqueueLinksByClickingElements. On a 3400-page site at rate-limited concurrency 1, this takes 10+ hours with no log output (appears hung). On Linux the circuit breaker fires (stricter WAF on datacenter IPs) which setsisAbortingScanNow = trueand skips the loop. On Windows residential IPs, the adaptive concurrency successfully recovers, the circuit breaker never fires, and the loop runs unbounded.Download URLs classified as crawler errors
URLs like Salesforce
/download/endpoints trigger a file download instead of loading a page. Playwright throws "Download is starting", Crawlee retries 3 times (all fail), andfailedRequestHandlerclassifies them as "Web Crawler Errored". Should be "Not A Supported Document" (or downloaded as PDF if scanning enabled).Rate-limited 403 URLs permanently lost
Crawlee retries 3 times in rapid succession (all during the same rate-limit window), exhausts retries, and records as error. The URL is never retried after concurrency recovers.
Non-HTML URLs misclassified as "Pages Not Scanned"
Sitemap URLs pointing to images (
.png,.jpg) or URLs that redirect (302) to PDFs/images return HTTP 200 with non-HTML content-type. In crawlSitemap, whenisWhitelistedContentTypefails for these, they fall to the else block which pushes tourlsCrawled.invalidwithSTATUS_CODE_METADATA[200]("Oobee was not able to scan the page due to access restrictions or compatibility issues") andhttpStatusCode: 200. The report UI only classifies items as "Unsupported Documents" whenhttpStatusCode === 1, so these appeared under "Pages Not Scanned". In crawlDomain,shouldSkipDueToUnsupportedContentandisBlacklistedFileExtensionsdetected them correctly but silently returned without pushing to any array.Fix
1. Cookie source (pristine clone)
Every browser gets its own
_pool{N}directory. The baseuserDataDirectoryis treated as read-only — cookies are always copied fresh from it. No running browser ever touches the original authenticated profile.2. Eager cleanup via
getPreLaunchHookWhen a new browser launches (meaning the previous one was retired), schedule cleanup of the previous pool directory after 35-40s (accounts for
closeInactiveBrowserAfterSecs: 30+ Windows file-lock grace). 3 retries with 5s backoff.3.
postPageCloseHookhardened (secondary cleanup)Increased initial delay to 5s on Windows (2s elsewhere), added 3 retries with 2s backoff as secondary best-effort cleanup for when the preLaunchHook path doesn't fire (e.g., final browser before scan ends).
4.
cleanUp()sweep fixed for WindowsNormalize backslashes to forward slashes in glob pattern. Added retry loop (up to 10 times at 5s intervals) for Chrome's async shutdown file locks.
5. Disk cache capped
--disk-cache-size=10485760(10MB) added to Chrome launch args. Retains cache benefit for repeated CSS/JS assets while keeping each pool directory small (~15-25MB vs 100-500MB).Peak disk usage: ~30-50MB (down from potentially 3.5GB).
6. Skip second-pass click-discovery for intelligent scans
Added
!fromCrawlIntelligentSitemapguard on the second-pass loop incrawlDomain. The domain phase of intelligent crawl is only meant to discover new pages via<a>links, not exhaustively click thousands of already-scanned pages.7. Link discovery during sitemap phase
crawlSitemapnow always creates aRequestQueuealongside theRequestList. WhenfromCrawlIntelligentSitemapis true, it callsenqueueLinksafter each successful scan to extract<a>links. Discovered URLs are scanned in the same pass (after all sitemap URLs complete). This eliminates most work the subsequentcrawlDomainsupplement phase would need to do.8. Download error handling
In
failedRequestHandler, detect "Download is starting" errors. If PDF scanning enabled: re-enqueue withskipNavigation: truefor the PDF download path. If disabled: classify as "Not A Supported Document" with SKIPPED log.9. 403 rate-limit retry
In
failedRequestHandler, re-enqueue 403 URLs once (withrateLimitRetriedflag) so they get a fresh attempt after adaptive concurrency recovers. NoonFailurecounted on the first pass to avoid double-counting toward the circuit breaker.10. Windows Eco QoS disabled
--disable-features=UseEcoQoSForBackgroundProcessprevents Windows from throttling background Chromium processes to efficiency cores.11. Non-HTML URL classification
Shared
blackListedFileExtensions(inconstants.ts): Expanded with additional non-scannable types (woff2,ico,bmp,tiff,tif,avi,mov,wmv,flv,ogg,wav,doc,docx,xls,xlsx,ppt,pptx). Both crawlers reference this single list.Both crawlers' preNavigationHooks: Detect URLs with blacklisted extensions and skip navigation entirely — avoids wasting browser resources loading images/media. In crawlSitemap, also sets
isNotSupportedDocumentuserData for immediate classification in the requestHandler.crawlSitemap else block: When navigation succeeds but
content-typeis non-HTML (detected via!contentType.startsWith('text/html') && !contentType.includes('html')), classifies asSTATUS_CODE_METADATA[1]withhttpStatusCode: 1. Handles 302-to-PDF and image URLs served with 200. Empty content-type falls through to existing logic.crawlDomain:
shouldSkipDueToUnsupportedContentandisBlacklistedFileExtensionsblocks now push tourlsCrawled.userExcludedwithhttpStatusCode: 1instead of silently returning. The preNavigationHook catches blacklisted extensions before navigation; for URLs without obvious extensions (e.g. redirects to non-HTML), the post-navigation content-type check is the safety net.No impact to HTML page scanning: HTML pages never reach these paths —
shouldSkipDueToUnsupportedContentreturns false fortext/html, extensions don't match, andisWhitelistedContentTypepasses. The full lifecycle (waitForPageLoaded → DOM mutation observer → runAxeScript) is unchanged.Standalone sitemap and website scans: Download handling and 403 retry now work in all modes (requestQueue always available).
enqueueLinksdiscovery remains gated to intelligent mode only.Test plan
userDataDirectorybase clone remains unmodified throughout the scan<a>links are scanned after sitemap URLs complete (check report for non-sitemap pages)enqueueLinksbehavior, but download/403 handling works_pool1,_pool2, etc. on both platforms