@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,141 @@
|
|
|
1
|
+
import type { DomainProfile } from './profiles/domain-profile.js';
|
|
2
|
+
|
|
3
|
+
export type CrawlEngineName =
|
|
4
|
+
| 'http'
|
|
5
|
+
| 'browser'
|
|
6
|
+
| 'browser-stealth'
|
|
7
|
+
| 'stagehand'
|
|
8
|
+
| 'vision'
|
|
9
|
+
| 'recorded-session'
|
|
10
|
+
| 'native';
|
|
11
|
+
|
|
12
|
+
export interface CrawlRouteDecisionInput {
|
|
13
|
+
appUrl: string;
|
|
14
|
+
appType?: string | null;
|
|
15
|
+
hasCredentials: boolean;
|
|
16
|
+
hasStorageState: boolean;
|
|
17
|
+
hasAuthToken: boolean;
|
|
18
|
+
hasSkyvern: boolean;
|
|
19
|
+
hasProxy: boolean;
|
|
20
|
+
profile: DomainProfile;
|
|
21
|
+
previousFailureReason?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CrawlRouteDecision {
|
|
25
|
+
engines: CrawlEngineName[];
|
|
26
|
+
reasons: string[];
|
|
27
|
+
confidence: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Per-domain rendering predictor — learns whether a domain requires a full browser
|
|
32
|
+
* or can be served by the fast HTTP engine. Implements adaptive crawlee-python pattern.
|
|
33
|
+
*
|
|
34
|
+
* Strategy:
|
|
35
|
+
* 1. On first encounter of a domain, classify the HTTP response body.
|
|
36
|
+
* 2. Store the decision in memory (Map). All future URLs on same domain skip re-testing.
|
|
37
|
+
* 3. "Requires browser" signals: React/Vue/Angular root with no content, tiny body (<500 chars meaningful text).
|
|
38
|
+
*/
|
|
39
|
+
export class RenderingTypePredictor {
|
|
40
|
+
private cache = new Map<string, 'http' | 'browser'>();
|
|
41
|
+
|
|
42
|
+
/** Return cached decision if available, undefined if unknown. */
|
|
43
|
+
getCached(url: string): 'http' | 'browser' | undefined {
|
|
44
|
+
try {
|
|
45
|
+
return this.cache.get(new URL(url).hostname);
|
|
46
|
+
} catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Analyze an HTTP response body to predict if JS rendering is required.
|
|
53
|
+
* Returns 'browser' if JS-heavy signals detected, 'http' otherwise.
|
|
54
|
+
*/
|
|
55
|
+
predict(url: string, htmlBody: string, statusCode: number): 'http' | 'browser' {
|
|
56
|
+
let decision: 'http' | 'browser' = 'http';
|
|
57
|
+
try {
|
|
58
|
+
if (statusCode >= 400) {
|
|
59
|
+
decision = 'browser'; // let browser handle error pages
|
|
60
|
+
} else {
|
|
61
|
+
const bodyLen = htmlBody.length;
|
|
62
|
+
// Strip tags to get raw text
|
|
63
|
+
const textOnly = htmlBody.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
|
|
64
|
+
const wordCount = textOnly.split(/\s+/).filter(w => w.length > 2).length;
|
|
65
|
+
|
|
66
|
+
// SPA signals: very short text, presence of React/Vue/Angular root markers
|
|
67
|
+
const hasSpaRoot = /<div[^>]*\s+id=["'](?:root|app|__nuxt|__next|app-root)["'][^>]*>\s*<\/div>/i.test(htmlBody)
|
|
68
|
+
|| /<app-root[^>]*>\s*<\/app-root>/i.test(htmlBody);
|
|
69
|
+
const hasNoscriptFallback = /<noscript[^>]*>[\s\S]{100,}/i.test(htmlBody);
|
|
70
|
+
const tinyBody = bodyLen < 3000 && wordCount < 50;
|
|
71
|
+
|
|
72
|
+
if ((hasSpaRoot && wordCount < 100) || (tinyBody && !hasNoscriptFallback)) {
|
|
73
|
+
decision = 'browser';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch { /* default http */ }
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
this.cache.set(new URL(url).hostname, decision);
|
|
80
|
+
} catch { /* ignore */ }
|
|
81
|
+
return decision;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Force a domain to use a specific engine (e.g. after observing crawl errors). */
|
|
85
|
+
set(url: string, engine: 'http' | 'browser'): void {
|
|
86
|
+
try { this.cache.set(new URL(url).hostname, engine); } catch { /* ignore */ }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
stats(): { cached: number; browserDomains: string[]; httpDomains: string[] } {
|
|
90
|
+
const browserDomains: string[] = [];
|
|
91
|
+
const httpDomains: string[] = [];
|
|
92
|
+
for (const [domain, engine] of this.cache) {
|
|
93
|
+
if (engine === 'browser') browserDomains.push(domain);
|
|
94
|
+
else httpDomains.push(domain);
|
|
95
|
+
}
|
|
96
|
+
return { cached: this.cache.size, browserDomains, httpDomains };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Singleton predictor — shared across crawl jobs in the same process. */
|
|
101
|
+
export const renderingPredictor = new RenderingTypePredictor();
|
|
102
|
+
|
|
103
|
+
export function chooseCrawlEngines(input: CrawlRouteDecisionInput): CrawlRouteDecision {
|
|
104
|
+
const engines: CrawlEngineName[] = [];
|
|
105
|
+
const reasons: string[] = [];
|
|
106
|
+
|
|
107
|
+
const appType = input.appType?.toLowerCase();
|
|
108
|
+
if (appType === 'android' || appType === 'ios' || appType === 'windows' || appType === 'macos') {
|
|
109
|
+
return { engines: ['native'], reasons: [`appType=${input.appType}`], confidence: 0.95 };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (input.hasStorageState || input.hasAuthToken) {
|
|
113
|
+
engines.push('recorded-session');
|
|
114
|
+
reasons.push('session/token auth available');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
engines.push(input.hasProxy ? 'browser-stealth' : 'browser');
|
|
118
|
+
reasons.push(input.hasProxy ? 'proxy configured; prefer stealth browser path' : 'default browser path');
|
|
119
|
+
|
|
120
|
+
if (input.hasCredentials || input.profile.industry !== 'unknown') {
|
|
121
|
+
engines.push('stagehand');
|
|
122
|
+
reasons.push(input.hasCredentials ? 'credentials available for AI login/navigation' : `domain profile=${input.profile.industry}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const hardUi = input.profile.industry === 'legacy-enterprise' || /captcha|blocked|canvas|iframe|bot/i.test(input.previousFailureReason ?? '');
|
|
126
|
+
if (input.hasSkyvern && hardUi) {
|
|
127
|
+
engines.push('vision');
|
|
128
|
+
reasons.push('hard UI or previous block detected; vision fallback enabled');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (engines.length === 1) {
|
|
132
|
+
engines.unshift('http');
|
|
133
|
+
reasons.push('HTTP/static extraction can be attempted before browser escalation');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
engines: Array.from(new Set(engines)),
|
|
138
|
+
reasons,
|
|
139
|
+
confidence: input.profile.confidence,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bulk export — export crawled screens to CSV / JSON / Markdown digest.
|
|
3
|
+
*
|
|
4
|
+
* Outputs:
|
|
5
|
+
* - CSV: url, name, wordCount, screenshotPath, markdownPath, createdAt — for spreadsheet analysis
|
|
6
|
+
* - JSON: full screen records array — for API integration
|
|
7
|
+
* - Markdown digest: concatenated markdown of all screens — for LLM context windows
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as fs from 'fs/promises';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
|
|
13
|
+
export interface ExportScreen {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
url: string;
|
|
17
|
+
projectId: string;
|
|
18
|
+
wordCount?: number;
|
|
19
|
+
screenshotPath?: string | null;
|
|
20
|
+
markdownPath?: string | null;
|
|
21
|
+
markdown?: string | null;
|
|
22
|
+
createdAt?: Date;
|
|
23
|
+
updatedAt?: Date;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function escapeCsv(val: string): string {
|
|
27
|
+
if (val.includes(',') || val.includes('"') || val.includes('\n')) {
|
|
28
|
+
return `"${val.replace(/"/g, '""')}"`;
|
|
29
|
+
}
|
|
30
|
+
return val;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function exportToCsv(screens: ExportScreen[], outputPath: string): Promise<void> {
|
|
34
|
+
const headers = ['id', 'name', 'url', 'projectId', 'wordCount', 'screenshotPath', 'markdownPath', 'createdAt'];
|
|
35
|
+
const rows = screens.map(s => [
|
|
36
|
+
s.id, s.name, s.url, s.projectId,
|
|
37
|
+
String(s.wordCount ?? 0),
|
|
38
|
+
s.screenshotPath ?? '',
|
|
39
|
+
s.markdownPath ?? '',
|
|
40
|
+
s.createdAt?.toISOString() ?? '',
|
|
41
|
+
].map(escapeCsv).join(','));
|
|
42
|
+
|
|
43
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
44
|
+
await fs.writeFile(outputPath, [headers.join(','), ...rows].join('\n'), 'utf8');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function exportToJson(screens: ExportScreen[], outputPath: string): Promise<void> {
|
|
48
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
49
|
+
const data = screens.map(s => ({
|
|
50
|
+
id: s.id,
|
|
51
|
+
name: s.name,
|
|
52
|
+
url: s.url,
|
|
53
|
+
projectId: s.projectId,
|
|
54
|
+
wordCount: s.wordCount,
|
|
55
|
+
screenshotPath: s.screenshotPath,
|
|
56
|
+
markdownPath: s.markdownPath,
|
|
57
|
+
createdAt: s.createdAt?.toISOString(),
|
|
58
|
+
updatedAt: s.updatedAt?.toISOString(),
|
|
59
|
+
}));
|
|
60
|
+
await fs.writeFile(outputPath, JSON.stringify(data, null, 2), 'utf8');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function exportToMarkdownDigest(
|
|
64
|
+
screens: ExportScreen[],
|
|
65
|
+
outputPath: string,
|
|
66
|
+
opts: { readMarkdownFromPath?: boolean; maxScreens?: number } = {},
|
|
67
|
+
): Promise<void> {
|
|
68
|
+
const { readMarkdownFromPath = true, maxScreens = 200 } = opts;
|
|
69
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
70
|
+
|
|
71
|
+
const handle = await fs.open(outputPath, 'w');
|
|
72
|
+
try {
|
|
73
|
+
await handle.write(`# ZeTa Crawl Digest\n\nExported: ${new Date().toISOString()}\nScreens: ${Math.min(screens.length, maxScreens)}\n\n---\n\n`);
|
|
74
|
+
|
|
75
|
+
for (const screen of screens.slice(0, maxScreens)) {
|
|
76
|
+
let md = screen.markdown ?? '';
|
|
77
|
+
if (!md && readMarkdownFromPath && screen.markdownPath) {
|
|
78
|
+
md = await fs.readFile(screen.markdownPath, 'utf8').catch(() => '');
|
|
79
|
+
}
|
|
80
|
+
await handle.write(`## ${screen.name}\n\n**URL:** ${screen.url}\n\n${md.trim()}\n\n---\n\n`);
|
|
81
|
+
}
|
|
82
|
+
} finally {
|
|
83
|
+
await handle.close();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector DB export — convert crawled screens to embedding-ready chunks.
|
|
3
|
+
*
|
|
4
|
+
* Produces a JSONL file where each line is one chunk ready for upsert into
|
|
5
|
+
* Pinecone, Qdrant, Weaviate, or any vector store. The embedding itself is
|
|
6
|
+
* NOT generated here (requires an embedding model at the user's end) — we
|
|
7
|
+
* produce the text chunks + metadata, and the caller embeds them.
|
|
8
|
+
*
|
|
9
|
+
* Format per line (JSONL):
|
|
10
|
+
* {
|
|
11
|
+
* "id": "<screenId>-<chunkIndex>",
|
|
12
|
+
* "text": "<chunk text>",
|
|
13
|
+
* "metadata": {
|
|
14
|
+
* "screenId": "...",
|
|
15
|
+
* "projectId": "...",
|
|
16
|
+
* "tenantId": "...",
|
|
17
|
+
* "url": "...",
|
|
18
|
+
* "screenName": "...",
|
|
19
|
+
* "chunkIndex": 0,
|
|
20
|
+
* "totalChunks": 3,
|
|
21
|
+
* "crawledAt": "2026-...",
|
|
22
|
+
* "wordCount": 142
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import * as fs from 'fs/promises';
|
|
28
|
+
import * as path from 'path';
|
|
29
|
+
|
|
30
|
+
export interface VectorChunk {
|
|
31
|
+
id: string;
|
|
32
|
+
text: string;
|
|
33
|
+
metadata: {
|
|
34
|
+
screenId: string;
|
|
35
|
+
projectId: string;
|
|
36
|
+
tenantId: string;
|
|
37
|
+
url: string;
|
|
38
|
+
screenName: string;
|
|
39
|
+
chunkIndex: number;
|
|
40
|
+
totalChunks: number;
|
|
41
|
+
crawledAt: string;
|
|
42
|
+
wordCount: number;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ScreenRecord {
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
url: string;
|
|
50
|
+
projectId: string;
|
|
51
|
+
tenantId: string;
|
|
52
|
+
markdown?: string | null;
|
|
53
|
+
markdownPath?: string | null;
|
|
54
|
+
updatedAt?: Date;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function splitIntoChunks(text: string, maxChunkWords = 300): string[] {
|
|
58
|
+
const paragraphs = text.split(/\n{2,}/).filter(p => p.trim().length > 0);
|
|
59
|
+
const chunks: string[] = [];
|
|
60
|
+
let current = '';
|
|
61
|
+
let wordCount = 0;
|
|
62
|
+
|
|
63
|
+
for (const para of paragraphs) {
|
|
64
|
+
const words = para.split(/\s+/).length;
|
|
65
|
+
if (wordCount + words > maxChunkWords && current.length > 0) {
|
|
66
|
+
chunks.push(current.trim());
|
|
67
|
+
current = '';
|
|
68
|
+
wordCount = 0;
|
|
69
|
+
}
|
|
70
|
+
current += (current ? '\n\n' : '') + para;
|
|
71
|
+
wordCount += words;
|
|
72
|
+
}
|
|
73
|
+
if (current.trim()) chunks.push(current.trim());
|
|
74
|
+
return chunks.length > 0 ? chunks : [text.slice(0, 2000)];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function exportScreensToVectorJsonl(
|
|
78
|
+
screens: ScreenRecord[],
|
|
79
|
+
outputPath: string,
|
|
80
|
+
opts: { maxChunkWords?: number; readMarkdownFromPath?: boolean } = {},
|
|
81
|
+
): Promise<{ totalChunks: number; skipped: number }> {
|
|
82
|
+
const { maxChunkWords = 300, readMarkdownFromPath = true } = opts;
|
|
83
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
84
|
+
|
|
85
|
+
const handle = await fs.open(outputPath, 'w');
|
|
86
|
+
let totalChunks = 0;
|
|
87
|
+
let skipped = 0;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
for (const screen of screens) {
|
|
91
|
+
let markdown = screen.markdown ?? '';
|
|
92
|
+
|
|
93
|
+
if (!markdown && readMarkdownFromPath && screen.markdownPath) {
|
|
94
|
+
try {
|
|
95
|
+
markdown = await fs.readFile(screen.markdownPath, 'utf8');
|
|
96
|
+
} catch { /* file missing — skip */ }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!markdown || markdown.trim().length < 20) {
|
|
100
|
+
skipped++;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const textChunks = splitIntoChunks(markdown, maxChunkWords);
|
|
105
|
+
for (let i = 0; i < textChunks.length; i++) {
|
|
106
|
+
const chunk: VectorChunk = {
|
|
107
|
+
id: `${screen.id}-${i}`,
|
|
108
|
+
text: textChunks[i],
|
|
109
|
+
metadata: {
|
|
110
|
+
screenId: screen.id,
|
|
111
|
+
projectId: screen.projectId,
|
|
112
|
+
tenantId: screen.tenantId,
|
|
113
|
+
url: screen.url,
|
|
114
|
+
screenName: screen.name,
|
|
115
|
+
chunkIndex: i,
|
|
116
|
+
totalChunks: textChunks.length,
|
|
117
|
+
crawledAt: screen.updatedAt?.toISOString() ?? new Date().toISOString(),
|
|
118
|
+
wordCount: textChunks[i].split(/\s+/).length,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
await handle.write(JSON.stringify(chunk) + '\n');
|
|
122
|
+
totalChunks++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} finally {
|
|
126
|
+
await handle.close();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { totalChunks, skipped };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Pinecone-compatible upsert batch format.
|
|
134
|
+
* Call this after generating embeddings to produce the final upsert payload.
|
|
135
|
+
*/
|
|
136
|
+
export function toPineconeUpsert(
|
|
137
|
+
chunks: VectorChunk[],
|
|
138
|
+
embeddings: number[][],
|
|
139
|
+
namespace = 'zeta-crawl',
|
|
140
|
+
): Array<{ id: string; values: number[]; metadata: VectorChunk['metadata'] }> {
|
|
141
|
+
return chunks.map((chunk, i) => ({
|
|
142
|
+
id: chunk.id,
|
|
143
|
+
values: embeddings[i],
|
|
144
|
+
metadata: chunk.metadata,
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Qdrant-compatible point format.
|
|
150
|
+
*/
|
|
151
|
+
export function toQdrantPoints(
|
|
152
|
+
chunks: VectorChunk[],
|
|
153
|
+
embeddings: number[][],
|
|
154
|
+
): Array<{ id: string; vector: number[]; payload: VectorChunk['metadata'] & { text: string } }> {
|
|
155
|
+
return chunks.map((chunk, i) => ({
|
|
156
|
+
id: chunk.id,
|
|
157
|
+
vector: embeddings[i],
|
|
158
|
+
payload: { ...chunk.metadata, text: chunk.text },
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { parseHTML } from 'linkedom';
|
|
2
|
+
|
|
3
|
+
export interface ExtractionResult {
|
|
4
|
+
matches: string[];
|
|
5
|
+
count: number;
|
|
6
|
+
method: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type TableRow = Record<string, string>;
|
|
10
|
+
|
|
11
|
+
export interface PageMetadata {
|
|
12
|
+
title: string | null;
|
|
13
|
+
description: string | null;
|
|
14
|
+
keywords: string | null;
|
|
15
|
+
ogTitle: string | null;
|
|
16
|
+
ogDescription: string | null;
|
|
17
|
+
ogImage: string | null;
|
|
18
|
+
ogUrl: string | null;
|
|
19
|
+
ogType: string | null;
|
|
20
|
+
ogSiteName: string | null;
|
|
21
|
+
ogLocale: string | null;
|
|
22
|
+
twitterCard: string | null;
|
|
23
|
+
twitterTitle: string | null;
|
|
24
|
+
twitterDescription: string | null;
|
|
25
|
+
twitterImage: string | null;
|
|
26
|
+
twitterSite: string | null;
|
|
27
|
+
canonical: string | null;
|
|
28
|
+
robots: string | null;
|
|
29
|
+
hreflang: Array<{ lang: string; href: string }>;
|
|
30
|
+
themeColor: string | null;
|
|
31
|
+
favicon: string | null;
|
|
32
|
+
jsonLd: object[];
|
|
33
|
+
schemaTypes: string[];
|
|
34
|
+
lang: string | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function extractByCssSelector(html: string, selector: string, attr?: string): ExtractionResult {
|
|
38
|
+
try {
|
|
39
|
+
const { document } = parseHTML(html);
|
|
40
|
+
const nodes = Array.from(document.querySelectorAll(selector));
|
|
41
|
+
const matches = nodes.map((n: any) => {
|
|
42
|
+
if (attr) return (n as any).getAttribute(attr) ?? '';
|
|
43
|
+
return (n as any).textContent?.trim() ?? '';
|
|
44
|
+
}).filter(Boolean);
|
|
45
|
+
return { matches, count: matches.length, method: 'css-selector' };
|
|
46
|
+
} catch (e) {
|
|
47
|
+
return { matches: [], count: 0, method: 'css-selector-error' };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function extractByXPath(html: string, xpath: string): ExtractionResult {
|
|
52
|
+
try {
|
|
53
|
+
const { document, XPathResult } = parseHTML(html) as any;
|
|
54
|
+
const result = document.evaluate(xpath, document, null, XPathResult?.ANY_TYPE ?? 0, null);
|
|
55
|
+
const matches: string[] = [];
|
|
56
|
+
let node = result.iterateNext?.();
|
|
57
|
+
while (node) {
|
|
58
|
+
const text = (node as any).textContent?.trim() ?? (node as any).nodeValue?.trim() ?? '';
|
|
59
|
+
if (text) matches.push(text);
|
|
60
|
+
node = result.iterateNext?.();
|
|
61
|
+
}
|
|
62
|
+
return { matches, count: matches.length, method: 'xpath' };
|
|
63
|
+
} catch (e) {
|
|
64
|
+
return { matches: [], count: 0, method: 'xpath-error' };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function extractByRegex(text: string, pattern: string, flags?: string): ExtractionResult {
|
|
69
|
+
try {
|
|
70
|
+
const re = new RegExp(pattern, (flags ?? 'g').includes('g') ? flags : (flags ?? '') + 'g');
|
|
71
|
+
const matches: string[] = [];
|
|
72
|
+
let m: RegExpExecArray | null;
|
|
73
|
+
while ((m = re.exec(text)) !== null) {
|
|
74
|
+
matches.push(m[1] ?? m[0]);
|
|
75
|
+
if (!re.global) break;
|
|
76
|
+
}
|
|
77
|
+
return { matches, count: matches.length, method: 'regex' };
|
|
78
|
+
} catch {
|
|
79
|
+
return { matches: [], count: 0, method: 'regex-error' };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function extractTables(html: string): TableRow[][] {
|
|
84
|
+
try {
|
|
85
|
+
const { document } = parseHTML(html);
|
|
86
|
+
const tables = Array.from(document.querySelectorAll('table'));
|
|
87
|
+
return tables.map((table: any) => {
|
|
88
|
+
const headers: string[] = [];
|
|
89
|
+
const rows: TableRow[] = [];
|
|
90
|
+
const ths = [...table.querySelectorAll('thead th, thead td, tr:first-child th')];
|
|
91
|
+
if (ths.length > 0) {
|
|
92
|
+
ths.forEach((th: any, i: number) => headers.push(th.textContent?.trim() || `col${i + 1}`));
|
|
93
|
+
}
|
|
94
|
+
const trs = [...table.querySelectorAll('tbody tr, tr')];
|
|
95
|
+
for (const tr of trs) {
|
|
96
|
+
const cells = [...(tr as any).querySelectorAll('td, th')];
|
|
97
|
+
if (cells.length === 0) continue;
|
|
98
|
+
if (headers.length === 0) {
|
|
99
|
+
cells.forEach((_: any, i: number) => headers.push(`col${i + 1}`));
|
|
100
|
+
}
|
|
101
|
+
const row: TableRow = {};
|
|
102
|
+
cells.forEach((cell: any, i: number) => {
|
|
103
|
+
const key = headers[i] || `col${i + 1}`;
|
|
104
|
+
row[key] = cell.textContent?.trim() ?? '';
|
|
105
|
+
});
|
|
106
|
+
if (Object.values(row).some(v => v)) rows.push(row);
|
|
107
|
+
}
|
|
108
|
+
return rows;
|
|
109
|
+
}).filter(t => t.length > 0);
|
|
110
|
+
} catch {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function extractMetadata(html: string, url: string): PageMetadata {
|
|
116
|
+
const meta: PageMetadata = {
|
|
117
|
+
title: null, description: null, keywords: null,
|
|
118
|
+
ogTitle: null, ogDescription: null, ogImage: null, ogUrl: null, ogType: null,
|
|
119
|
+
ogSiteName: null, ogLocale: null,
|
|
120
|
+
twitterCard: null, twitterTitle: null, twitterDescription: null,
|
|
121
|
+
twitterImage: null, twitterSite: null,
|
|
122
|
+
canonical: null, robots: null,
|
|
123
|
+
hreflang: [], themeColor: null, favicon: null,
|
|
124
|
+
jsonLd: [], schemaTypes: [], lang: null,
|
|
125
|
+
};
|
|
126
|
+
try {
|
|
127
|
+
const { document } = parseHTML(html);
|
|
128
|
+
meta.title = (document as any).title ?? (document as any).querySelector('title')?.textContent?.trim() ?? null;
|
|
129
|
+
meta.lang = (document as any).documentElement?.getAttribute('lang') ?? null;
|
|
130
|
+
|
|
131
|
+
const metas = [...(document as any).querySelectorAll('meta')];
|
|
132
|
+
for (const m of metas) {
|
|
133
|
+
const name = (m as any).getAttribute('name')?.toLowerCase() ?? '';
|
|
134
|
+
const prop = (m as any).getAttribute('property')?.toLowerCase() ?? '';
|
|
135
|
+
const content = (m as any).getAttribute('content') ?? '';
|
|
136
|
+
if (name === 'description') meta.description = content;
|
|
137
|
+
if (name === 'keywords') meta.keywords = content;
|
|
138
|
+
if (name === 'robots') meta.robots = content;
|
|
139
|
+
if (name === 'twitter:card') meta.twitterCard = content;
|
|
140
|
+
if (name === 'twitter:title') meta.twitterTitle = content;
|
|
141
|
+
if (name === 'twitter:description') meta.twitterDescription = content;
|
|
142
|
+
if (name === 'twitter:image') meta.twitterImage = content;
|
|
143
|
+
if (name === 'twitter:site') meta.twitterSite = content;
|
|
144
|
+
if (name === 'theme-color') meta.themeColor = content;
|
|
145
|
+
if (prop === 'og:title') meta.ogTitle = content;
|
|
146
|
+
if (prop === 'og:description') meta.ogDescription = content;
|
|
147
|
+
if (prop === 'og:image') meta.ogImage = content;
|
|
148
|
+
if (prop === 'og:url') meta.ogUrl = content;
|
|
149
|
+
if (prop === 'og:type') meta.ogType = content;
|
|
150
|
+
if (prop === 'og:site_name') meta.ogSiteName = content;
|
|
151
|
+
if (prop === 'og:locale') meta.ogLocale = content;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const canonical = (document as any).querySelector('link[rel="canonical"]');
|
|
155
|
+
if (canonical) meta.canonical = canonical.getAttribute('href') ?? null;
|
|
156
|
+
|
|
157
|
+
const faviconEl = (document as any).querySelector('link[rel="icon"], link[rel="shortcut icon"]');
|
|
158
|
+
if (faviconEl) meta.favicon = faviconEl.getAttribute('href') ?? null;
|
|
159
|
+
|
|
160
|
+
const hreflangEls = [...(document as any).querySelectorAll('link[rel="alternate"][hreflang]')];
|
|
161
|
+
for (const el of hreflangEls) {
|
|
162
|
+
const lang = (el as any).getAttribute('hreflang') ?? '';
|
|
163
|
+
const href = (el as any).getAttribute('href') ?? '';
|
|
164
|
+
if (lang && href) meta.hreflang.push({ lang, href });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const jsonLdScripts = [...(document as any).querySelectorAll('script[type="application/ld+json"]')];
|
|
168
|
+
for (const s of jsonLdScripts) {
|
|
169
|
+
try {
|
|
170
|
+
const parsed = JSON.parse((s as any).textContent ?? '{}');
|
|
171
|
+
meta.jsonLd.push(parsed);
|
|
172
|
+
const types = Array.isArray(parsed) ? parsed.map((p: any) => p['@type']).flat() : [parsed['@type']];
|
|
173
|
+
meta.schemaTypes.push(...types.filter(Boolean));
|
|
174
|
+
} catch { /* skip invalid JSON-LD */ }
|
|
175
|
+
}
|
|
176
|
+
} catch { /* non-fatal */ }
|
|
177
|
+
return meta;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface ProductData {
|
|
181
|
+
name: string | null;
|
|
182
|
+
description: string | null;
|
|
183
|
+
brand: string | null;
|
|
184
|
+
sku: string | null;
|
|
185
|
+
price: number | null;
|
|
186
|
+
priceCurrency: string | null;
|
|
187
|
+
availability: string | null;
|
|
188
|
+
rating: number | null;
|
|
189
|
+
reviewCount: number | null;
|
|
190
|
+
image: string | null;
|
|
191
|
+
url: string | null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function extractStructuredProduct(jsonLd: object[]): ProductData | null {
|
|
195
|
+
for (const item of jsonLd) {
|
|
196
|
+
try {
|
|
197
|
+
const type = (item as any)['@type'];
|
|
198
|
+
const isProduct = Array.isArray(type)
|
|
199
|
+
? type.some((t: any) => typeof t === 'string' && t.toLowerCase() === 'product')
|
|
200
|
+
: typeof type === 'string' && type.toLowerCase() === 'product';
|
|
201
|
+
if (!isProduct) continue;
|
|
202
|
+
|
|
203
|
+
const i = item as any;
|
|
204
|
+
const offers = Array.isArray(i.offers) ? i.offers[0] : i.offers;
|
|
205
|
+
|
|
206
|
+
const rawPrice = offers?.price;
|
|
207
|
+
const price = rawPrice != null ? parseFloat(rawPrice) : NaN;
|
|
208
|
+
|
|
209
|
+
const rawRating = i.aggregateRating?.ratingValue;
|
|
210
|
+
const rating = rawRating != null ? parseFloat(rawRating) : NaN;
|
|
211
|
+
|
|
212
|
+
const rawReviewCount = i.aggregateRating?.reviewCount;
|
|
213
|
+
const reviewCount = rawReviewCount != null ? parseInt(rawReviewCount, 10) : NaN;
|
|
214
|
+
|
|
215
|
+
const rawAvailability: string | undefined = offers?.availability;
|
|
216
|
+
const availability = rawAvailability
|
|
217
|
+
? rawAvailability.replace(/^https?:\/\/schema\.org\//, '')
|
|
218
|
+
: null;
|
|
219
|
+
|
|
220
|
+
const rawImage = i.image;
|
|
221
|
+
const image = Array.isArray(rawImage) ? (rawImage[0] ?? null) : (rawImage ?? null);
|
|
222
|
+
|
|
223
|
+
const brand = i.brand?.name ?? i.brand ?? null;
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
name: i.name ?? null,
|
|
227
|
+
description: i.description ?? null,
|
|
228
|
+
brand: typeof brand === 'string' ? brand : null,
|
|
229
|
+
sku: i.sku ?? null,
|
|
230
|
+
price: isNaN(price) ? null : price,
|
|
231
|
+
priceCurrency: offers?.priceCurrency ?? null,
|
|
232
|
+
availability,
|
|
233
|
+
rating: isNaN(rating) ? null : rating,
|
|
234
|
+
reviewCount: isNaN(reviewCount) ? null : reviewCount,
|
|
235
|
+
image,
|
|
236
|
+
url: i.url ?? null,
|
|
237
|
+
};
|
|
238
|
+
} catch { /* skip malformed item */ }
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function extractImages(html: string, baseUrl: string): Array<{ src: string; alt: string; width?: number; height?: number }> {
|
|
244
|
+
try {
|
|
245
|
+
const { document } = parseHTML(html);
|
|
246
|
+
const base = new URL(baseUrl);
|
|
247
|
+
return Array.from((document as any).querySelectorAll('img'))
|
|
248
|
+
.map((img: any) => {
|
|
249
|
+
const src = img.getAttribute('src') ?? img.getAttribute('data-src') ?? '';
|
|
250
|
+
if (!src) return null;
|
|
251
|
+
try {
|
|
252
|
+
return {
|
|
253
|
+
src: new URL(src, base).href,
|
|
254
|
+
alt: (img.getAttribute('alt') ?? '') as string,
|
|
255
|
+
width: img.getAttribute('width') ? parseInt(img.getAttribute('width')) : undefined,
|
|
256
|
+
height: img.getAttribute('height') ? parseInt(img.getAttribute('height')) : undefined,
|
|
257
|
+
} as { src: string; alt: string; width?: number; height?: number };
|
|
258
|
+
} catch { return null; }
|
|
259
|
+
})
|
|
260
|
+
.filter((x): x is { src: string; alt: string; width?: number; height?: number } => x !== null);
|
|
261
|
+
} catch {
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function extractMediaFiles(html: string, baseUrl: string): Array<{ url: string; type: 'image' | 'video' | 'audio' | 'document'; tag: string }> {
|
|
267
|
+
const IMAGE_EXT = /\.(png|jpe?g|gif|webp|svg|ico|avif)(\?.*)?$/i;
|
|
268
|
+
const VIDEO_EXT = /\.(mp4|webm|ogv|mov|avi)(\?.*)?$/i;
|
|
269
|
+
const AUDIO_EXT = /\.(mp3|ogg|wav|flac|aac)(\?.*)?$/i;
|
|
270
|
+
const DOC_EXT = /\.(pdf|docx?|xlsx?|pptx?|csv|zip)(\?.*)?$/i;
|
|
271
|
+
|
|
272
|
+
function classify(url: string, tag: string): 'image' | 'video' | 'audio' | 'document' {
|
|
273
|
+
if (tag === 'img' || IMAGE_EXT.test(url)) return 'image';
|
|
274
|
+
if (tag === 'video' || VIDEO_EXT.test(url)) return 'video';
|
|
275
|
+
if (tag === 'audio' || AUDIO_EXT.test(url)) return 'audio';
|
|
276
|
+
if (DOC_EXT.test(url)) return 'document';
|
|
277
|
+
return 'document';
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
const { document } = parseHTML(html);
|
|
282
|
+
const base = new URL(baseUrl);
|
|
283
|
+
const results: Array<{ url: string; type: 'image' | 'video' | 'audio' | 'document'; tag: string }> = [];
|
|
284
|
+
const seen = new Set<string>();
|
|
285
|
+
|
|
286
|
+
const selectors: [string, string] = ['img[src],video[src],audio[src],source[src]', 'src'];
|
|
287
|
+
const attrPairs: Array<[string, string]> = [
|
|
288
|
+
['img', 'src'], ['video', 'src'], ['audio', 'src'], ['source', 'src'],
|
|
289
|
+
['a', 'href'],
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
for (const [sel, attr] of attrPairs) {
|
|
293
|
+
for (const el of [...(document as any).querySelectorAll(sel)]) {
|
|
294
|
+
const raw = (el as any).getAttribute(attr) ?? '';
|
|
295
|
+
if (!raw) continue;
|
|
296
|
+
try {
|
|
297
|
+
const resolved = new URL(raw, base).href;
|
|
298
|
+
if (seen.has(resolved)) continue;
|
|
299
|
+
if (sel === 'a' && !DOC_EXT.test(resolved) && !IMAGE_EXT.test(resolved) && !VIDEO_EXT.test(resolved) && !AUDIO_EXT.test(resolved)) continue;
|
|
300
|
+
seen.add(resolved);
|
|
301
|
+
results.push({ url: resolved, type: classify(resolved, sel), tag: sel });
|
|
302
|
+
} catch { /* skip */ }
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return results;
|
|
306
|
+
} catch {
|
|
307
|
+
return [];
|
|
308
|
+
}
|
|
309
|
+
}
|