@taodigital/crawler 0.0.0-stage → 1.2.1
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/Dockerfile +82 -0
- package/README.md +10 -2
- package/dist/accessibility-auditor.d.ts +29 -0
- package/dist/accessibility-auditor.js +57 -0
- package/dist/accessibility-auditor.js.map +7 -0
- package/dist/accessibility-tree-crawler.d.ts +44 -0
- package/dist/accessibility-tree-crawler.js +156 -0
- package/dist/accessibility-tree-crawler.js.map +7 -0
- package/dist/advanced-ui-probes.d.ts +47 -0
- package/dist/advanced-ui-probes.js +310 -0
- package/dist/advanced-ui-probes.js.map +7 -0
- package/dist/ai-login.d.ts +32 -0
- package/dist/ai-login.js +250 -0
- package/dist/ai-login.js.map +7 -0
- package/dist/auth-token-rotator.d.ts +47 -0
- package/dist/auth-token-rotator.js +58 -0
- package/dist/auth-token-rotator.js.map +7 -0
- package/dist/auth.d.ts +20 -0
- package/dist/auth.js +348 -0
- package/dist/auth.js.map +7 -0
- package/dist/browser-extension.d.ts +37 -0
- package/dist/browser-extension.js +113 -0
- package/dist/browser-extension.js.map +7 -0
- package/dist/captcha-solver.d.ts +16 -0
- package/dist/captcha-solver.js +105 -0
- package/dist/captcha-solver.js.map +7 -0
- package/dist/color-contrast.d.ts +30 -0
- package/dist/color-contrast.js +78 -0
- package/dist/color-contrast.js.map +7 -0
- package/dist/content-chunker.d.ts +18 -0
- package/dist/content-chunker.js +152 -0
- package/dist/content-chunker.js.map +7 -0
- package/dist/content-dedup.d.ts +23 -0
- package/dist/content-dedup.js +60 -0
- package/dist/content-dedup.js.map +7 -0
- package/dist/cookie-consent.d.ts +25 -0
- package/dist/cookie-consent.js +78 -0
- package/dist/cookie-consent.js.map +7 -0
- package/dist/cookie-jar.d.ts +20 -0
- package/dist/cookie-jar.js +61 -0
- package/dist/cookie-jar.js.map +7 -0
- package/dist/crawl-session-manager.d.ts +44 -0
- package/dist/crawl-session-manager.js +145 -0
- package/dist/crawl-session-manager.js.map +7 -0
- package/dist/crawler.d.ts +233 -0
- package/dist/crawler.js +6239 -0
- package/dist/crawler.js.map +7 -0
- package/dist/crud-interaction.d.ts +31 -0
- package/dist/crud-interaction.js +264 -0
- package/dist/crud-interaction.js.map +7 -0
- package/dist/custom-js-injector.d.ts +42 -0
- package/dist/custom-js-injector.js +50 -0
- package/dist/custom-js-injector.js.map +7 -0
- package/dist/dark-mode-capture.d.ts +23 -0
- package/dist/dark-mode-capture.js +75 -0
- package/dist/dark-mode-capture.js.map +7 -0
- package/dist/dead-link-detector.d.ts +37 -0
- package/dist/dead-link-detector.js +50 -0
- package/dist/dead-link-detector.js.map +7 -0
- package/dist/dropdown-enumerator.d.ts +20 -0
- package/dist/dropdown-enumerator.js +230 -0
- package/dist/dropdown-enumerator.js.map +7 -0
- package/dist/element-hash.d.ts +9 -0
- package/dist/element-hash.js +9 -0
- package/dist/element-hash.js.map +7 -0
- package/dist/email-otp.d.ts +42 -0
- package/dist/email-otp.js +177 -0
- package/dist/email-otp.js.map +7 -0
- package/dist/engine-router.d.ts +47 -0
- package/dist/engine-router.js +94 -0
- package/dist/engine-router.js.map +7 -0
- package/dist/export/bulk-export.d.ts +26 -0
- package/dist/export/bulk-export.js +76 -0
- package/dist/export/bulk-export.js.map +7 -0
- package/dist/export/vector-export.d.ts +76 -0
- package/dist/export/vector-export.js +85 -0
- package/dist/export/vector-export.js.map +7 -0
- package/dist/extractors/advanced.d.ts +64 -0
- package/dist/extractors/advanced.js +265 -0
- package/dist/extractors/advanced.js.map +7 -0
- package/dist/extractors/artifact-envelope.d.ts +63 -0
- package/dist/extractors/artifact-envelope.js +63 -0
- package/dist/extractors/artifact-envelope.js.map +7 -0
- package/dist/extractors/branding-extractor.d.ts +46 -0
- package/dist/extractors/branding-extractor.js +104 -0
- package/dist/extractors/branding-extractor.js.map +7 -0
- package/dist/extractors/summary-extractor.d.ts +10 -0
- package/dist/extractors/summary-extractor.js +36 -0
- package/dist/extractors/summary-extractor.js.map +7 -0
- package/dist/failure-analysis-agent.d.ts +16 -0
- package/dist/failure-analysis-agent.js +42 -0
- package/dist/failure-analysis-agent.js.map +7 -0
- package/dist/feature-flag-detector.d.ts +30 -0
- package/dist/feature-flag-detector.js +55 -0
- package/dist/feature-flag-detector.js.map +7 -0
- package/dist/filter-pagination-crawler.d.ts +52 -0
- package/dist/filter-pagination-crawler.js +299 -0
- package/dist/filter-pagination-crawler.js.map +7 -0
- package/dist/form-detector.d.ts +34 -0
- package/dist/form-detector.js +58 -0
- package/dist/form-detector.js.map +7 -0
- package/dist/frontier.d.ts +40 -0
- package/dist/frontier.js +103 -0
- package/dist/frontier.js.map +7 -0
- package/dist/gap-pass.d.ts +36 -0
- package/dist/gap-pass.js +115 -0
- package/dist/gap-pass.js.map +7 -0
- package/dist/graphql-detector.d.ts +39 -0
- package/dist/graphql-detector.js +86 -0
- package/dist/graphql-detector.js.map +7 -0
- package/dist/http-engine.d.ts +80 -0
- package/dist/http-engine.js +318 -0
- package/dist/http-engine.js.map +7 -0
- package/dist/incremental-diff.d.ts +116 -0
- package/dist/incremental-diff.js +137 -0
- package/dist/incremental-diff.js.map +7 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +83 -0
- package/dist/index.js.map +7 -0
- package/dist/input-fuzzer.d.ts +30 -0
- package/dist/input-fuzzer.js +105 -0
- package/dist/input-fuzzer.js.map +7 -0
- package/dist/interactive-probe.d.ts +144 -0
- package/dist/interactive-probe.js +865 -0
- package/dist/interactive-probe.js.map +7 -0
- package/dist/job-queue.d.ts +297 -0
- package/dist/job-queue.js +3812 -0
- package/dist/job-queue.js.map +7 -0
- package/dist/js-coverage.d.ts +29 -0
- package/dist/js-coverage.js +56 -0
- package/dist/js-coverage.js.map +7 -0
- package/dist/js-router-detector.d.ts +17 -0
- package/dist/js-router-detector.js +90 -0
- package/dist/js-router-detector.js.map +7 -0
- package/dist/language-detector.d.ts +22 -0
- package/dist/language-detector.js +57 -0
- package/dist/language-detector.js.map +7 -0
- package/dist/link-classifier.d.ts +23 -0
- package/dist/link-classifier.js +40 -0
- package/dist/link-classifier.js.map +7 -0
- package/dist/live-view.d.ts +36 -0
- package/dist/live-view.js +105 -0
- package/dist/live-view.js.map +7 -0
- package/dist/locator-extractor.d.ts +46 -0
- package/dist/locator-extractor.js +220 -0
- package/dist/locator-extractor.js.map +7 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +10 -0
- package/dist/logger.js.map +7 -0
- package/dist/markdown-extract.d.ts +8 -0
- package/dist/markdown-extract.js +54 -0
- package/dist/markdown-extract.js.map +7 -0
- package/dist/multi-context-handler.d.ts +55 -0
- package/dist/multi-context-handler.js +182 -0
- package/dist/multi-context-handler.js.map +7 -0
- package/dist/native-crawler.d.ts +69 -0
- package/dist/native-crawler.js +839 -0
- package/dist/native-crawler.js.map +7 -0
- package/dist/nested-window-crawler.d.ts +41 -0
- package/dist/nested-window-crawler.js +172 -0
- package/dist/nested-window-crawler.js.map +7 -0
- package/dist/network-action-correlator.d.ts +47 -0
- package/dist/network-action-correlator.js +209 -0
- package/dist/network-action-correlator.js.map +7 -0
- package/dist/network-observer.d.ts +49 -0
- package/dist/network-observer.js +149 -0
- package/dist/network-observer.js.map +7 -0
- package/dist/network-throttle.d.ts +25 -0
- package/dist/network-throttle.js +34 -0
- package/dist/network-throttle.js.map +7 -0
- package/dist/openapi-generator.d.ts +49 -0
- package/dist/openapi-generator.js +89 -0
- package/dist/openapi-generator.js.map +7 -0
- package/dist/pdf-extractor.d.ts +28 -0
- package/dist/pdf-extractor.js +81 -0
- package/dist/pdf-extractor.js.map +7 -0
- package/dist/performance-metrics-collector.d.ts +50 -0
- package/dist/performance-metrics-collector.js +155 -0
- package/dist/performance-metrics-collector.js.map +7 -0
- package/dist/performance-metrics.d.ts +52 -0
- package/dist/performance-metrics.js +83 -0
- package/dist/performance-metrics.js.map +7 -0
- package/dist/phash.d.ts +50 -0
- package/dist/phash.js +136 -0
- package/dist/phash.js.map +7 -0
- package/dist/pii-scrubber.d.ts +21 -0
- package/dist/pii-scrubber.js +75 -0
- package/dist/pii-scrubber.js.map +7 -0
- package/dist/playwright-recorder.d.ts +39 -0
- package/dist/playwright-recorder.js +123 -0
- package/dist/playwright-recorder.js.map +7 -0
- package/dist/policies/crawl-safety.d.ts +9 -0
- package/dist/policies/crawl-safety.js +41 -0
- package/dist/policies/crawl-safety.js.map +7 -0
- package/dist/profiles/domain-profile.d.ts +19 -0
- package/dist/profiles/domain-profile.js +160 -0
- package/dist/profiles/domain-profile.js.map +7 -0
- package/dist/proxy.d.ts +50 -0
- package/dist/proxy.js +165 -0
- package/dist/proxy.js.map +7 -0
- package/dist/rate-limiter.d.ts +31 -0
- package/dist/rate-limiter.js +64 -0
- package/dist/rate-limiter.js.map +7 -0
- package/dist/redirect-tracker.d.ts +37 -0
- package/dist/redirect-tracker.js +65 -0
- package/dist/redirect-tracker.js.map +7 -0
- package/dist/redis-frontier.d.ts +48 -0
- package/dist/redis-frontier.js +126 -0
- package/dist/redis-frontier.js.map +7 -0
- package/dist/redis-queue.d.ts +48 -0
- package/dist/redis-queue.js +165 -0
- package/dist/redis-queue.js.map +7 -0
- package/dist/response-cache.d.ts +38 -0
- package/dist/response-cache.js +140 -0
- package/dist/response-cache.js.map +7 -0
- package/dist/responsive-detector.d.ts +29 -0
- package/dist/responsive-detector.js +46 -0
- package/dist/responsive-detector.js.map +7 -0
- package/dist/retry-policy.d.ts +20 -0
- package/dist/retry-policy.js +54 -0
- package/dist/retry-policy.js.map +7 -0
- package/dist/robots-txt.d.ts +9 -0
- package/dist/robots-txt.js +77 -0
- package/dist/robots-txt.js.map +7 -0
- package/dist/screen-reader.d.ts +15 -0
- package/dist/screen-reader.js +48 -0
- package/dist/screen-reader.js.map +7 -0
- package/dist/screenshot-masker.d.ts +28 -0
- package/dist/screenshot-masker.js +62 -0
- package/dist/screenshot-masker.js.map +7 -0
- package/dist/seo-checker.d.ts +33 -0
- package/dist/seo-checker.js +91 -0
- package/dist/seo-checker.js.map +7 -0
- package/dist/service-worker-interceptor.d.ts +36 -0
- package/dist/service-worker-interceptor.js +136 -0
- package/dist/service-worker-interceptor.js.map +7 -0
- package/dist/session-capture.d.ts +39 -0
- package/dist/session-capture.js +274 -0
- package/dist/session-capture.js.map +7 -0
- package/dist/session-export.d.ts +49 -0
- package/dist/session-export.js +117 -0
- package/dist/session-export.js.map +7 -0
- package/dist/shadow-dom-crawler.d.ts +35 -0
- package/dist/shadow-dom-crawler.js +132 -0
- package/dist/shadow-dom-crawler.js.map +7 -0
- package/dist/sitemap-writer.d.ts +21 -0
- package/dist/sitemap-writer.js +47 -0
- package/dist/sitemap-writer.js.map +7 -0
- package/dist/sitemap.d.ts +51 -0
- package/dist/sitemap.js +160 -0
- package/dist/sitemap.js.map +7 -0
- package/dist/skyvern.d.ts +56 -0
- package/dist/skyvern.js +129 -0
- package/dist/skyvern.js.map +7 -0
- package/dist/sqs-queue.d.ts +13 -0
- package/dist/sqs-queue.js +115 -0
- package/dist/sqs-queue.js.map +7 -0
- package/dist/sso-token.d.ts +29 -0
- package/dist/sso-token.js +76 -0
- package/dist/sso-token.js.map +7 -0
- package/dist/state-graph.d.ts +59 -0
- package/dist/state-graph.js +193 -0
- package/dist/state-graph.js.map +7 -0
- package/dist/stealth/human-behavior.d.ts +20 -0
- package/dist/stealth/human-behavior.js +202 -0
- package/dist/stealth/human-behavior.js.map +7 -0
- package/dist/telemetry.d.ts +34 -0
- package/dist/telemetry.js +77 -0
- package/dist/telemetry.js.map +7 -0
- package/dist/test-executor.d.ts +81 -0
- package/dist/test-executor.js +443 -0
- package/dist/test-executor.js.map +7 -0
- package/dist/tls-fingerprint.d.ts +50 -0
- package/dist/tls-fingerprint.js +81 -0
- package/dist/tls-fingerprint.js.map +7 -0
- package/dist/url-pattern-completer.d.ts +28 -0
- package/dist/url-pattern-completer.js +122 -0
- package/dist/url-pattern-completer.js.map +7 -0
- package/dist/visual-diff-verdict.d.ts +40 -0
- package/dist/visual-diff-verdict.js +27 -0
- package/dist/visual-diff-verdict.js.map +7 -0
- package/dist/webhook-notifier.d.ts +30 -0
- package/dist/webhook-notifier.js +76 -0
- package/dist/webhook-notifier.js.map +7 -0
- package/dist/websocket-detector.d.ts +24 -0
- package/dist/websocket-detector.js +60 -0
- package/dist/websocket-detector.js.map +7 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +33 -0
- package/dist/worker.js.map +7 -0
- package/package.json +49 -4
- package/src/accessibility-auditor.ts +102 -0
- package/src/accessibility-tree-crawler.ts +246 -0
- package/src/advanced-ui-probes.ts +470 -0
- package/src/ai-login.ts +298 -0
- package/src/auth-token-rotator.ts +89 -0
- package/src/auth.ts +418 -0
- package/src/browser-extension.ts +186 -0
- package/src/captcha-solver.ts +143 -0
- package/src/color-contrast.ts +116 -0
- package/src/content-chunker.ts +170 -0
- package/src/content-dedup.ts +99 -0
- package/src/cookie-consent.ts +106 -0
- package/src/cookie-jar.ts +91 -0
- package/src/crawl-session-manager.ts +231 -0
- package/src/crawler.ts +7123 -0
- package/src/crawler.ts.rej +12 -0
- package/src/crud-interaction.ts +386 -0
- package/src/custom-js-injector.ts +93 -0
- package/src/dark-mode-capture.ts +110 -0
- package/src/dead-link-detector.ts +76 -0
- package/src/dropdown-enumerator.ts +312 -0
- package/src/element-hash.ts +16 -0
- package/src/email-otp.ts +252 -0
- package/src/engine-router.ts +141 -0
- package/src/export/bulk-export.ts +85 -0
- package/src/export/vector-export.ts +160 -0
- package/src/extractors/advanced.ts +309 -0
- package/src/extractors/artifact-envelope.ts +111 -0
- package/src/extractors/branding-extractor.ts +159 -0
- package/src/extractors/summary-extractor.ts +49 -0
- package/src/failure-analysis-agent.ts +51 -0
- package/src/feature-flag-detector.ts +97 -0
- package/src/filter-pagination-crawler.ts +500 -0
- package/src/form-detector.ts +103 -0
- package/src/frontier.test.ts +64 -0
- package/src/frontier.ts +144 -0
- package/src/gap-pass.ts +165 -0
- package/src/graphql-detector.ts +129 -0
- package/src/http-engine.ts +453 -0
- package/src/incremental-diff.ts +259 -0
- package/src/index.ts +52 -0
- package/src/input-fuzzer.ts +129 -0
- package/src/interactive-probe.ts +1136 -0
- package/src/job-queue.test.ts +187 -0
- package/src/job-queue.ts +4504 -0
- package/src/js-coverage.ts +87 -0
- package/src/js-router-detector.ts +112 -0
- package/src/language-detector.ts +82 -0
- package/src/link-classifier.ts +65 -0
- package/src/live-view.ts +146 -0
- package/src/locator-extractor.ts +309 -0
- package/src/logger.ts +9 -0
- package/src/markdown-extract.test.ts +73 -0
- package/src/markdown-extract.ts +76 -0
- package/src/multi-context-handler.ts +296 -0
- package/src/native-crawler.ts +1108 -0
- package/src/nested-window-crawler.ts +310 -0
- package/src/network-action-correlator.ts +320 -0
- package/src/network-observer.ts +204 -0
- package/src/network-throttle.ts +69 -0
- package/src/openapi-generator.ts +135 -0
- package/src/pdf-extractor.ts +123 -0
- package/src/performance-metrics-collector.ts +243 -0
- package/src/performance-metrics.ts +134 -0
- package/src/phash.ts +184 -0
- package/src/pii-scrubber.ts +99 -0
- package/src/playwright-recorder.ts +170 -0
- package/src/policies/crawl-safety.ts +52 -0
- package/src/profiles/domain-profile.ts +190 -0
- package/src/proxy.ts +238 -0
- package/src/rate-limiter.ts +87 -0
- package/src/redirect-tracker.ts +97 -0
- package/src/redis-frontier.ts +143 -0
- package/src/redis-queue.ts +208 -0
- package/src/response-cache.ts +150 -0
- package/src/responsive-detector.ts +92 -0
- package/src/retry-policy.ts +87 -0
- package/src/robots-txt.ts +96 -0
- package/src/screen-reader.ts +98 -0
- package/src/screenshot-masker.ts +98 -0
- package/src/seo-checker.ts +134 -0
- package/src/service-worker-interceptor.ts +185 -0
- package/src/session-capture.ts +335 -0
- package/src/session-export.test.ts +75 -0
- package/src/session-export.ts +172 -0
- package/src/shadow-dom-crawler.ts +198 -0
- package/src/sitemap-writer.ts +65 -0
- package/src/sitemap.test.ts +127 -0
- package/src/sitemap.ts +233 -0
- package/src/skyvern.ts +199 -0
- package/src/sqs-queue.ts +129 -0
- package/src/sso-token.ts +120 -0
- package/src/state-graph.ts +280 -0
- package/src/stealth/human-behavior.ts +227 -0
- package/src/telemetry.ts +122 -0
- package/src/test-executor.test.ts +72 -0
- package/src/test-executor.ts +578 -0
- package/src/tls-fingerprint.ts +111 -0
- package/src/url-pattern-completer.ts +161 -0
- package/src/vendor.d.ts +20 -0
- package/src/visual-diff-verdict.test.ts +80 -0
- package/src/visual-diff-verdict.ts +59 -0
- package/src/webhook-notifier.ts +109 -0
- package/src/websocket-detector.ts +83 -0
- package/src/worker.ts +50 -0
- package/tsconfig.json +13 -0
- package/types/accessibility-auditor.d.ts +29 -0
- package/types/accessibility-tree-crawler.d.ts +44 -0
- package/types/advanced-ui-probes.d.ts +47 -0
- package/types/ai-login.d.ts +32 -0
- package/types/auth-token-rotator.d.ts +47 -0
- package/types/auth.d.ts +20 -0
- package/types/browser-extension.d.ts +37 -0
- package/types/bull-worker.d.ts +6 -0
- package/types/captcha-solver.d.ts +16 -0
- package/types/color-contrast.d.ts +30 -0
- package/types/content-chunker.d.ts +18 -0
- package/types/content-dedup.d.ts +23 -0
- package/types/cookie-consent.d.ts +25 -0
- package/types/cookie-jar.d.ts +20 -0
- package/types/crawl-session-manager.d.ts +44 -0
- package/types/crawler.d.ts +233 -0
- package/types/crud-interaction.d.ts +31 -0
- package/types/custom-js-injector.d.ts +42 -0
- package/types/dark-mode-capture.d.ts +23 -0
- package/types/dead-link-detector.d.ts +37 -0
- package/types/dropdown-enumerator.d.ts +20 -0
- package/types/element-hash.d.ts +9 -0
- package/types/email-otp.d.ts +42 -0
- package/types/engine-router.d.ts +47 -0
- package/types/export/bulk-export.d.ts +26 -0
- package/types/export/vector-export.d.ts +76 -0
- package/types/extractors/advanced.d.ts +64 -0
- package/types/extractors/artifact-envelope.d.ts +63 -0
- package/types/extractors/branding-extractor.d.ts +46 -0
- package/types/extractors/summary-extractor.d.ts +10 -0
- package/types/failure-analysis-agent.d.ts +16 -0
- package/types/feature-flag-detector.d.ts +30 -0
- package/types/filter-pagination-crawler.d.ts +52 -0
- package/types/form-detector.d.ts +34 -0
- package/types/frontier.d.ts +40 -0
- package/types/gap-pass.d.ts +36 -0
- package/types/graphql-detector.d.ts +39 -0
- package/types/http-engine.d.ts +80 -0
- package/types/incremental-diff.d.ts +116 -0
- package/types/index.d.ts +52 -0
- package/types/input-fuzzer.d.ts +30 -0
- package/types/interactive-probe.d.ts +144 -0
- package/types/job-queue.d.ts +308 -0
- package/types/js-coverage.d.ts +29 -0
- package/types/js-router-detector.d.ts +17 -0
- package/types/language-detector.d.ts +22 -0
- package/types/link-classifier.d.ts +23 -0
- package/types/live-view.d.ts +36 -0
- package/types/locator-extractor.d.ts +46 -0
- package/types/logger.d.ts +2 -0
- package/types/markdown-extract.d.ts +8 -0
- package/types/multi-context-handler.d.ts +55 -0
- package/types/native-crawler.d.ts +69 -0
- package/types/nested-window-crawler.d.ts +41 -0
- package/types/network-action-correlator.d.ts +47 -0
- package/types/network-observer.d.ts +49 -0
- package/types/network-throttle.d.ts +25 -0
- package/types/openapi-generator.d.ts +49 -0
- package/types/pdf-extractor.d.ts +28 -0
- package/types/performance-metrics-collector.d.ts +50 -0
- package/types/performance-metrics.d.ts +52 -0
- package/types/phash.d.ts +50 -0
- package/types/pii-scrubber.d.ts +21 -0
- package/types/playwright-recorder.d.ts +39 -0
- package/types/policies/crawl-safety.d.ts +9 -0
- package/types/profiles/domain-profile.d.ts +19 -0
- package/types/proxy.d.ts +50 -0
- package/types/rate-limiter.d.ts +31 -0
- package/types/redirect-tracker.d.ts +37 -0
- package/types/redis-frontier.d.ts +48 -0
- package/types/redis-queue.d.ts +48 -0
- package/types/response-cache.d.ts +38 -0
- package/types/responsive-detector.d.ts +29 -0
- package/types/retry-policy.d.ts +20 -0
- package/types/robots-txt.d.ts +9 -0
- package/types/screen-reader.d.ts +15 -0
- package/types/screenshot-masker.d.ts +28 -0
- package/types/seo-checker.d.ts +33 -0
- package/types/service-worker-interceptor.d.ts +36 -0
- package/types/session-capture.d.ts +39 -0
- package/types/session-export.d.ts +49 -0
- package/types/shadow-dom-crawler.d.ts +35 -0
- package/types/sitemap-writer.d.ts +21 -0
- package/types/sitemap.d.ts +51 -0
- package/types/skyvern.d.ts +56 -0
- package/types/sqs-queue.d.ts +13 -0
- package/types/sso-token.d.ts +29 -0
- package/types/state-graph.d.ts +59 -0
- package/types/stealth/human-behavior.d.ts +20 -0
- package/types/telemetry.d.ts +34 -0
- package/types/test-executor.d.ts +84 -0
- package/types/tls-fingerprint.d.ts +50 -0
- package/types/url-pattern-completer.d.ts +28 -0
- package/types/visual-diff-verdict.d.ts +40 -0
- package/types/webhook-notifier.d.ts +30 -0
- package/types/websocket-detector.d.ts +24 -0
- package/types/worker.d.ts +1 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP-only crawl engine — 10-50x faster than browser for SSR/static sites.
|
|
3
|
+
*
|
|
4
|
+
* Uses native Node.js fetch (HTTP/2), linkedom, Readability, and Turndown.
|
|
5
|
+
* No browser overhead. Falls back to browser engine on JS-heavy sites.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - HTTP/2 native (Node 20 built-in fetch)
|
|
9
|
+
* - ETag / Last-Modified response caching
|
|
10
|
+
* - 429 / 503 exponential backoff with jitter
|
|
11
|
+
* - BFS / DFS traversal
|
|
12
|
+
* - URL regex filtering
|
|
13
|
+
* - Robots.txt compliance
|
|
14
|
+
* - Link discovery (href, action, src)
|
|
15
|
+
* - Markdown extraction (Readability + Turndown)
|
|
16
|
+
* - OG / schema.org / meta extraction
|
|
17
|
+
* - Table → JSON extraction
|
|
18
|
+
* - JSON Lines, CSV, raw HTML output formats
|
|
19
|
+
* - Streaming progress via onProgress callback
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { createHash } from 'node:crypto';
|
|
23
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
import { Readability } from '@mozilla/readability';
|
|
26
|
+
import TurndownService from 'turndown';
|
|
27
|
+
import { parseHTML } from 'linkedom';
|
|
28
|
+
import { RobotsCache } from './robots-txt.js';
|
|
29
|
+
import { ResponseCache } from './response-cache.js';
|
|
30
|
+
import { extractMetadata, extractTables, extractImages } from './extractors/advanced.js';
|
|
31
|
+
import { CHROME_CIPHERS, CHROME_CURVES } from './tls-fingerprint.js';
|
|
32
|
+
import { renderingPredictor } from './engine-router.js';
|
|
33
|
+
|
|
34
|
+
// Chrome-level TLS dispatcher: impit (true JA3/JA4 BoringSSL match) with undici fallback
|
|
35
|
+
let chromeFetchDispatcher: any;
|
|
36
|
+
async function getChromeFetchDispatcher() {
|
|
37
|
+
if (!chromeFetchDispatcher) {
|
|
38
|
+
try {
|
|
39
|
+
// impit provides native Chrome BoringSSL TLS fingerprint — true JA3/JA4 match
|
|
40
|
+
// @ts-ignore — impit is an optional dependency
|
|
41
|
+
const impitMod = await import('impit');
|
|
42
|
+
const ImpitClass = impitMod.Impit ?? impitMod.default;
|
|
43
|
+
if (ImpitClass) {
|
|
44
|
+
chromeFetchDispatcher = new ImpitClass({ browser: 'chrome' });
|
|
45
|
+
return chromeFetchDispatcher;
|
|
46
|
+
}
|
|
47
|
+
} catch { /* impit not installed — fall through to undici */ }
|
|
48
|
+
const { Agent } = await import('undici');
|
|
49
|
+
chromeFetchDispatcher = new Agent({
|
|
50
|
+
connect: {
|
|
51
|
+
ciphers: CHROME_CIPHERS,
|
|
52
|
+
ecdhCurve: CHROME_CURVES,
|
|
53
|
+
minVersion: 'TLSv1.2' as any,
|
|
54
|
+
maxVersion: 'TLSv1.3' as any,
|
|
55
|
+
},
|
|
56
|
+
keepAliveTimeout: 10_000,
|
|
57
|
+
keepAliveMaxTimeout: 30_000,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return chromeFetchDispatcher;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const turndown = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced' });
|
|
64
|
+
|
|
65
|
+
// Pages unlikely to have testable UI — skip from HTTP crawl queue
|
|
66
|
+
const _skipPathExtra = process.env.CRAWL_SKIP_PATH_EXTRA
|
|
67
|
+
? process.env.CRAWL_SKIP_PATH_EXTRA.split(',').map((p) => p.trim()).filter(Boolean)
|
|
68
|
+
: [];
|
|
69
|
+
const SKIP_PATH = _skipPathExtra.length > 0
|
|
70
|
+
? new RegExp(`/(terms|privacy|dpa|legal|cookie|changelog|blog|help|docs|about|contact|careers|press|imprint|gdpr|compliance|acceptable-use|security-policy|accessibility|${_skipPathExtra.join('|')})([-/]|$)`, 'i')
|
|
71
|
+
: /\/(terms|privacy|dpa|legal|cookie|changelog|blog|help|docs|about|contact|careers|press|imprint|gdpr|compliance|acceptable-use|security-policy|accessibility)([-/]|$)/i;
|
|
72
|
+
|
|
73
|
+
async function getHttpConfig() {
|
|
74
|
+
const { getSystemConfig } = await import('@taodigital/core');
|
|
75
|
+
const cfg = await getSystemConfig('crawl_tuning');
|
|
76
|
+
return {
|
|
77
|
+
retryLimit: parseInt(String(cfg?.httpRetryLimit ?? '5'), 10),
|
|
78
|
+
backoffCapMs: parseInt(String(cfg?.httpBackoffCapMs ?? '60000'), 10),
|
|
79
|
+
retry5xx: parseInt(String(cfg?.http5xxRetryLimit ?? '3'), 10),
|
|
80
|
+
timeoutMs: parseInt(String(cfg?.httpTimeoutMs ?? '15000'), 10),
|
|
81
|
+
concurrency: parseInt(String(cfg?.httpConcurrency ?? '5'), 10),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type HttpOutputFormat = 'json' | 'jsonlines' | 'csv' | 'markdown' | 'html';
|
|
86
|
+
|
|
87
|
+
export interface HttpScreen {
|
|
88
|
+
url: string;
|
|
89
|
+
title: string | null;
|
|
90
|
+
markdown: string;
|
|
91
|
+
html?: string;
|
|
92
|
+
metadata: {
|
|
93
|
+
description: string | null;
|
|
94
|
+
ogTitle: string | null;
|
|
95
|
+
ogDescription: string | null;
|
|
96
|
+
ogImage: string | null;
|
|
97
|
+
canonical: string | null;
|
|
98
|
+
schemaTypes: string[];
|
|
99
|
+
lang: string | null;
|
|
100
|
+
};
|
|
101
|
+
tables: Array<Record<string, string>[]>;
|
|
102
|
+
links: string[];
|
|
103
|
+
images: Array<{ src: string; alt: string }>;
|
|
104
|
+
statusCode: number;
|
|
105
|
+
fromCache: boolean;
|
|
106
|
+
requiresBrowser?: boolean;
|
|
107
|
+
durationMs: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface HttpCrawlOpts {
|
|
111
|
+
appUrl: string;
|
|
112
|
+
maxScreens?: number; // default 100
|
|
113
|
+
urlFilter?: string; // regex pattern — only crawl matching URLs
|
|
114
|
+
traversalStrategy?: 'BFS' | 'DFS'; // default BFS
|
|
115
|
+
outputFormats?: HttpOutputFormat[];
|
|
116
|
+
cacheDir?: string; // default '/tmp/zeta-cache'
|
|
117
|
+
userAgent?: string;
|
|
118
|
+
headers?: Record<string, string>;
|
|
119
|
+
respectRobotsTxt?: boolean; // default true
|
|
120
|
+
includeRawHtml?: boolean;
|
|
121
|
+
timeoutMs?: number; // per-request timeout, default 15000
|
|
122
|
+
concurrency?: number; // default 5 parallel fetches
|
|
123
|
+
onProgress?: (update: { url: string; screensFound: number; total: number; cached: boolean }) => void;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface HttpCrawlResult {
|
|
127
|
+
screens: HttpScreen[];
|
|
128
|
+
stats: {
|
|
129
|
+
total: number;
|
|
130
|
+
cached: number;
|
|
131
|
+
errors: number;
|
|
132
|
+
rateLimited: number;
|
|
133
|
+
durationMs: number;
|
|
134
|
+
cacheHitRate: number;
|
|
135
|
+
};
|
|
136
|
+
jsonLines?: string;
|
|
137
|
+
csv?: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function canonicalize(url: string): string {
|
|
141
|
+
try {
|
|
142
|
+
const u = new URL(url);
|
|
143
|
+
u.hash = '';
|
|
144
|
+
// Strip tracking params
|
|
145
|
+
for (const p of ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term', 'fbclid', 'gclid', 'ref', 'referrer']) {
|
|
146
|
+
u.searchParams.delete(p);
|
|
147
|
+
}
|
|
148
|
+
return u.href.toLowerCase().replace(/\/$/, '');
|
|
149
|
+
} catch {
|
|
150
|
+
return url.toLowerCase();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function extractLinks(html: string, baseUrl: string): string[] {
|
|
155
|
+
const links: string[] = [];
|
|
156
|
+
const seen = new Set<string>();
|
|
157
|
+
try {
|
|
158
|
+
const base = new URL(baseUrl);
|
|
159
|
+
const { document } = parseHTML(html);
|
|
160
|
+
const anchors = Array.from(document.querySelectorAll('a[href]'));
|
|
161
|
+
for (const a of anchors) {
|
|
162
|
+
const href = (a as any).getAttribute('href') ?? '';
|
|
163
|
+
if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:') || href.startsWith('javascript:')) continue;
|
|
164
|
+
try {
|
|
165
|
+
const resolved = new URL(href, base).href;
|
|
166
|
+
// Same origin only
|
|
167
|
+
if (new URL(resolved).origin !== base.origin) continue;
|
|
168
|
+
const c = canonicalize(resolved);
|
|
169
|
+
if (!seen.has(c)) { seen.add(c); links.push(resolved); }
|
|
170
|
+
} catch { /* skip */ }
|
|
171
|
+
}
|
|
172
|
+
} catch { /* non-fatal */ }
|
|
173
|
+
return links;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function extractMarkdown(html: string, url: string): { markdown: string; title: string | null } {
|
|
177
|
+
try {
|
|
178
|
+
const { document } = parseHTML(html);
|
|
179
|
+
// Remove script/style tags before Readability
|
|
180
|
+
for (const tag of Array.from(document.querySelectorAll('script, style, noscript'))) (tag as any).remove();
|
|
181
|
+
const reader = new Readability(document as any, { charThreshold: 50 });
|
|
182
|
+
const article = reader.parse();
|
|
183
|
+
if (article && article.textContent && article.textContent.split(/\s+/).length >= 30) {
|
|
184
|
+
const md = turndown.turndown(article.content ?? html);
|
|
185
|
+
return { markdown: md, title: article.title ?? null };
|
|
186
|
+
}
|
|
187
|
+
} catch { /* fall through */ }
|
|
188
|
+
// Full-page fallback
|
|
189
|
+
try {
|
|
190
|
+
const { document } = parseHTML(html);
|
|
191
|
+
for (const tag of Array.from(document.querySelectorAll('script, style, noscript, nav, footer'))) (tag as any).remove();
|
|
192
|
+
const bodyHtml = (document as any).body?.innerHTML ?? html;
|
|
193
|
+
const md = turndown.turndown(bodyHtml);
|
|
194
|
+
const titleEl = (document as any).querySelector('title');
|
|
195
|
+
return { markdown: md, title: titleEl?.textContent?.trim() ?? null };
|
|
196
|
+
} catch {
|
|
197
|
+
return { markdown: '', title: null };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
interface FetchResult {
|
|
202
|
+
body: string;
|
|
203
|
+
statusCode: number;
|
|
204
|
+
contentType: string;
|
|
205
|
+
fromCache: boolean;
|
|
206
|
+
durationMs: number;
|
|
207
|
+
etag?: string;
|
|
208
|
+
lastModified?: string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function fetchWithBackoff(
|
|
212
|
+
url: string,
|
|
213
|
+
cache: ResponseCache,
|
|
214
|
+
opts: { userAgent: string; timeoutMs: number; extraHeaders?: Record<string, string> },
|
|
215
|
+
retryCount = 0,
|
|
216
|
+
httpCfg?: Awaited<ReturnType<typeof getHttpConfig>>
|
|
217
|
+
): Promise<FetchResult> {
|
|
218
|
+
if (!httpCfg) httpCfg = await getHttpConfig();
|
|
219
|
+
const t0 = performance.now();
|
|
220
|
+
|
|
221
|
+
if (cache.isFresh(url)) {
|
|
222
|
+
const cached = cache.get(url)!;
|
|
223
|
+
return { body: cached.body, statusCode: 200, contentType: cached.contentType, fromCache: true, durationMs: 0 };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const validationHeaders = cache.getValidationHeaders(url);
|
|
227
|
+
|
|
228
|
+
const headers: Record<string, string> = {
|
|
229
|
+
'User-Agent': opts.userAgent,
|
|
230
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
|
|
231
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
232
|
+
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
233
|
+
// Chrome-like security headers reduce bot-detection fingerprint
|
|
234
|
+
'sec-ch-ua': `"Google Chrome";v="${process.env.CRAWLER_CHROME_VERSION ?? '120'}", "Chromium";v="${process.env.CRAWLER_CHROME_VERSION ?? '120'}", "Not-A.Brand";v="99"`,
|
|
235
|
+
'sec-ch-ua-mobile': '?0',
|
|
236
|
+
'sec-ch-ua-platform': '"Windows"',
|
|
237
|
+
'Sec-Fetch-Dest': 'document',
|
|
238
|
+
'Sec-Fetch-Mode': 'navigate',
|
|
239
|
+
'Sec-Fetch-Site': 'none',
|
|
240
|
+
'Sec-Fetch-User': '?1',
|
|
241
|
+
'Upgrade-Insecure-Requests': '1',
|
|
242
|
+
...validationHeaders,
|
|
243
|
+
...(opts.extraHeaders ?? {}),
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
let response: Response;
|
|
247
|
+
try {
|
|
248
|
+
const dispatcher = await getChromeFetchDispatcher();
|
|
249
|
+
response = await (fetch as any)(url, {
|
|
250
|
+
headers,
|
|
251
|
+
signal: AbortSignal.timeout(opts.timeoutMs),
|
|
252
|
+
redirect: 'follow',
|
|
253
|
+
dispatcher,
|
|
254
|
+
});
|
|
255
|
+
} catch (e: any) {
|
|
256
|
+
throw new Error(`[http-engine] fetch failed for ${url}: ${e.message}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const durationMs = performance.now() - t0;
|
|
260
|
+
|
|
261
|
+
// 304 Not Modified — use cached body
|
|
262
|
+
if (response.status === 304) {
|
|
263
|
+
const cached = cache.get(url);
|
|
264
|
+
if (cached) {
|
|
265
|
+
cache.markHit(url);
|
|
266
|
+
return { body: cached.body, statusCode: 200, contentType: cached.contentType, fromCache: true, durationMs };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 429 / 503 — exponential backoff with jitter
|
|
271
|
+
if (response.status === 429 || response.status === 503) {
|
|
272
|
+
if (retryCount >= httpCfg.retryLimit) throw new Error(`[http-engine] rate-limited after ${httpCfg.retryLimit} retries: ${url}`);
|
|
273
|
+
const retryAfter = parseInt(response.headers.get('retry-after') ?? '0') || 0;
|
|
274
|
+
const baseDelay = retryAfter > 0 ? retryAfter * 1000 : Math.min(httpCfg.backoffCapMs, 1_000 * Math.pow(2, retryCount));
|
|
275
|
+
const jitter = Math.random() * 1000;
|
|
276
|
+
console.log(`[http-engine] ${response.status} on ${url} — backing off ${Math.round(baseDelay + jitter)}ms (retry ${retryCount + 1}/${httpCfg.retryLimit})`);
|
|
277
|
+
await new Promise(r => setTimeout(r, baseDelay + jitter));
|
|
278
|
+
return fetchWithBackoff(url, cache, opts, retryCount + 1, httpCfg);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 5xx — retry up to 3 times
|
|
282
|
+
if (response.status >= 500) {
|
|
283
|
+
if (retryCount < httpCfg.retry5xx) {
|
|
284
|
+
await new Promise(r => setTimeout(r, 2_000 * (retryCount + 1)));
|
|
285
|
+
return fetchWithBackoff(url, cache, opts, retryCount + 1, httpCfg);
|
|
286
|
+
}
|
|
287
|
+
if (cache.staleIfError(url)) {
|
|
288
|
+
const cached = cache.get(url)!;
|
|
289
|
+
return { body: cached.body, statusCode: 200, contentType: cached.contentType, fromCache: true, durationMs: performance.now() - t0 };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
294
|
+
const body = await response.text();
|
|
295
|
+
const etag = response.headers.get('etag') ?? undefined;
|
|
296
|
+
const lastModified = response.headers.get('last-modified') ?? undefined;
|
|
297
|
+
const cacheControl = response.headers.get('cache-control') ?? undefined;
|
|
298
|
+
|
|
299
|
+
// Cache successful HTML responses
|
|
300
|
+
if (response.ok && contentType.includes('html')) {
|
|
301
|
+
cache.set(url, { url, etag, lastModified, cacheControl, body, contentType, statusCode: response.status });
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return { body, statusCode: response.status, contentType, fromCache: false, durationMs, etag, lastModified };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export async function httpCrawl(opts: HttpCrawlOpts): Promise<HttpCrawlResult> {
|
|
308
|
+
const startTime = performance.now();
|
|
309
|
+
const httpCfg = await getHttpConfig();
|
|
310
|
+
const maxScreens = opts.maxScreens ?? 100;
|
|
311
|
+
const cacheDir = opts.cacheDir ?? '/tmp/zeta-cache';
|
|
312
|
+
const timeoutMs = opts.timeoutMs ?? httpCfg.timeoutMs;
|
|
313
|
+
const userAgent = opts.userAgent ?? 'ZeTa-Crawler/1.0 (+https://zeta.taodigitalsolutions.com/crawler)';
|
|
314
|
+
const cache = new ResponseCache(cacheDir);
|
|
315
|
+
const robots = opts.respectRobotsTxt !== false ? new RobotsCache() : null;
|
|
316
|
+
const urlFilterRe = opts.urlFilter ? (() => { try { return new RegExp(opts.urlFilter!, 'i'); } catch { return null; } })() : null;
|
|
317
|
+
|
|
318
|
+
const visited = new Set<string>();
|
|
319
|
+
const queue: string[] = [opts.appUrl];
|
|
320
|
+
const queued = new Set<string>([canonicalize(opts.appUrl)]);
|
|
321
|
+
const screens: HttpScreen[] = [];
|
|
322
|
+
let cachedCount = 0;
|
|
323
|
+
let errorCount = 0;
|
|
324
|
+
let rateLimitedCount = 0;
|
|
325
|
+
|
|
326
|
+
const batchFetch = async (urls: string[]): Promise<void> => {
|
|
327
|
+
await Promise.all(urls.map(async (url) => {
|
|
328
|
+
const canonUrl = canonicalize(url);
|
|
329
|
+
if (visited.has(canonUrl)) return;
|
|
330
|
+
visited.add(canonUrl);
|
|
331
|
+
|
|
332
|
+
// Robots.txt check
|
|
333
|
+
if (robots && !(await robots.isAllowed(url).catch(() => true))) {
|
|
334
|
+
console.log(`[http-engine] robots.txt blocked: ${url}`);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Skip content/legal pages
|
|
339
|
+
try {
|
|
340
|
+
const { pathname } = new URL(url);
|
|
341
|
+
if (SKIP_PATH.test(pathname)) return;
|
|
342
|
+
} catch { /* skip */ }
|
|
343
|
+
|
|
344
|
+
// URL filter
|
|
345
|
+
if (urlFilterRe) {
|
|
346
|
+
try {
|
|
347
|
+
const { pathname, search } = new URL(url);
|
|
348
|
+
if (!urlFilterRe.test(pathname + search)) {
|
|
349
|
+
console.log(`[http-engine] filtered out: ${url}`);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
} catch { /* skip */ }
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
let fetchResult: FetchResult;
|
|
356
|
+
try {
|
|
357
|
+
fetchResult = await fetchWithBackoff(url, cache, { userAgent, timeoutMs, extraHeaders: opts.headers }, 0, httpCfg);
|
|
358
|
+
if (fetchResult.statusCode === 429 || fetchResult.statusCode === 503) rateLimitedCount++;
|
|
359
|
+
} catch (err: any) {
|
|
360
|
+
console.error(`[http-engine] error fetching ${url}: ${err.message}`);
|
|
361
|
+
errorCount++;
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!fetchResult.contentType.includes('html')) return; // skip non-HTML
|
|
366
|
+
if (fetchResult.statusCode >= 400) return;
|
|
367
|
+
if (fetchResult.fromCache) cachedCount++;
|
|
368
|
+
|
|
369
|
+
const { markdown, title } = extractMarkdown(fetchResult.body, url);
|
|
370
|
+
const fullMeta = extractMetadata(fetchResult.body, url);
|
|
371
|
+
const links = extractLinks(fetchResult.body, url);
|
|
372
|
+
const tables = extractTables(fetchResult.body);
|
|
373
|
+
const images = extractImages(fetchResult.body, url);
|
|
374
|
+
|
|
375
|
+
const screen: HttpScreen = {
|
|
376
|
+
url,
|
|
377
|
+
title: title ?? fullMeta.title,
|
|
378
|
+
markdown,
|
|
379
|
+
html: opts.includeRawHtml ? fetchResult.body : undefined,
|
|
380
|
+
metadata: {
|
|
381
|
+
description: fullMeta.description,
|
|
382
|
+
ogTitle: fullMeta.ogTitle,
|
|
383
|
+
ogDescription: fullMeta.ogDescription,
|
|
384
|
+
ogImage: fullMeta.ogImage,
|
|
385
|
+
canonical: fullMeta.canonical,
|
|
386
|
+
schemaTypes: fullMeta.schemaTypes,
|
|
387
|
+
lang: fullMeta.lang,
|
|
388
|
+
},
|
|
389
|
+
tables,
|
|
390
|
+
links,
|
|
391
|
+
images: images.map(i => ({ src: i.src, alt: i.alt })),
|
|
392
|
+
statusCode: fetchResult.statusCode,
|
|
393
|
+
fromCache: fetchResult.fromCache,
|
|
394
|
+
requiresBrowser: renderingPredictor.getCached(url) === 'browser',
|
|
395
|
+
durationMs: fetchResult.durationMs,
|
|
396
|
+
};
|
|
397
|
+
screens.push(screen);
|
|
398
|
+
|
|
399
|
+
// Record rendering prediction: SPA signals → browser needed; content-rich → http engine works
|
|
400
|
+
renderingPredictor.predict(url, fetchResult.body, fetchResult.statusCode);
|
|
401
|
+
|
|
402
|
+
opts.onProgress?.({ url, screensFound: screens.length, total: maxScreens, cached: fetchResult.fromCache });
|
|
403
|
+
|
|
404
|
+
// Enqueue discovered links
|
|
405
|
+
for (const link of links) {
|
|
406
|
+
const c = canonicalize(link);
|
|
407
|
+
if (!queued.has(c) && !visited.has(c) && screens.length + queue.length < maxScreens * 2) {
|
|
408
|
+
queued.add(c);
|
|
409
|
+
if (opts.traversalStrategy === 'DFS') queue.unshift(link);
|
|
410
|
+
else queue.push(link);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}));
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const concurrency = opts.concurrency ?? httpCfg.concurrency;
|
|
417
|
+
|
|
418
|
+
while (queue.length > 0 && screens.length < maxScreens) {
|
|
419
|
+
const batch = queue.splice(0, concurrency);
|
|
420
|
+
await batchFetch(batch);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Output formats
|
|
424
|
+
let jsonLines: string | undefined;
|
|
425
|
+
let csv: string | undefined;
|
|
426
|
+
|
|
427
|
+
const formats = opts.outputFormats ?? [];
|
|
428
|
+
if (formats.includes('jsonlines')) {
|
|
429
|
+
jsonLines = screens.map(s => JSON.stringify({ url: s.url, title: s.title, markdown: s.markdown, metadata: s.metadata })).join('\n');
|
|
430
|
+
}
|
|
431
|
+
if (formats.includes('csv')) {
|
|
432
|
+
const header = 'url,title,description,ogTitle,schemaTypes,statusCode,fromCache,durationMs';
|
|
433
|
+
const rows = screens.map(s =>
|
|
434
|
+
[s.url, s.title ?? '', s.metadata.description ?? '', s.metadata.ogTitle ?? '', s.metadata.schemaTypes.join('|'), s.statusCode, s.fromCache, Math.round(s.durationMs)].map(v => `"${String(v).replace(/"/g, '""')}"`).join(',')
|
|
435
|
+
);
|
|
436
|
+
csv = [header, ...rows].join('\n');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const cacheStats = cache.stats();
|
|
440
|
+
return {
|
|
441
|
+
screens,
|
|
442
|
+
stats: {
|
|
443
|
+
total: screens.length,
|
|
444
|
+
cached: cachedCount,
|
|
445
|
+
errors: errorCount,
|
|
446
|
+
rateLimited: rateLimitedCount,
|
|
447
|
+
durationMs: performance.now() - startTime,
|
|
448
|
+
cacheHitRate: cacheStats.hitRate,
|
|
449
|
+
},
|
|
450
|
+
jsonLines,
|
|
451
|
+
csv,
|
|
452
|
+
};
|
|
453
|
+
}
|