graph-crawler 5.3.7__tar.gz → 5.4.2__tar.gz
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.
- graph_crawler-5.4.2/CHANGELOG.md +652 -0
- graph_crawler-5.4.2/LICENSE +21 -0
- graph_crawler-5.4.2/MANIFEST.in +16 -0
- graph_crawler-5.4.2/PKG-INFO +523 -0
- graph_crawler-5.4.2/README.md +401 -0
- graph_crawler-5.4.2/graph_crawler/__init__.py +495 -0
- graph_crawler-5.4.2/graph_crawler/__version__.py +9 -0
- graph_crawler-5.4.2/graph_crawler/ai/__init__.py +50 -0
- graph_crawler-5.4.2/graph_crawler/api/__init__.py +28 -0
- graph_crawler-5.4.2/graph_crawler/api/_core.py +283 -0
- graph_crawler-5.4.2/graph_crawler/api/_distributed.py +172 -0
- graph_crawler-5.4.2/graph_crawler/api/_shared.py +110 -0
- graph_crawler-5.4.2/graph_crawler/api/_sitemap_distributed.py +237 -0
- graph_crawler-5.4.2/graph_crawler/api/async_.py +329 -0
- graph_crawler-5.4.2/graph_crawler/api/cli.py +440 -0
- graph_crawler-5.4.2/graph_crawler/api/client/__init__.py +8 -0
- graph_crawler-5.4.2/graph_crawler/api/client/client.py +479 -0
- graph_crawler-5.4.2/graph_crawler/api/history_manager.py +55 -0
- graph_crawler-5.4.2/graph_crawler/api/project_init.py +196 -0
- graph_crawler-5.4.2/graph_crawler/api/server/__init__.py +18 -0
- graph_crawler-5.4.2/graph_crawler/api/server/crawl_monitor.py +145 -0
- graph_crawler-5.4.2/graph_crawler/api/server/dashboard.py +219 -0
- graph_crawler-5.4.2/graph_crawler/api/server/rest_api.py +704 -0
- graph_crawler-5.4.2/graph_crawler/api/server/webhooks.py +434 -0
- graph_crawler-5.4.2/graph_crawler/api/server/websocket_manager.py +89 -0
- graph_crawler-5.4.2/graph_crawler/api/stats_collector.py +88 -0
- graph_crawler-5.4.2/graph_crawler/api/sync.py +550 -0
- graph_crawler-5.4.2/graph_crawler/application/__init__.py +38 -0
- graph_crawler-5.4.2/graph_crawler/application/bootstrap.py +215 -0
- graph_crawler-5.4.2/graph_crawler/application/context/__init__.py +31 -0
- graph_crawler-5.4.2/graph_crawler/application/context/dependency_registry.py +354 -0
- graph_crawler-5.4.2/graph_crawler/application/context/graph_context.py +217 -0
- graph_crawler-5.4.2/graph_crawler/application/context/merge_context.py +221 -0
- graph_crawler-5.4.2/graph_crawler/application/dto/__init__.py +67 -0
- graph_crawler-5.4.2/graph_crawler/application/dto/edge_dto.py +25 -0
- graph_crawler-5.4.2/graph_crawler/application/dto/graph_dto.py +27 -0
- graph_crawler-5.4.2/graph_crawler/application/dto/mappers/__init__.py +14 -0
- graph_crawler-5.4.2/graph_crawler/application/dto/mappers/edge_mapper.py +149 -0
- graph_crawler-5.4.2/graph_crawler/application/dto/mappers/graph_mapper.py +606 -0
- graph_crawler-5.4.2/graph_crawler/application/dto/mappers/node_data_codec.py +190 -0
- graph_crawler-5.4.2/graph_crawler/application/dto/mappers/node_mapper.py +421 -0
- graph_crawler-5.4.2/graph_crawler/application/dto/node_dto.py +27 -0
- graph_crawler-5.4.2/graph_crawler/application/dto/utils.py +272 -0
- graph_crawler-5.4.2/graph_crawler/application/services/__init__.py +60 -0
- graph_crawler-5.4.2/graph_crawler/application/services/application_container.py +199 -0
- graph_crawler-5.4.2/graph_crawler/application/services/driver_factory.py +147 -0
- graph_crawler-5.4.2/graph_crawler/application/services/exporters/__init__.py +26 -0
- graph_crawler-5.4.2/graph_crawler/application/services/exporters/base_exporter.py +150 -0
- graph_crawler-5.4.2/graph_crawler/application/services/exporters/csv_exporter.py +318 -0
- graph_crawler-5.4.2/graph_crawler/application/services/exporters/edge_exporter.py +432 -0
- graph_crawler-5.4.2/graph_crawler/application/services/exporters/excel_exporter.py +297 -0
- graph_crawler-5.4.2/graph_crawler/application/services/exporters/jsonl_exporter.py +291 -0
- graph_crawler-5.4.2/graph_crawler/application/services/exporters/node_exporter.py +284 -0
- graph_crawler-5.4.2/graph_crawler/application/services/exporters/parquet_exporter.py +248 -0
- graph_crawler-5.4.2/graph_crawler/application/services/exporters/sql_exporter.py +267 -0
- graph_crawler-5.4.2/graph_crawler/application/services/finalize.py +115 -0
- graph_crawler-5.4.2/graph_crawler/application/services/low_memory_graph_saver.py +341 -0
- graph_crawler-5.4.2/graph_crawler/application/services/resource_budget.py +179 -0
- graph_crawler-5.4.2/graph_crawler/application/services/scheduling/__init__.py +12 -0
- graph_crawler-5.4.2/graph_crawler/application/services/scheduling/rule_based_scheduler.py +105 -0
- graph_crawler-5.4.2/graph_crawler/application/services/storage_factory.py +195 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/__init__.py +11 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/__init__.py +81 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/adaptive_throttler.py +369 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/async_bridge.py +45 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/base_spider.py +155 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/celery_batch_spider.py +645 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/celery_spider.py +446 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/checkpoint.py +426 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/crawl_coordinator.py +1132 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/dead_letter_queue.py +413 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/domain_rate_limiter.py +262 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/filters/__init__.py +10 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/filters/base.py +74 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/filters/domain_filter.py +228 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/filters/domain_patterns.py +30 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/filters/path_filter.py +95 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/incremental_strategy.py +239 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/link_processor.py +884 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/multiprocess_spider.py +505 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/node_scanner.py +873 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/parsers/__init__.py +6 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/parsers/base.py +73 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/parsers/html_parser.py +154 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/progress_tracker.py +235 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/scheduler.py +760 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/serialization_mixin.py +218 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/sitemap_parser.py +1092 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/sitemap_processor.py +405 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/sitemap_spider.py +665 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/spider.py +873 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/spider_lifecycle.py +108 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/crawling/worker_pool.py +158 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/graph_export.py +504 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/graph_merge/__init__.py +33 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/graph_merge/graph_operations.py +628 -0
- graph_crawler-5.4.2/graph_crawler/application/use_cases/graph_merge/merge_strategies.py +347 -0
- graph_crawler-5.4.2/graph_crawler/data/__init__.py +28 -0
- graph_crawler-5.4.2/graph_crawler/data/backends/__init__.py +21 -0
- graph_crawler-5.4.2/graph_crawler/data/backends/memory.py +486 -0
- graph_crawler-5.4.2/graph_crawler/data/backends/sqlite.py +1900 -0
- graph_crawler-5.4.2/graph_crawler/data/interfaces.py +563 -0
- graph_crawler-5.4.2/graph_crawler/domain/__init__.py +62 -0
- graph_crawler-5.4.2/graph_crawler/domain/context/__init__.py +14 -0
- graph_crawler-5.4.2/graph_crawler/domain/context/crawl_context.py +531 -0
- graph_crawler-5.4.2/graph_crawler/domain/entities/__init__.py +32 -0
- graph_crawler-5.4.2/graph_crawler/domain/entities/edge.py +345 -0
- graph_crawler-5.4.2/graph_crawler/domain/entities/edge_analysis.py +223 -0
- graph_crawler-5.4.2/graph_crawler/domain/entities/graph.py +4276 -0
- graph_crawler-5.4.2/graph_crawler/domain/entities/graph_operations.py +30 -0
- graph_crawler-5.4.2/graph_crawler/domain/entities/graph_statistics.py +10 -0
- graph_crawler-5.4.2/graph_crawler/domain/entities/merge_strategies.py +33 -0
- graph_crawler-5.4.2/graph_crawler/domain/entities/node.py +966 -0
- graph_crawler-5.4.2/graph_crawler/domain/entities/redirect_mode.py +23 -0
- graph_crawler-5.4.2/graph_crawler/domain/entities/registries.py +223 -0
- graph_crawler-5.4.2/graph_crawler/domain/entities/sitemap_node.py +77 -0
- graph_crawler-5.4.2/graph_crawler/domain/entities/strategies.py +202 -0
- graph_crawler-5.4.2/graph_crawler/domain/events/__init__.py +153 -0
- graph_crawler-5.4.2/graph_crawler/domain/events/base.py +60 -0
- graph_crawler-5.4.2/graph_crawler/domain/events/crawl.py +112 -0
- graph_crawler-5.4.2/graph_crawler/domain/events/edge.py +43 -0
- graph_crawler-5.4.2/graph_crawler/domain/events/event_bus.py +250 -0
- graph_crawler-5.4.2/graph_crawler/domain/events/events.py +313 -0
- graph_crawler-5.4.2/graph_crawler/domain/events/fetch.py +120 -0
- graph_crawler-5.4.2/graph_crawler/domain/events/idempotent_subscriber.py +120 -0
- graph_crawler-5.4.2/graph_crawler/domain/events/legacy_type_map.py +45 -0
- graph_crawler-5.4.2/graph_crawler/domain/events/llm.py +149 -0
- graph_crawler-5.4.2/graph_crawler/domain/events/node.py +111 -0
- graph_crawler-5.4.2/graph_crawler/domain/events/plugin.py +92 -0
- graph_crawler-5.4.2/graph_crawler/domain/events/scheduler.py +98 -0
- graph_crawler-5.4.2/graph_crawler/domain/events/storage.py +161 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/__init__.py +113 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/adapter.py +29 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/control_channel.py +381 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/crawl_context.py +49 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/distributed_spider.py +62 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/driver.py +86 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/event_bus.py +61 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/exporters.py +69 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/filter.py +19 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/merge_context.py +52 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/node_interfaces.py +388 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/parser.py +88 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/plugin_manager.py +53 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/processor.py +11 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/scanner.py +57 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/scheduler.py +43 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/scheduling/__init__.py +40 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/scheduling/frontier.py +97 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/scheduling/scheduler.py +107 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/spider.py +63 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/stop_condition.py +211 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/storage/__init__.py +43 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/storage/base.py +66 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/storage/eviction.py +98 -0
- graph_crawler-5.4.2/graph_crawler/domain/interfaces/storage/queue.py +234 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/__init__.py +216 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/adjacency_maintenance.py +95 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/cycle_detection.py +92 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/degree_ranking.py +47 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/depth_context.py +123 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/display_title.py +47 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/duplicate_detection.py +141 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/duplicate_selector.py +127 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/duplicate_stats.py +63 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/edge_filter.py +82 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/edge_statistics.py +110 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/eviction_policy.py +83 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/graph_query_cascade.py +178 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/graph_statistics.py +186 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/graph_traversal.py +150 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/language_versions.py +73 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/lsh_index.py +99 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/minimap_builder.py +147 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/node_content_resolver.py +128 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/node_view_builder.py +163 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/pagination_guards.py +136 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/predicate_ops.py +77 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/schema_filter.py +138 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/search_haystack.py +102 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/simhash_clustering.py +96 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/simhash_fallback.py +59 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/similarity_ranking.py +62 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/text_search.py +157 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/url_clustering.py +88 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/url_deduplication.py +101 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/url_normalizer.py +117 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/url_rule_matcher.py +111 -0
- graph_crawler-5.4.2/graph_crawler/domain/services/url_tree_builder.py +197 -0
- graph_crawler-5.4.2/graph_crawler/domain/value_objects/__init__.py +88 -0
- graph_crawler-5.4.2/graph_crawler/domain/value_objects/configs.py +1079 -0
- graph_crawler-5.4.2/graph_crawler/domain/value_objects/domain_patterns.py +71 -0
- graph_crawler-5.4.2/graph_crawler/domain/value_objects/fetch_options.py +57 -0
- graph_crawler-5.4.2/graph_crawler/domain/value_objects/html_document.py +59 -0
- graph_crawler-5.4.2/graph_crawler/domain/value_objects/lifecycle.py +30 -0
- graph_crawler-5.4.2/graph_crawler/domain/value_objects/models.py +1315 -0
- graph_crawler-5.4.2/graph_crawler/domain/value_objects/page_actions.py +76 -0
- graph_crawler-5.4.2/graph_crawler/domain/value_objects/redirect_hop.py +49 -0
- graph_crawler-5.4.2/graph_crawler/domain/value_objects/scheduling/__init__.py +23 -0
- graph_crawler-5.4.2/graph_crawler/domain/value_objects/scheduling/crawl_request.py +153 -0
- graph_crawler-5.4.2/graph_crawler/domain/value_objects/scheduling/retry_policy.py +109 -0
- graph_crawler-5.4.2/graph_crawler/domain/value_objects/settings.py +350 -0
- graph_crawler-5.4.2/graph_crawler/domain/value_objects/url_normalization.py +497 -0
- graph_crawler-5.4.2/graph_crawler/domain/value_objects/views.py +293 -0
- graph_crawler-5.4.2/graph_crawler/extensions/__init__.py +1 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/__init__.py +65 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/base.py +138 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/cache_middleware.py +279 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/chain.py +158 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/error_recovery_middleware.py +442 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/logging_middleware.py +108 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/proxy_health.py +137 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/proxy_middleware.py +209 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/proxy_models.py +80 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/proxy_selection.py +87 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/rate_limit_middleware.py +262 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/request_response_middleware.py +478 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/retry_middleware.py +279 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/robots_cache.py +85 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/robots_middleware.py +110 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/robots_validator.py +60 -0
- graph_crawler-5.4.2/graph_crawler/extensions/middleware/user_agent_middleware.py +251 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/__init__.py +57 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/builtin/__init__.py +7 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/builtin/stats_export_plugin.py +99 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/crawl_engine/__init__.py +24 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/crawl_engine/base.py +155 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/crawl_engine/priority_provider.py +249 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/crawl_engine/smart_crawl.py +295 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/engine/__init__.py +128 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/engine/anti_bot_detection.py +55 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/engine/anti_bot_playwright.py +88 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/engine/anti_bot_scripts.py +132 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/engine/anti_bot_stealth.py +177 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/engine/base.py +121 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/engine/captcha/__init__.py +22 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/engine/captcha/detector.py +94 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/engine/captcha/models.py +74 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/engine/captcha/plugin.py +230 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/engine/captcha/services.py +389 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/__init__.py +86 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/base.py +525 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/content_extractors/__init__.py +15 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/content_extractors/base.py +82 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/content_extractors/goose3_extractor.py +81 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/content_extractors/newspaper_extractor.py +92 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/content_extractors/plugin.py +135 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/content_extractors/readability_extractor.py +92 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/defaults.py +32 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/extractors/__init__.py +30 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/extractors/email_extractor.py +148 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/extractors/phone_extractor.py +136 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/extractors/price_extractor.py +137 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/links.py +184 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/markdown_plugin.py +250 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/metadata.py +377 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/smart_page_finder.py +752 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/structured_data/__init__.py +51 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/structured_data/constants.py +34 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/structured_data/exceptions.py +18 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/structured_data/extractor.py +375 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/structured_data/options.py +83 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/structured_data/parsers/__init__.py +25 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/structured_data/parsers/base.py +43 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/structured_data/parsers/jsonld.py +404 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/structured_data/parsers/microdata.py +115 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/structured_data/parsers/opengraph.py +65 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/structured_data/parsers/rdfa.py +120 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/structured_data/parsers/twitter.py +65 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/structured_data/plugin.py +132 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/structured_data/result.py +233 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/text.py +77 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/node/url_normalizer.py +363 -0
- graph_crawler-5.4.2/graph_crawler/extensions/plugins/registry.py +97 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/__init__.py +1 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/adapters/__init__.py +60 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/adapters/base.py +498 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/adapters/selectolax_adapter.py +233 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/messaging/__init__.py +37 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/messaging/celery_app.py +202 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/messaging/celery_batch.py +284 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/messaging/celery_job_task.py +414 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/messaging/celery_unified.py +419 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/messaging/config.py +201 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/messaging/easy_crawler.py +279 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/messaging/worker_api.py +609 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/__init__.py +135 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/auto_storage.py +336 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/base.py +111 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/eviction.py +100 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/graph_repository.py +398 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/json/__init__.py +10 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/json_storage.py +373 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/lmdb_eviction_storage.py +416 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/memory/__init__.py +1 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/memory_dedup_store.py +90 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/memory_frontier.py +180 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/memory_politeness_store.py +88 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/memory_storage.py +166 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/mongodb_storage.py +382 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/naming_strategy.py +174 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/postgresql_storage.py +450 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/protocols/__init__.py +1 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/redis_dedup_store.py +119 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/redis_politeness_store.py +122 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/repository.py +130 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/sqlite/__init__.py +10 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/sqlite_eviction_storage.py +905 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/sqlite_storage.py +767 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/unified/__init__.py +48 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/unified/file_job_storage.py +245 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/unified/file_queue_storage.py +247 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/unified/memory_job_storage.py +94 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/unified/memory_queue_storage.py +117 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/unified/postgresql_job_storage.py +317 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/unified/postgresql_queue_storage.py +366 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/persistence/unified/unified_storage.py +244 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/__init__.py +135 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/async_http/__init__.py +38 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/async_http/config.py +57 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/async_http/context.py +55 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/async_http/driver.py +792 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/async_http/driver_curl_cffi.py +437 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/async_http/plugins/__init__.py +48 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle.py +484 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle_adaptive.py +1430 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/async_http/plugins/headers.py +69 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/async_http/plugins/http_cache.py +321 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/async_http/plugins/rate_limiter.py +96 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/async_http/plugins/retry.py +315 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/async_http/stages.py +33 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/base.py +344 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/base_plugin.py +151 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/compression/__init__.py +23 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/compression/strategy.py +220 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/connection_pool.py +373 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/context.py +114 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/core/__init__.py +15 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/core/base_async.py +370 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/core/body_decoder.py +28 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/core/body_limit.py +223 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/core/mixins.py +258 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/core/redirect_utils.py +169 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/escalation.py +1177 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/factory.py +211 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/__init__.py +19 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/adaptive_semaphore.py +65 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/batch_scheduler.py +772 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/browser_factory.py +603 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/browser_profile.py +716 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/config.py +93 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/context.py +46 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/driver.py +78 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/engine.py +480 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/fetch_metrics.py +115 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/net/__init__.py +41 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/net/geoip.py +136 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/net/proxy_pool.py +223 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/net/rate_limiter.py +135 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/page_actions.py +217 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/page_fetch.py +1086 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/plugins/__init__.py +71 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/plugins/_network_record.py +66 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/plugins/captcha_detector.py +297 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/plugins/captcha_solver.py +107 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/plugins/cookie_consent.py +547 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/plugins/form_filler.py +179 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/plugins/human_bezier.py +290 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/plugins/network_log.py +418 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/plugins/ram_adaptive.py +208 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/plugins/screenshot.py +171 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/plugins/user_profile.py +546 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/pool_lifecycle.py +659 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/pooled_driver.py +702 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/stages.py +35 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/playwright/stealth_hints.py +154 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/plugin_manager.py +317 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/protocols.py +132 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/undetected/__init__.py +8 -0
- graph_crawler-5.4.2/graph_crawler/infrastructure/transport/undetected/async_driver.py +479 -0
- graph_crawler-5.4.2/graph_crawler/native/__init__.py +199 -0
- graph_crawler-5.4.2/graph_crawler/native/bloom_filter.pyx +255 -0
- graph_crawler-5.4.2/graph_crawler/native/html_parser.pyx +262 -0
- graph_crawler-5.4.2/graph_crawler/native/setup.py +103 -0
- graph_crawler-5.4.2/graph_crawler/native/url_utils.pyx +209 -0
- graph_crawler-5.4.2/graph_crawler/observability/__init__.py +62 -0
- graph_crawler-5.4.2/graph_crawler/observability/error_tracing.py +361 -0
- graph_crawler-5.4.2/graph_crawler/observability/extensions/__init__.py +18 -0
- graph_crawler-5.4.2/graph_crawler/observability/extensions/memory_guard.py +237 -0
- graph_crawler-5.4.2/graph_crawler/observability/extensions/state_manager.py +319 -0
- graph_crawler-5.4.2/graph_crawler/observability/listeners/__init__.py +33 -0
- graph_crawler-5.4.2/graph_crawler/observability/listeners/base.py +82 -0
- graph_crawler-5.4.2/graph_crawler/observability/listeners/base_metrics_listener.py +18 -0
- graph_crawler-5.4.2/graph_crawler/observability/listeners/crawl_listener.py +28 -0
- graph_crawler-5.4.2/graph_crawler/observability/listeners/dlq_listener.py +215 -0
- graph_crawler-5.4.2/graph_crawler/observability/listeners/error_listener.py +18 -0
- graph_crawler-5.4.2/graph_crawler/observability/listeners/logging_listener.py +88 -0
- graph_crawler-5.4.2/graph_crawler/observability/listeners/metrics_listener.py +109 -0
- graph_crawler-5.4.2/graph_crawler/observability/listeners/node_listener.py +33 -0
- graph_crawler-5.4.2/graph_crawler/observability/listeners/plugin_listener.py +28 -0
- graph_crawler-5.4.2/graph_crawler/observability/listeners/storage_listener.py +21 -0
- graph_crawler-5.4.2/graph_crawler/observability/listeners/url_listener.py +29 -0
- graph_crawler-5.4.2/graph_crawler/observability/metrics/__init__.py +23 -0
- graph_crawler-5.4.2/graph_crawler/observability/metrics/memory_profiler.py +510 -0
- graph_crawler-5.4.2/graph_crawler/observability/metrics/metrics_collector.py +408 -0
- graph_crawler-5.4.2/graph_crawler/observability/metrics/prometheus_metrics.py +293 -0
- graph_crawler-5.4.2/graph_crawler/observability/metrics_core.py +389 -0
- graph_crawler-5.4.2/graph_crawler/observability/structured_logging.py +216 -0
- graph_crawler-5.4.2/graph_crawler/observability/tracing.py +295 -0
- graph_crawler-5.4.2/graph_crawler/optimizations/__init__.py +89 -0
- graph_crawler-5.4.2/graph_crawler/optimizations/generators.py +477 -0
- graph_crawler-5.4.2/graph_crawler/optimizations/memory_slots.py +641 -0
- graph_crawler-5.4.2/graph_crawler/optimizations/simhash_numba.py +284 -0
- graph_crawler-5.4.2/graph_crawler/shared/__init__.py +25 -0
- graph_crawler-5.4.2/graph_crawler/shared/constants.py +952 -0
- graph_crawler-5.4.2/graph_crawler/shared/dto/__init__.py +38 -0
- graph_crawler-5.4.2/graph_crawler/shared/dto/edge_dto.py +52 -0
- graph_crawler-5.4.2/graph_crawler/shared/dto/graph_dto.py +97 -0
- graph_crawler-5.4.2/graph_crawler/shared/dto/node_dto.py +193 -0
- graph_crawler-5.4.2/graph_crawler/shared/error_handling/__init__.py +15 -0
- graph_crawler-5.4.2/graph_crawler/shared/error_handling/error_handler.py +333 -0
- graph_crawler-5.4.2/graph_crawler/shared/exceptions.py +394 -0
- graph_crawler-5.4.2/graph_crawler/shared/platform/__init__.py +125 -0
- graph_crawler-5.4.2/graph_crawler/shared/platform/adaptive_resolver.py +418 -0
- graph_crawler-5.4.2/graph_crawler/shared/platform/config.py +617 -0
- graph_crawler-5.4.2/graph_crawler/shared/platform/detector.py +326 -0
- graph_crawler-5.4.2/graph_crawler/shared/platform/diagnostics.py +650 -0
- graph_crawler-5.4.2/graph_crawler/shared/platform/metrics.py +291 -0
- graph_crawler-5.4.2/graph_crawler/shared/security/__init__.py +22 -0
- graph_crawler-5.4.2/graph_crawler/shared/security/url_sanitizer.py +100 -0
- graph_crawler-5.4.2/graph_crawler/shared/security/url_validator.py +299 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/__init__.py +213 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/async_compat.py +141 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/best_effort.py +48 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/bloom_filter.py +373 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/captcha/__init__.py +36 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/captcha/base.py +92 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/captcha/manager.py +464 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/celery_config.py +308 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/celery_helpers.py +155 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/cgroup_limits.py +223 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/content_decoder.py +150 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/distributed_rate_limiter.py +265 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/distributed_rate_limiter_backends.py +286 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/dns_cache.py +368 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/enterprise_ua_generator.py +294 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/event_publisher_mixin.py +190 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/fast_json.py +240 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/fingerprint.py +36 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/fingerprint_data.py +99 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/fingerprint_generators.py +128 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/fingerprint_profile.py +121 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/fingerprint_stealth.py +105 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/html_utils.py +118 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/id_generator.py +195 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/isolated_executor.py +200 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/memory_optimizer.py +536 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/rate_limiter.py +185 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/regex_validator.py +966 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/shadow_dom.py +40 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/text_utils.py +387 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/url_patterns.py +135 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/url_utils.py +595 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/user_agent_rotator.py +333 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/validation_helpers.py +225 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/visualization.py +134 -0
- graph_crawler-5.4.2/graph_crawler/shared/utils/visualization_core.py +558 -0
- graph_crawler-5.4.2/graph_crawler/tools/__init__.py +1 -0
- graph_crawler-5.4.2/graph_crawler/tools/db_cleanup.py +237 -0
- graph_crawler-5.4.2/graph_crawler.egg-info/PKG-INFO +523 -0
- graph_crawler-5.4.2/graph_crawler.egg-info/SOURCES.txt +476 -0
- graph_crawler-5.4.2/graph_crawler.egg-info/requires.txt +126 -0
- graph_crawler-5.4.2/pyproject.toml +302 -0
- graph_crawler-5.4.2/setup.cfg +4 -0
- graph_crawler-5.3.7/CHANGELOG.md +0 -546
- graph_crawler-5.3.7/LICENSE +0 -21
- graph_crawler-5.3.7/MANIFEST.in +0 -16
- graph_crawler-5.3.7/PKG-INFO +0 -522
- graph_crawler-5.3.7/README.md +0 -401
- graph_crawler-5.3.7/graph_crawler/__init__.py +0 -496
- graph_crawler-5.3.7/graph_crawler/__version__.py +0 -8
- graph_crawler-5.3.7/graph_crawler/ai/__init__.py +0 -50
- graph_crawler-5.3.7/graph_crawler/api/__init__.py +0 -28
- graph_crawler-5.3.7/graph_crawler/api/_core.py +0 -287
- graph_crawler-5.3.7/graph_crawler/api/_distributed.py +0 -172
- graph_crawler-5.3.7/graph_crawler/api/_shared.py +0 -110
- graph_crawler-5.3.7/graph_crawler/api/_sitemap_distributed.py +0 -238
- graph_crawler-5.3.7/graph_crawler/api/async_.py +0 -327
- graph_crawler-5.3.7/graph_crawler/api/cli.py +0 -440
- graph_crawler-5.3.7/graph_crawler/api/client/__init__.py +0 -8
- graph_crawler-5.3.7/graph_crawler/api/client/client.py +0 -476
- graph_crawler-5.3.7/graph_crawler/api/history_manager.py +0 -55
- graph_crawler-5.3.7/graph_crawler/api/project_init.py +0 -196
- graph_crawler-5.3.7/graph_crawler/api/server/__init__.py +0 -18
- graph_crawler-5.3.7/graph_crawler/api/server/crawl_monitor.py +0 -145
- graph_crawler-5.3.7/graph_crawler/api/server/dashboard.py +0 -221
- graph_crawler-5.3.7/graph_crawler/api/server/rest_api.py +0 -704
- graph_crawler-5.3.7/graph_crawler/api/server/webhooks.py +0 -421
- graph_crawler-5.3.7/graph_crawler/api/server/websocket_manager.py +0 -89
- graph_crawler-5.3.7/graph_crawler/api/stats_collector.py +0 -88
- graph_crawler-5.3.7/graph_crawler/api/sync.py +0 -544
- graph_crawler-5.3.7/graph_crawler/application/__init__.py +0 -38
- graph_crawler-5.3.7/graph_crawler/application/bootstrap.py +0 -211
- graph_crawler-5.3.7/graph_crawler/application/context/__init__.py +0 -31
- graph_crawler-5.3.7/graph_crawler/application/context/dependency_registry.py +0 -354
- graph_crawler-5.3.7/graph_crawler/application/context/graph_context.py +0 -217
- graph_crawler-5.3.7/graph_crawler/application/context/merge_context.py +0 -221
- graph_crawler-5.3.7/graph_crawler/application/dto/__init__.py +0 -67
- graph_crawler-5.3.7/graph_crawler/application/dto/edge_dto.py +0 -25
- graph_crawler-5.3.7/graph_crawler/application/dto/graph_dto.py +0 -27
- graph_crawler-5.3.7/graph_crawler/application/dto/mappers/__init__.py +0 -14
- graph_crawler-5.3.7/graph_crawler/application/dto/mappers/edge_mapper.py +0 -149
- graph_crawler-5.3.7/graph_crawler/application/dto/mappers/graph_mapper.py +0 -622
- graph_crawler-5.3.7/graph_crawler/application/dto/mappers/node_data_codec.py +0 -190
- graph_crawler-5.3.7/graph_crawler/application/dto/mappers/node_mapper.py +0 -418
- graph_crawler-5.3.7/graph_crawler/application/dto/node_dto.py +0 -27
- graph_crawler-5.3.7/graph_crawler/application/dto/utils.py +0 -272
- graph_crawler-5.3.7/graph_crawler/application/services/__init__.py +0 -60
- graph_crawler-5.3.7/graph_crawler/application/services/application_container.py +0 -208
- graph_crawler-5.3.7/graph_crawler/application/services/driver_factory.py +0 -147
- graph_crawler-5.3.7/graph_crawler/application/services/exporters/__init__.py +0 -26
- graph_crawler-5.3.7/graph_crawler/application/services/exporters/base_exporter.py +0 -150
- graph_crawler-5.3.7/graph_crawler/application/services/exporters/csv_exporter.py +0 -318
- graph_crawler-5.3.7/graph_crawler/application/services/exporters/edge_exporter.py +0 -432
- graph_crawler-5.3.7/graph_crawler/application/services/exporters/excel_exporter.py +0 -301
- graph_crawler-5.3.7/graph_crawler/application/services/exporters/jsonl_exporter.py +0 -290
- graph_crawler-5.3.7/graph_crawler/application/services/exporters/node_exporter.py +0 -284
- graph_crawler-5.3.7/graph_crawler/application/services/exporters/parquet_exporter.py +0 -248
- graph_crawler-5.3.7/graph_crawler/application/services/exporters/sql_exporter.py +0 -267
- graph_crawler-5.3.7/graph_crawler/application/services/finalize.py +0 -115
- graph_crawler-5.3.7/graph_crawler/application/services/low_memory_graph_saver.py +0 -337
- graph_crawler-5.3.7/graph_crawler/application/services/resource_budget.py +0 -179
- graph_crawler-5.3.7/graph_crawler/application/services/scheduling/__init__.py +0 -12
- graph_crawler-5.3.7/graph_crawler/application/services/scheduling/rule_based_scheduler.py +0 -105
- graph_crawler-5.3.7/graph_crawler/application/services/storage_factory.py +0 -195
- graph_crawler-5.3.7/graph_crawler/application/use_cases/__init__.py +0 -11
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/__init__.py +0 -81
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/adaptive_throttler.py +0 -373
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/async_bridge.py +0 -45
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/base_spider.py +0 -155
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/celery_batch_spider.py +0 -656
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/celery_spider.py +0 -445
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/checkpoint.py +0 -421
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/crawl_coordinator.py +0 -1068
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/dead_letter_queue.py +0 -413
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/domain_rate_limiter.py +0 -262
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/filters/__init__.py +0 -10
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/filters/base.py +0 -74
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/filters/domain_filter.py +0 -226
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/filters/domain_patterns.py +0 -30
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/filters/path_filter.py +0 -95
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/incremental_strategy.py +0 -239
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/link_processor.py +0 -885
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/multiprocess_spider.py +0 -511
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/node_scanner.py +0 -867
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/parsers/__init__.py +0 -6
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/parsers/base.py +0 -73
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/parsers/html_parser.py +0 -154
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/progress_tracker.py +0 -235
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/scheduler.py +0 -757
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/serialization_mixin.py +0 -218
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/sitemap_parser.py +0 -793
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/sitemap_processor.py +0 -404
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/sitemap_spider.py +0 -653
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/spider.py +0 -858
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/spider_lifecycle.py +0 -108
- graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/worker_pool.py +0 -154
- graph_crawler-5.3.7/graph_crawler/application/use_cases/graph_export.py +0 -504
- graph_crawler-5.3.7/graph_crawler/application/use_cases/graph_merge/__init__.py +0 -33
- graph_crawler-5.3.7/graph_crawler/application/use_cases/graph_merge/graph_operations.py +0 -632
- graph_crawler-5.3.7/graph_crawler/application/use_cases/graph_merge/merge_strategies.py +0 -348
- graph_crawler-5.3.7/graph_crawler/data/__init__.py +0 -28
- graph_crawler-5.3.7/graph_crawler/data/backends/__init__.py +0 -21
- graph_crawler-5.3.7/graph_crawler/data/backends/memory.py +0 -486
- graph_crawler-5.3.7/graph_crawler/data/backends/sqlite.py +0 -1891
- graph_crawler-5.3.7/graph_crawler/data/interfaces.py +0 -563
- graph_crawler-5.3.7/graph_crawler/domain/__init__.py +0 -62
- graph_crawler-5.3.7/graph_crawler/domain/context/__init__.py +0 -14
- graph_crawler-5.3.7/graph_crawler/domain/context/crawl_context.py +0 -531
- graph_crawler-5.3.7/graph_crawler/domain/entities/__init__.py +0 -32
- graph_crawler-5.3.7/graph_crawler/domain/entities/edge.py +0 -345
- graph_crawler-5.3.7/graph_crawler/domain/entities/edge_analysis.py +0 -223
- graph_crawler-5.3.7/graph_crawler/domain/entities/graph.py +0 -4291
- graph_crawler-5.3.7/graph_crawler/domain/entities/graph_operations.py +0 -30
- graph_crawler-5.3.7/graph_crawler/domain/entities/graph_statistics.py +0 -10
- graph_crawler-5.3.7/graph_crawler/domain/entities/merge_strategies.py +0 -33
- graph_crawler-5.3.7/graph_crawler/domain/entities/node.py +0 -962
- graph_crawler-5.3.7/graph_crawler/domain/entities/redirect_mode.py +0 -23
- graph_crawler-5.3.7/graph_crawler/domain/entities/registries.py +0 -223
- graph_crawler-5.3.7/graph_crawler/domain/entities/sitemap_node.py +0 -77
- graph_crawler-5.3.7/graph_crawler/domain/entities/strategies.py +0 -202
- graph_crawler-5.3.7/graph_crawler/domain/events/__init__.py +0 -162
- graph_crawler-5.3.7/graph_crawler/domain/events/base.py +0 -60
- graph_crawler-5.3.7/graph_crawler/domain/events/crawl.py +0 -112
- graph_crawler-5.3.7/graph_crawler/domain/events/edge.py +0 -43
- graph_crawler-5.3.7/graph_crawler/domain/events/event_bus.py +0 -250
- graph_crawler-5.3.7/graph_crawler/domain/events/events.py +0 -313
- graph_crawler-5.3.7/graph_crawler/domain/events/fetch.py +0 -120
- graph_crawler-5.3.7/graph_crawler/domain/events/idempotent_subscriber.py +0 -120
- graph_crawler-5.3.7/graph_crawler/domain/events/legacy_type_map.py +0 -45
- graph_crawler-5.3.7/graph_crawler/domain/events/llm.py +0 -148
- graph_crawler-5.3.7/graph_crawler/domain/events/node.py +0 -111
- graph_crawler-5.3.7/graph_crawler/domain/events/plugin.py +0 -92
- graph_crawler-5.3.7/graph_crawler/domain/events/scheduler.py +0 -98
- graph_crawler-5.3.7/graph_crawler/domain/events/storage.py +0 -161
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/__init__.py +0 -113
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/adapter.py +0 -29
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/control_channel.py +0 -381
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/crawl_context.py +0 -49
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/distributed_spider.py +0 -62
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/driver.py +0 -87
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/event_bus.py +0 -61
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/exporters.py +0 -69
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/filter.py +0 -19
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/merge_context.py +0 -52
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/node_interfaces.py +0 -389
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/parser.py +0 -88
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/plugin_manager.py +0 -53
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/processor.py +0 -11
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/scanner.py +0 -57
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/scheduler.py +0 -43
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/scheduling/__init__.py +0 -40
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/scheduling/frontier.py +0 -97
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/scheduling/scheduler.py +0 -107
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/spider.py +0 -63
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/stop_condition.py +0 -211
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/storage/__init__.py +0 -43
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/storage/base.py +0 -68
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/storage/eviction.py +0 -98
- graph_crawler-5.3.7/graph_crawler/domain/interfaces/storage/queue.py +0 -234
- graph_crawler-5.3.7/graph_crawler/domain/services/__init__.py +0 -216
- graph_crawler-5.3.7/graph_crawler/domain/services/adjacency_maintenance.py +0 -95
- graph_crawler-5.3.7/graph_crawler/domain/services/cycle_detection.py +0 -92
- graph_crawler-5.3.7/graph_crawler/domain/services/degree_ranking.py +0 -47
- graph_crawler-5.3.7/graph_crawler/domain/services/depth_context.py +0 -123
- graph_crawler-5.3.7/graph_crawler/domain/services/display_title.py +0 -47
- graph_crawler-5.3.7/graph_crawler/domain/services/duplicate_detection.py +0 -141
- graph_crawler-5.3.7/graph_crawler/domain/services/duplicate_selector.py +0 -127
- graph_crawler-5.3.7/graph_crawler/domain/services/duplicate_stats.py +0 -63
- graph_crawler-5.3.7/graph_crawler/domain/services/edge_filter.py +0 -82
- graph_crawler-5.3.7/graph_crawler/domain/services/edge_statistics.py +0 -110
- graph_crawler-5.3.7/graph_crawler/domain/services/eviction_policy.py +0 -83
- graph_crawler-5.3.7/graph_crawler/domain/services/graph_query_cascade.py +0 -178
- graph_crawler-5.3.7/graph_crawler/domain/services/graph_statistics.py +0 -186
- graph_crawler-5.3.7/graph_crawler/domain/services/graph_traversal.py +0 -150
- graph_crawler-5.3.7/graph_crawler/domain/services/language_versions.py +0 -73
- graph_crawler-5.3.7/graph_crawler/domain/services/lsh_index.py +0 -99
- graph_crawler-5.3.7/graph_crawler/domain/services/minimap_builder.py +0 -147
- graph_crawler-5.3.7/graph_crawler/domain/services/node_content_resolver.py +0 -128
- graph_crawler-5.3.7/graph_crawler/domain/services/node_view_builder.py +0 -163
- graph_crawler-5.3.7/graph_crawler/domain/services/pagination_guards.py +0 -136
- graph_crawler-5.3.7/graph_crawler/domain/services/predicate_ops.py +0 -77
- graph_crawler-5.3.7/graph_crawler/domain/services/schema_filter.py +0 -138
- graph_crawler-5.3.7/graph_crawler/domain/services/search_haystack.py +0 -102
- graph_crawler-5.3.7/graph_crawler/domain/services/simhash_clustering.py +0 -96
- graph_crawler-5.3.7/graph_crawler/domain/services/simhash_fallback.py +0 -59
- graph_crawler-5.3.7/graph_crawler/domain/services/similarity_ranking.py +0 -62
- graph_crawler-5.3.7/graph_crawler/domain/services/text_search.py +0 -157
- graph_crawler-5.3.7/graph_crawler/domain/services/url_clustering.py +0 -88
- graph_crawler-5.3.7/graph_crawler/domain/services/url_deduplication.py +0 -101
- graph_crawler-5.3.7/graph_crawler/domain/services/url_normalizer.py +0 -117
- graph_crawler-5.3.7/graph_crawler/domain/services/url_rule_matcher.py +0 -111
- graph_crawler-5.3.7/graph_crawler/domain/services/url_tree_builder.py +0 -197
- graph_crawler-5.3.7/graph_crawler/domain/value_objects/__init__.py +0 -88
- graph_crawler-5.3.7/graph_crawler/domain/value_objects/configs.py +0 -894
- graph_crawler-5.3.7/graph_crawler/domain/value_objects/domain_patterns.py +0 -71
- graph_crawler-5.3.7/graph_crawler/domain/value_objects/fetch_options.py +0 -57
- graph_crawler-5.3.7/graph_crawler/domain/value_objects/html_document.py +0 -59
- graph_crawler-5.3.7/graph_crawler/domain/value_objects/lifecycle.py +0 -30
- graph_crawler-5.3.7/graph_crawler/domain/value_objects/models.py +0 -1315
- graph_crawler-5.3.7/graph_crawler/domain/value_objects/page_actions.py +0 -72
- graph_crawler-5.3.7/graph_crawler/domain/value_objects/redirect_hop.py +0 -49
- graph_crawler-5.3.7/graph_crawler/domain/value_objects/scheduling/__init__.py +0 -23
- graph_crawler-5.3.7/graph_crawler/domain/value_objects/scheduling/crawl_request.py +0 -153
- graph_crawler-5.3.7/graph_crawler/domain/value_objects/scheduling/retry_policy.py +0 -109
- graph_crawler-5.3.7/graph_crawler/domain/value_objects/settings.py +0 -350
- graph_crawler-5.3.7/graph_crawler/domain/value_objects/url_normalization.py +0 -497
- graph_crawler-5.3.7/graph_crawler/domain/value_objects/views.py +0 -293
- graph_crawler-5.3.7/graph_crawler/extensions/__init__.py +0 -1
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/__init__.py +0 -65
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/base.py +0 -136
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/cache_middleware.py +0 -277
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/chain.py +0 -155
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/error_recovery_middleware.py +0 -442
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/logging_middleware.py +0 -108
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/proxy_health.py +0 -141
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/proxy_middleware.py +0 -209
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/proxy_models.py +0 -80
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/proxy_selection.py +0 -87
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/rate_limit_middleware.py +0 -262
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/request_response_middleware.py +0 -476
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/retry_middleware.py +0 -279
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/robots_cache.py +0 -85
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/robots_middleware.py +0 -110
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/robots_validator.py +0 -60
- graph_crawler-5.3.7/graph_crawler/extensions/middleware/user_agent_middleware.py +0 -251
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/__init__.py +0 -57
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/builtin/__init__.py +0 -7
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/builtin/stats_export_plugin.py +0 -99
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/crawl_engine/__init__.py +0 -24
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/crawl_engine/base.py +0 -155
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/crawl_engine/priority_provider.py +0 -247
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/crawl_engine/smart_crawl.py +0 -296
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/__init__.py +0 -128
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/anti_bot_detection.py +0 -55
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/anti_bot_playwright.py +0 -88
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/anti_bot_scripts.py +0 -136
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/anti_bot_stealth.py +0 -177
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/base.py +0 -121
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/captcha/__init__.py +0 -22
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/captcha/detector.py +0 -94
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/captcha/models.py +0 -74
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/captcha/plugin.py +0 -228
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/captcha/services.py +0 -389
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/__init__.py +0 -86
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/base.py +0 -520
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/content_extractors/__init__.py +0 -15
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/content_extractors/base.py +0 -82
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/content_extractors/goose3_extractor.py +0 -80
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/content_extractors/newspaper_extractor.py +0 -93
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/content_extractors/plugin.py +0 -132
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/content_extractors/readability_extractor.py +0 -91
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/defaults.py +0 -32
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/extractors/__init__.py +0 -30
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/extractors/email_extractor.py +0 -146
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/extractors/phone_extractor.py +0 -136
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/extractors/price_extractor.py +0 -137
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/links.py +0 -184
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/markdown_plugin.py +0 -249
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/metadata.py +0 -384
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/smart_page_finder.py +0 -749
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/__init__.py +0 -51
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/constants.py +0 -27
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/exceptions.py +0 -18
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/extractor.py +0 -361
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/options.py +0 -62
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/parsers/__init__.py +0 -25
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/parsers/base.py +0 -43
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/parsers/jsonld.py +0 -331
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/parsers/microdata.py +0 -117
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/parsers/opengraph.py +0 -66
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/parsers/rdfa.py +0 -122
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/parsers/twitter.py +0 -66
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/plugin.py +0 -132
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/result.py +0 -233
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/text.py +0 -76
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/url_normalizer.py +0 -363
- graph_crawler-5.3.7/graph_crawler/extensions/plugins/registry.py +0 -97
- graph_crawler-5.3.7/graph_crawler/infrastructure/__init__.py +0 -1
- graph_crawler-5.3.7/graph_crawler/infrastructure/adapters/__init__.py +0 -60
- graph_crawler-5.3.7/graph_crawler/infrastructure/adapters/base.py +0 -498
- graph_crawler-5.3.7/graph_crawler/infrastructure/adapters/selectolax_adapter.py +0 -233
- graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/__init__.py +0 -37
- graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/celery_app.py +0 -202
- graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/celery_batch.py +0 -284
- graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/celery_job_task.py +0 -415
- graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/celery_unified.py +0 -419
- graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/config.py +0 -201
- graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/easy_crawler.py +0 -279
- graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/worker_api.py +0 -607
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/__init__.py +0 -135
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/auto_storage.py +0 -336
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/base.py +0 -111
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/eviction.py +0 -100
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/graph_repository.py +0 -401
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/json/__init__.py +0 -10
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/json_storage.py +0 -375
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/lmdb_eviction_storage.py +0 -401
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/memory/__init__.py +0 -1
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/memory_dedup_store.py +0 -90
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/memory_frontier.py +0 -180
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/memory_politeness_store.py +0 -88
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/memory_storage.py +0 -166
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/mongodb_storage.py +0 -383
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/naming_strategy.py +0 -174
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/postgresql_storage.py +0 -449
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/protocols/__init__.py +0 -1
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/redis_dedup_store.py +0 -119
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/redis_politeness_store.py +0 -122
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/repository.py +0 -130
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/sqlite/__init__.py +0 -10
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/sqlite_eviction_storage.py +0 -919
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/sqlite_storage.py +0 -775
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/__init__.py +0 -47
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/file_job_storage.py +0 -245
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/file_queue_storage.py +0 -248
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/memory_job_storage.py +0 -94
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/memory_queue_storage.py +0 -117
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/postgresql_job_storage.py +0 -317
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/postgresql_queue_storage.py +0 -364
- graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/unified_storage.py +0 -244
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/__init__.py +0 -135
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/__init__.py +0 -38
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/config.py +0 -47
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/context.py +0 -55
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/driver.py +0 -724
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/driver_curl_cffi.py +0 -407
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/plugins/__init__.py +0 -48
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle.py +0 -484
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle_adaptive.py +0 -1435
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/plugins/headers.py +0 -69
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/plugins/http_cache.py +0 -321
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/plugins/rate_limiter.py +0 -96
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/plugins/retry.py +0 -316
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/stages.py +0 -33
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/base.py +0 -349
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/base_plugin.py +0 -151
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/compression/__init__.py +0 -23
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/compression/strategy.py +0 -219
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/connection_pool.py +0 -349
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/context.py +0 -114
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/core/__init__.py +0 -15
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/core/base_async.py +0 -378
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/core/body_decoder.py +0 -26
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/core/mixins.py +0 -258
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/core/redirect_utils.py +0 -168
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/escalation.py +0 -1105
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/factory.py +0 -210
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/__init__.py +0 -19
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/adaptive_semaphore.py +0 -65
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/batch_scheduler.py +0 -764
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/browser_factory.py +0 -580
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/browser_profile.py +0 -718
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/config.py +0 -84
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/context.py +0 -46
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/driver.py +0 -80
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/engine.py +0 -480
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/fetch_metrics.py +0 -115
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/net/__init__.py +0 -41
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/net/geoip.py +0 -136
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/net/proxy_pool.py +0 -225
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/net/rate_limiter.py +0 -139
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/page_actions.py +0 -186
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/page_fetch.py +0 -968
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/__init__.py +0 -71
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/_network_record.py +0 -66
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/captcha_detector.py +0 -315
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/captcha_solver.py +0 -108
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/cookie_consent.py +0 -565
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/form_filler.py +0 -177
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/human_bezier.py +0 -297
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/network_log.py +0 -406
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/ram_adaptive.py +0 -208
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/screenshot.py +0 -169
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/user_profile.py +0 -556
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/pool_lifecycle.py +0 -658
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/pooled_driver.py +0 -706
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/stages.py +0 -35
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/stealth_hints.py +0 -153
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/plugin_manager.py +0 -316
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/protocols.py +0 -132
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/undetected/__init__.py +0 -8
- graph_crawler-5.3.7/graph_crawler/infrastructure/transport/undetected/async_driver.py +0 -465
- graph_crawler-5.3.7/graph_crawler/native/__init__.py +0 -199
- graph_crawler-5.3.7/graph_crawler/native/bloom_filter.pyx +0 -255
- graph_crawler-5.3.7/graph_crawler/native/html_parser.pyx +0 -262
- graph_crawler-5.3.7/graph_crawler/native/setup.py +0 -103
- graph_crawler-5.3.7/graph_crawler/native/url_utils.pyx +0 -209
- graph_crawler-5.3.7/graph_crawler/observability/__init__.py +0 -62
- graph_crawler-5.3.7/graph_crawler/observability/decorators/__init__.py +0 -11
- graph_crawler-5.3.7/graph_crawler/observability/decorators/cache.py +0 -126
- graph_crawler-5.3.7/graph_crawler/observability/decorators/log.py +0 -56
- graph_crawler-5.3.7/graph_crawler/observability/decorators/retry.py +0 -121
- graph_crawler-5.3.7/graph_crawler/observability/decorators/timing.py +0 -43
- graph_crawler-5.3.7/graph_crawler/observability/error_tracing.py +0 -361
- graph_crawler-5.3.7/graph_crawler/observability/extensions/__init__.py +0 -18
- graph_crawler-5.3.7/graph_crawler/observability/extensions/memory_guard.py +0 -237
- graph_crawler-5.3.7/graph_crawler/observability/extensions/state_manager.py +0 -319
- graph_crawler-5.3.7/graph_crawler/observability/listeners/__init__.py +0 -33
- graph_crawler-5.3.7/graph_crawler/observability/listeners/base.py +0 -82
- graph_crawler-5.3.7/graph_crawler/observability/listeners/base_metrics_listener.py +0 -18
- graph_crawler-5.3.7/graph_crawler/observability/listeners/crawl_listener.py +0 -28
- graph_crawler-5.3.7/graph_crawler/observability/listeners/dlq_listener.py +0 -215
- graph_crawler-5.3.7/graph_crawler/observability/listeners/error_listener.py +0 -18
- graph_crawler-5.3.7/graph_crawler/observability/listeners/logging_listener.py +0 -88
- graph_crawler-5.3.7/graph_crawler/observability/listeners/metrics_listener.py +0 -109
- graph_crawler-5.3.7/graph_crawler/observability/listeners/node_listener.py +0 -33
- graph_crawler-5.3.7/graph_crawler/observability/listeners/plugin_listener.py +0 -28
- graph_crawler-5.3.7/graph_crawler/observability/listeners/storage_listener.py +0 -21
- graph_crawler-5.3.7/graph_crawler/observability/listeners/url_listener.py +0 -29
- graph_crawler-5.3.7/graph_crawler/observability/metrics/__init__.py +0 -23
- graph_crawler-5.3.7/graph_crawler/observability/metrics/memory_profiler.py +0 -508
- graph_crawler-5.3.7/graph_crawler/observability/metrics/metrics_collector.py +0 -408
- graph_crawler-5.3.7/graph_crawler/observability/metrics/prometheus_metrics.py +0 -293
- graph_crawler-5.3.7/graph_crawler/observability/metrics_core.py +0 -389
- graph_crawler-5.3.7/graph_crawler/observability/structured_logging.py +0 -216
- graph_crawler-5.3.7/graph_crawler/observability/tracing.py +0 -291
- graph_crawler-5.3.7/graph_crawler/optimizations/__init__.py +0 -89
- graph_crawler-5.3.7/graph_crawler/optimizations/generators.py +0 -477
- graph_crawler-5.3.7/graph_crawler/optimizations/memory_slots.py +0 -641
- graph_crawler-5.3.7/graph_crawler/optimizations/simhash_numba.py +0 -284
- graph_crawler-5.3.7/graph_crawler/shared/__init__.py +0 -25
- graph_crawler-5.3.7/graph_crawler/shared/constants.py +0 -943
- graph_crawler-5.3.7/graph_crawler/shared/dto/__init__.py +0 -38
- graph_crawler-5.3.7/graph_crawler/shared/dto/edge_dto.py +0 -52
- graph_crawler-5.3.7/graph_crawler/shared/dto/graph_dto.py +0 -97
- graph_crawler-5.3.7/graph_crawler/shared/dto/node_dto.py +0 -193
- graph_crawler-5.3.7/graph_crawler/shared/error_handling/__init__.py +0 -15
- graph_crawler-5.3.7/graph_crawler/shared/error_handling/error_handler.py +0 -333
- graph_crawler-5.3.7/graph_crawler/shared/exceptions.py +0 -394
- graph_crawler-5.3.7/graph_crawler/shared/platform/__init__.py +0 -125
- graph_crawler-5.3.7/graph_crawler/shared/platform/adaptive_resolver.py +0 -414
- graph_crawler-5.3.7/graph_crawler/shared/platform/config.py +0 -635
- graph_crawler-5.3.7/graph_crawler/shared/platform/detector.py +0 -334
- graph_crawler-5.3.7/graph_crawler/shared/platform/diagnostics.py +0 -660
- graph_crawler-5.3.7/graph_crawler/shared/platform/metrics.py +0 -299
- graph_crawler-5.3.7/graph_crawler/shared/security/__init__.py +0 -22
- graph_crawler-5.3.7/graph_crawler/shared/security/url_sanitizer.py +0 -100
- graph_crawler-5.3.7/graph_crawler/shared/security/url_validator.py +0 -299
- graph_crawler-5.3.7/graph_crawler/shared/utils/__init__.py +0 -212
- graph_crawler-5.3.7/graph_crawler/shared/utils/async_compat.py +0 -140
- graph_crawler-5.3.7/graph_crawler/shared/utils/bloom_filter.py +0 -373
- graph_crawler-5.3.7/graph_crawler/shared/utils/captcha/__init__.py +0 -36
- graph_crawler-5.3.7/graph_crawler/shared/utils/captcha/base.py +0 -92
- graph_crawler-5.3.7/graph_crawler/shared/utils/captcha/manager.py +0 -474
- graph_crawler-5.3.7/graph_crawler/shared/utils/celery_config.py +0 -332
- graph_crawler-5.3.7/graph_crawler/shared/utils/celery_helpers.py +0 -153
- graph_crawler-5.3.7/graph_crawler/shared/utils/cgroup_limits.py +0 -227
- graph_crawler-5.3.7/graph_crawler/shared/utils/content_decoder.py +0 -150
- graph_crawler-5.3.7/graph_crawler/shared/utils/distributed_rate_limiter.py +0 -265
- graph_crawler-5.3.7/graph_crawler/shared/utils/distributed_rate_limiter_backends.py +0 -284
- graph_crawler-5.3.7/graph_crawler/shared/utils/dns_cache.py +0 -370
- graph_crawler-5.3.7/graph_crawler/shared/utils/enterprise_ua_generator.py +0 -294
- graph_crawler-5.3.7/graph_crawler/shared/utils/event_publisher_mixin.py +0 -187
- graph_crawler-5.3.7/graph_crawler/shared/utils/fast_json.py +0 -240
- graph_crawler-5.3.7/graph_crawler/shared/utils/fingerprint.py +0 -36
- graph_crawler-5.3.7/graph_crawler/shared/utils/fingerprint_data.py +0 -99
- graph_crawler-5.3.7/graph_crawler/shared/utils/fingerprint_generators.py +0 -128
- graph_crawler-5.3.7/graph_crawler/shared/utils/fingerprint_profile.py +0 -121
- graph_crawler-5.3.7/graph_crawler/shared/utils/fingerprint_stealth.py +0 -105
- graph_crawler-5.3.7/graph_crawler/shared/utils/html_utils.py +0 -118
- graph_crawler-5.3.7/graph_crawler/shared/utils/id_generator.py +0 -195
- graph_crawler-5.3.7/graph_crawler/shared/utils/isolated_executor.py +0 -200
- graph_crawler-5.3.7/graph_crawler/shared/utils/memory_optimizer.py +0 -536
- graph_crawler-5.3.7/graph_crawler/shared/utils/rate_limiter.py +0 -185
- graph_crawler-5.3.7/graph_crawler/shared/utils/regex_validator.py +0 -966
- graph_crawler-5.3.7/graph_crawler/shared/utils/shadow_dom.py +0 -36
- graph_crawler-5.3.7/graph_crawler/shared/utils/text_utils.py +0 -387
- graph_crawler-5.3.7/graph_crawler/shared/utils/url_patterns.py +0 -135
- graph_crawler-5.3.7/graph_crawler/shared/utils/url_utils.py +0 -595
- graph_crawler-5.3.7/graph_crawler/shared/utils/user_agent_rotator.py +0 -333
- graph_crawler-5.3.7/graph_crawler/shared/utils/validation_helpers.py +0 -225
- graph_crawler-5.3.7/graph_crawler/shared/utils/visualization.py +0 -134
- graph_crawler-5.3.7/graph_crawler/shared/utils/visualization_core.py +0 -566
- graph_crawler-5.3.7/graph_crawler/tools/__init__.py +0 -1
- graph_crawler-5.3.7/graph_crawler/tools/db_cleanup.py +0 -263
- graph_crawler-5.3.7/graph_crawler.egg-info/PKG-INFO +0 -522
- graph_crawler-5.3.7/graph_crawler.egg-info/SOURCES.txt +0 -479
- graph_crawler-5.3.7/graph_crawler.egg-info/requires.txt +0 -125
- graph_crawler-5.3.7/pyproject.toml +0 -301
- graph_crawler-5.3.7/setup.cfg +0 -4
- {graph_crawler-5.3.7 → graph_crawler-5.4.2}/graph_crawler/py.typed +0 -0
- {graph_crawler-5.3.7 → graph_crawler-5.4.2}/graph_crawler.egg-info/dependency_links.txt +0 -0
- {graph_crawler-5.3.7 → graph_crawler-5.4.2}/graph_crawler.egg-info/entry_points.txt +0 -0
- {graph_crawler-5.3.7 → graph_crawler-5.4.2}/graph_crawler.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 5.4.2 — 2026-10
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **A frozen browser tab no longer holds the crawl until the crawl-level timeout** (Parser `WEB_GRAF_ISSUES.md` §6,
|
|
8
|
+
NEW-3-5). After `fetch_timeout` the playwright fetcher asked the tab for partial HTML (`page.content()`) and
|
|
9
|
+
provenance (`page.evaluate`) without any bound; a tab whose renderer is busy with page script never answers, so the
|
|
10
|
+
worker hung (`Crawl-level timeout … in_flight=1`, ~12 min idle per scan in production). Recovery is now bounded by
|
|
11
|
+
`TIMEOUT_RECOVERY_S` (5 s); an unresponsive tab is logged (`Browser tab frozen after fetch timeout`) and closed, the
|
|
12
|
+
pool opens a fresh one. A slow but alive tab still returns partial HTML as before.
|
|
13
|
+
- **Worker-pool stall watchdog for browser drivers.** If no node finishes for
|
|
14
|
+
`2 × driver.fetch_timeout + node_enrich_timeout + 10 s`, stuck workers are cancelled (`Worker pool stalled`) and the
|
|
15
|
+
pool stops; with a non-empty frontier the stop is recorded as `stalled`. HTTP drivers are not affected.
|
|
16
|
+
- Tests: `tests/test_new_3_5_stalled_browser_pool.py` (real Chromium part: `GC_TEST_REAL_BROWSER=1`).
|
|
17
|
+
|
|
18
|
+
## 5.4.1 — 2026-10
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **A page whose script rewrites the address bar is no longer treated as a redirect** (Parser `WEB_GRAF_ISSUES.md` §5,
|
|
23
|
+
NEW-1-15). Playwright provenance took `page.url` as the final URL, so `history.pushState/replaceState`
|
|
24
|
+
(e.g. `/en/vacancies/<slug>` → `/en`) looked like a JS redirect and `Graph._handle_redirect_canonical` merged the
|
|
25
|
+
scanned page into the existing `/en` node — its content was dropped (`deduped by canonical-redirect: N`).
|
|
26
|
+
`_build_playwright_provenance` now compares `page.url` with the URL of the loaded document
|
|
27
|
+
(`performance.getEntriesByType('navigation')[0].name`): if they differ, the document URL is the final one
|
|
28
|
+
(no redirect, or the server-3xx target); the rewrite is logged at INFO
|
|
29
|
+
`Address rewritten by page script (history API), not a redirect`. Real redirects (server 3xx, `location.href`)
|
|
30
|
+
are unchanged; if the document URL cannot be read, the old behaviour is kept.
|
|
31
|
+
- Tests: `tests/transport/test_history_api_not_redirect.py` (real Chromium part: `GC_TEST_REAL_BROWSER=1`).
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- **Non-network error policies go through `resilix` (new core dependency `resilix>=1.1,<2`, zero-dependency).**
|
|
36
|
+
- `shared/utils/best_effort.close_quietly` — one best-effort close with timeout for browser/context/page/CDP
|
|
37
|
+
and the persistent engine session (`BrowserFactory._safe_close`, `close_browser`, `detach_page_cdp`,
|
|
38
|
+
PID-probe CDP detach, `PoolLifecycle._stop_persistent_session`). Log format is unchanged
|
|
39
|
+
(`<name> close timeout (<t>s)` at WARNING, other errors at DEBUG, `TargetClosedError` is silent);
|
|
40
|
+
external `CancelledError` is not swallowed.
|
|
41
|
+
- Functions whose whole body was one `try → default` are now `@protect.when(..., return_=...)`
|
|
42
|
+
(playwright `browser_factory`, `cookie_consent`, nodriver driver, captcha balance checks, storages
|
|
43
|
+
`clear`/`save_partial`, `cgroup_limits`, `url_validator.is_url_safe`, …).
|
|
44
|
+
- `try/except Exception: logger.debug("Suppressed …")` blocks are now `protect.suppress(...)` with the
|
|
45
|
+
calling function name as the log message.
|
|
46
|
+
- Batch fetches (`fetch_many`, `fetch_many_fast`) and sitemap fan-out use `resilix.gather`: results keep input
|
|
47
|
+
order, a failed URL becomes an error `FetchResponse`, concurrency is capped by `limit=max_concurrent`, and
|
|
48
|
+
an external `CancelledError` now propagates instead of being returned as a result.
|
|
49
|
+
- Fire-and-forget tasks (async middleware `setup()`, dashboard broadcast, event publishing, sitemap session
|
|
50
|
+
close) use `resilix.spawn` — no lost tasks, failures are logged.
|
|
51
|
+
- `CODE_STYLE.md` §7.3/§7.11: non-network policies — only `resilix.protect`; network retries — only
|
|
52
|
+
`RetryMiddleware`/`RetryPolicy`; `protect.timeout` only for `async def`.
|
|
53
|
+
|
|
54
|
+
### Removed
|
|
55
|
+
|
|
56
|
+
- Unused `graph_crawler.observability.decorators` (`retry`, `async_retry`, `timed`, `cached`, `log_calls`;
|
|
57
|
+
357 lines, no imports anywhere).
|
|
58
|
+
|
|
59
|
+
## 5.4.0 — 2026-10
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- **Oversized and binary responses are no longer downloaded in full** (Parser `WEB_GRAF_ISSUES.md` §1).
|
|
64
|
+
The dead `DEFAULT_MAX_CONTENT_LENGTH` constant is now enforced by `transport/core/body_limit.py` on every tier:
|
|
65
|
+
- HTTP tiers (aiohttp, curl_cffi — `fetch` and `fetch_fast`): a `Content-Length` above `max_response_bytes`
|
|
66
|
+
(default 10 MB) or a non-textual `Content-Type` (`application/octet-stream`, `video/*`, `application/zip`…,
|
|
67
|
+
`skip_non_textual_bodies=True`) skips the body entirely; without `Content-Length` the body is streamed in chunks
|
|
68
|
+
and the connection is dropped as soon as the limit is exceeded; decoded HTML is also capped by
|
|
69
|
+
`max_page_html_bytes` (default 15 MB);
|
|
70
|
+
- browser tiers: `max_page_html_bytes` caps the rendered HTML (Playwright incl. partial HTML after a timeout, and
|
|
71
|
+
nodriver); `max_page_total_bytes` (default 0 = off) caps the sum of all responses of a page and aborts further
|
|
72
|
+
non-document requests;
|
|
73
|
+
- a skipped body is returned as `html=None`, `error="response_too_large: …" / "non_textual_content: …"`,
|
|
74
|
+
`extras["body_skipped"]="too_large" | "non_textual"`; `EscalatingDriver` does not escalate it, forwards all
|
|
75
|
+
limit keys to its tiers, and the node gets `metadata["body_skipped"]` / `metadata["page_total_bytes_exceeded"]`;
|
|
76
|
+
- sitemaps keep their own larger limit: a sitemap skipped by the driver is refetched via the sitemap path.
|
|
77
|
+
- `0` disables any of the limits.
|
|
78
|
+
- `AsyncUndetectedConfig.sandbox` was declared but ignored (hard-coded `False`); it is now passed to `uc.start()`
|
|
79
|
+
(default changed to `False`, i.e. the effective behaviour is unchanged).
|
|
80
|
+
- Tests: `tests/transport/test_body_limits.py`.
|
|
81
|
+
- **Compressed sitemaps (`.gz`) are read completely** (Parser `WEB_GRAF_ISSUES.md` §2, NEW-3-9).
|
|
82
|
+
`SitemapParser._fetch_aiohttp` used a single `response.content.read(MAX_SITEMAP_SIZE)`, which returns only the bytes
|
|
83
|
+
already received (usually the first network chunk), so a 30–300 KB `.gz` sitemap was cut to a few KB, failed to
|
|
84
|
+
decompress and was logged as `Sitemap truncated (exceeded 52428800 bytes)`. The body is now read in chunks until EOF
|
|
85
|
+
or until the limit; `truncated` is set only when the file is really larger than the limit (the log line now also
|
|
86
|
+
shows the number of bytes read). A `Content-Length` above the limit still skips the body.
|
|
87
|
+
- New parameter `max_sitemap_bytes` in `crawl_sitemap` / `async_crawl_sitemap` / `SitemapSpider`
|
|
88
|
+
(`None` → `MAX_SITEMAP_SIZE` 50 MB, `0` → no limit); previously the limit could be set only on a hand-built
|
|
89
|
+
`SitemapParser`.
|
|
90
|
+
- Tests: `tests/test_sitemap_gz_streaming.py` (local server streams a 5 000-URL `.gz` in 4 KB chunks).
|
|
91
|
+
- **Redirect merge no longer logs `backend checkpoint skipped`** (Parser `WEB_GRAF_ISSUES.md` §3, NEW-3-19).
|
|
92
|
+
After merging a redirected node into an existing final node, `Graph._handle_redirect_canonical` called
|
|
93
|
+
`backend._run_async(backend.checkpoint())` without the `result_in_loop` argument added in 5.3.7, so every merge raised
|
|
94
|
+
`TypeError` (caught, logged as a warning, coroutine never run). The WAL checkpoint is not needed there —
|
|
95
|
+
`delete_node_sync` has already removed the node and its edges from the DB and the cache, and readers share the same
|
|
96
|
+
connection — so the call is removed. `SQLiteBackend._run_async` now defaults `result_in_loop=None`, which also fixes
|
|
97
|
+
the `Graph.remove_node` fallback for backends without `delete_node_sync`.
|
|
98
|
+
Tests: `tests/test_redirect_canonical_sqlite.py`.
|
|
99
|
+
- **A large JSON-LD block with a `JobPosting` is no longer dropped whole** (Parser `WEB_GRAF_ISSUES.md` §4, NEW-2-12).
|
|
100
|
+
A block larger than `max_jsonld_size` (100 KB) but not larger than `max_jsonld_oversized_size` (new, default 5 MB) is
|
|
101
|
+
parsed when a quick regex finds an object of a type from `oversized_keep_types` (new, default `["JobPosting"]`;
|
|
102
|
+
plain, list and `https://schema.org/…` forms of `@type`), and only those objects are kept — also from `@graph`,
|
|
103
|
+
arrays and nested `ItemList` items; the rest of the block is discarded. Other oversized blocks are skipped as before;
|
|
104
|
+
the warning now states the size and why (`no JobPosting inside` / `contains JobPosting but exceeds hard limit N` /
|
|
105
|
+
`JobPosting mentioned but no such object found`). `oversized_keep_types=[]` restores the plain size limit.
|
|
106
|
+
Tests: `tests/unit/plugins/node/structured_data/test_jsonld_oversized.py`.
|
|
107
|
+
- `LMDBEvictionStorage` had no `load_all_edges_sync`, so `Graph.load_all_from_disk()` with the LMDB backend (chosen
|
|
108
|
+
automatically when `lmdb` is installed) restored nodes but silently dropped evicted edges. Tests:
|
|
109
|
+
`tests/unit/test_p9_hotpath.py` (SQLite and LMDB).
|
|
110
|
+
|
|
111
|
+
## 5.3.7 — 2026-10
|
|
112
|
+
|
|
113
|
+
### Fixed
|
|
114
|
+
|
|
115
|
+
- **A crawl worker no longer dies on a canonical redirect while another SQLite statement is running.**
|
|
116
|
+
`SQLiteBackend._sync_execute` treated `sqlite3.OperationalError: cannot commit transaction - SQL statements in
|
|
117
|
+
progress` as a failed write, although the DELETE/UPDATE had already run in the open transaction of the shared
|
|
118
|
+
connection. The fallback `_run_async` then ran the coroutine on a new loop in a thread (`asyncio.Lock ... is bound to
|
|
119
|
+
a different event loop`) and, catching that `RuntimeError`, called `asyncio.run()` inside the running loop — the
|
|
120
|
+
worker failed (`Graph.remove_node` → `delete_node_sync`). Now:
|
|
121
|
+
- a commit refused because of in-progress statements is deferred: the change stays in the open transaction and is
|
|
122
|
+
committed by the next async commit or by `close()`;
|
|
123
|
+
- `_run_async` inside a running loop schedules the write as a task on that loop (errors are logged) instead of
|
|
124
|
+
blocking on a foreign loop; outside a loop it still runs synchronously;
|
|
125
|
+
- `close()` awaits scheduled writes and commits before closing.
|
|
126
|
+
- Tests: `tests/test_sqlite_sync_write_in_loop.py`.
|
|
127
|
+
|
|
128
|
+
## 5.3.6 — 2026-06
|
|
129
|
+
|
|
130
|
+
### Fixed
|
|
131
|
+
|
|
132
|
+
- **A timeout on robots.txt / sitemap stops the sitemap phase of the host (1.6, daxx.com).** A down origin behind
|
|
133
|
+
Cloudflare does not always answer 52x: robots.txt came back after ~20 s and every sitemap request hung until the
|
|
134
|
+
client timeout, then the default candidates (`sitemap.xml`, `sitemap_index.xml`) and the driver's retries/tier
|
|
135
|
+
escalation repeated it (80–180 s per domain). Now:
|
|
136
|
+
- `SitemapParser._fetch` bounds the whole request by `SitemapParser.timeout` (covers driver retries and tier
|
|
137
|
+
escalation); a timeout is an answer "origin does not respond", not a reason to try again;
|
|
138
|
+
- the first timeout (no status + timeout error, incl. the driver's `tier timeout after …`) or CF 520–527 marks the
|
|
139
|
+
host `ORIGIN_DOWN` (`SitemapParser.mark_origin_down` / `origin_down_reason`); no further request to that host in
|
|
140
|
+
this sitemap phase — no default candidates, no nested sitemaps, no retries;
|
|
141
|
+
- `result["origin_down_reason"]` = `"timeout"` | `"cf_52x"`; `SitemapSpider.origin_down`,
|
|
142
|
+
`get_stats()["origin_down"]`, `SITEMAP_CRAWL_COMPLETED.data["origin_down"]`, and the robots.txt node gets
|
|
143
|
+
`error_message = "origin_down: <reason>"`. A spider-level `SITEMAP_PARSE_TIMEOUT` also marks the host down.
|
|
144
|
+
- Non-timeout network errors (DNS, connection refused) keep the old behaviour (default candidates are tried).
|
|
145
|
+
Live: daxx.com sitemap phase 80 s → 50 s (robots ~20 s + one 30 s timeout), healthy sites unchanged.
|
|
146
|
+
- Tests: `tests/test_sitemap_origin_down_timeout.py` (8).
|
|
147
|
+
|
|
148
|
+
## 5.3.5 — 2026-06
|
|
149
|
+
|
|
150
|
+
### Fixed
|
|
151
|
+
|
|
152
|
+
- **Dirty `Sitemap:` value in robots.txt no longer loses the sitemap (P19).** `SitemapParser._parse_robots_txt`
|
|
153
|
+
took the whole directive value verbatim (`line[8:].strip()`), so `Sitemap: sitemap URL https://site.com/sitemap_index.xml`
|
|
154
|
+
was glued into a broken address and the sitemap was lost. It now extracts the first `https?://\S+` from the value;
|
|
155
|
+
`urljoin` is applied only when the value is a relative path starting with `/`. New `SitemapParser._clean_sitemap_value`.
|
|
156
|
+
- **A 404/410 is never a bot block (P19 side-finding).** `escalation._classify` returns `BotBlockStatus.NONE` for
|
|
157
|
+
status 404/410 right after the authoritative challenge headers. A dead sitemap candidate (or any not-found page) is
|
|
158
|
+
no longer classified as `captcha`/`challenge` and dragged through every tier (http→tls→browser). A real challenge
|
|
159
|
+
uses 403/503, not 404, so the authoritative header check still wins. Invariant: 404 is a definitive "not found"
|
|
160
|
+
from the server, not a page served instead of content. (After N3, verify & lock with a test — see Parser side.)
|
|
161
|
+
- **A redirect inside the same ATS no longer blocks `domain_filter` (P21).** `CrawlCoordinator._handle_redirect_after_scan`
|
|
162
|
+
always allows a redirect to the **same host** (short ATS forms `apply.workable.com/j/X` → `apply.workable.com/<slug>/j/X`).
|
|
163
|
+
For a different host it still asks `domain_filter.is_allowed`, and when that refuses it consults a new host-alias
|
|
164
|
+
extension point `DomainFilter.allow_redirect(source, target)` (driven by a settable `redirect_host_alias` hook) — so
|
|
165
|
+
the Parser can pass its `DOMAIN_POLICIES` and allow `boards.greenhouse.io` → `job-boards.greenhouse.io` without
|
|
166
|
+
opening the whole internet.
|
|
167
|
+
- **Sitemap under a Cloudflare 52x origin error stops without retries (1.6).** On the first `520`–`527` response to
|
|
168
|
+
robots.txt / sitemap, `SitemapParser` stops the sitemap phase and returns `result["origin_down"] = True` (logged as
|
|
169
|
+
`ORIGIN_DOWN`) instead of burning 100–180 s on retries/escalation against a down origin. New
|
|
170
|
+
`SitemapParser._is_origin_down`; `origin_down` is propagated through robots / default / nested sitemap paths.
|
|
171
|
+
- **Nodes evicted by low-memory eviction are no longer lost in a browser full rescan (P23).**
|
|
172
|
+
`Graph.iter_nodes_async(include_evicted=True)` now also yields nodes that were evicted to disk (read-only, without
|
|
173
|
+
pulling them back into RAM), so a merge that iterates the source graph sees them. `Node.pinned` + `Graph.pin_node()`
|
|
174
|
+
/ `Graph.pin_seed_nodes()` keep seed (`depth == 0`) vacancies in RAM — they were the oldest and got evicted first.
|
|
175
|
+
`Graph.eviction_stats` exposes the graph's own eviction counters (a backend-less graph evicts to LMDB/SQLite, which
|
|
176
|
+
`backend.get_stats()` reported as 0).
|
|
177
|
+
- **A 403 storm without vendor markers is treated as a block (P24).** `EscalatingDriver` tracks a per-host window of
|
|
178
|
+
recent HTTP statuses; if a host that recently returned 2xx then returns 403 with no anti-bot markers and either ≥3
|
|
179
|
+
consecutive 403 or >30 % 403 in the window, the 403 is reclassified as `SOFT_BLOCK` (`escalation_reason=http_403_storm`)
|
|
180
|
+
and the tier escalates. A single 403 on a 2xx background is still treated as auth/method (no escalation). New
|
|
181
|
+
`_HostStatusWindow`.
|
|
182
|
+
- **The HTML enrichment pool is priority-aware (2.1 / §15.4).** `run_in_html_pool(fn, *args, priority=...)` admits the
|
|
183
|
+
highest-priority waiter first when the pool is saturated (job-URLs and career subdomains enrich before catalog/blog
|
|
184
|
+
pages); with a free slot and empty queue it behaves exactly like the old FIFO semaphore. `Node` passes its URL-rule
|
|
185
|
+
priority. New `_PrioritySemaphore`.
|
|
186
|
+
- **Cloudflare 520–527 is never a bot block.** `escalation._classify` returns `NONE` for 520–527 (as for 404/410):
|
|
187
|
+
the CF error page embeds `challenge-platform`, so a down origin was classified `challenge` and escalated
|
|
188
|
+
http→tls→browser (~20 s per tier) before `SitemapParser` could stop on `ORIGIN_DOWN`.
|
|
189
|
+
- **robots.txt after a domain move keeps the new host's sitemap.** The same-origin gate and relative `Sitemap:`
|
|
190
|
+
paths now use the host that actually served robots.txt (`final_url`): `kilo.health` → `kilo.co/robots.txt` with
|
|
191
|
+
`Sitemap: … https://kilo.co/sitemap_index.xml` was dropped as cross-origin even after P19 cleaning.
|
|
192
|
+
- **Cancelling a call waiting in the HTML-pool queue resumes the enrich budget.** With the priority semaphore the
|
|
193
|
+
acquire moved out of the `try`, so a cancellation in the queue left the node deadline suspended.
|
|
194
|
+
- **`Node.pinned` does not leak into `user_data['_custom_fields']`** (`NodeMapper`, SQLite eviction storage base
|
|
195
|
+
fields) — it broke DTO/user-data invariants (`test_reserved_user_data_keys`, `test_node_mapper_content_depth`).
|
|
196
|
+
- Tests: `tests/test_v535_fixes.py` (28, offline: P19, 404/52x, 1.6, P21 incl. end-to-end `async_crawl`, P23, P24, 2.1).
|
|
197
|
+
|
|
198
|
+
### Added
|
|
199
|
+
|
|
200
|
+
- `crawl` / `async_crawl` / `GraphCrawlerClient.crawl` / `CrawlerConfig`: `redirect_host_alias=` —
|
|
201
|
+
`Callable[[source_url, target_url], bool]` installed on the spider's `DomainFilter.redirect_host_alias`
|
|
202
|
+
(the extension point was otherwise unreachable through the public API).
|
|
203
|
+
- `SitemapParser._clean_sitemap_value`, `SitemapParser._is_origin_down`; `result["origin_down"]` key.
|
|
204
|
+
- `DomainFilter.allow_redirect(source_url, target_url)` and settable `DomainFilter.redirect_host_alias` hook.
|
|
205
|
+
- `Node.pinned`; `Graph.pin_node`, `Graph.pin_seed_nodes`, `Graph.eviction_stats`; `iter_nodes_async(include_evicted=)`.
|
|
206
|
+
- `run_in_html_pool(..., priority=)`.
|
|
207
|
+
|
|
208
|
+
### Fixed (carried from Unreleased)
|
|
209
|
+
|
|
210
|
+
- **Sitemap `<lastmod>` is no longer dropped.** `SitemapParser` reads `<lastmod>` of every `<url>` (with or without
|
|
211
|
+
namespace) into `result["lastmod"]` (`{url: value}`, merged across robots/default/nested sitemaps), and
|
|
212
|
+
`SitemapProcessor.create_url_nodes(lastmod=...)` stores it on the new `SitemapNode.lastmod` field (W3C datetime as
|
|
213
|
+
in the file). Consumers can now tell a 2021 post from a fresh page; `_parse_urlset` keeps its contract.
|
|
214
|
+
|
|
215
|
+
- **An embedded captcha widget on a page with content is not a bot block.** `classify_block` follows the same
|
|
216
|
+
principle as the browser `CaptchaDetectorPlugin`: a block is a page served *instead of* content. After the
|
|
217
|
+
authoritative challenge headers (`cf-mitigated`, `x-amzn-waf-action`, `x-vercel-mitigated`), a non-blocking
|
|
218
|
+
response whose `<body>` has ≥1000 visible characters or ≥15 links is `NONE`, whatever widget or protection
|
|
219
|
+
script it embeds. Previously visible text was measured over the first 64 KB of the document (in WordPress that is
|
|
220
|
+
almost all `<head>`), and `cf-turnstile` / `challenge-platform` (Cloudflare loads its JSD script on regular pages)
|
|
221
|
+
were unconditional markers — so every page with reCAPTCHA/hCaptcha/Turnstile in a contact or apply form was
|
|
222
|
+
`captcha`, escalated HTTP→TLS and pinned the whole host to the TLS tier. `cf-turnstile`, `captcha-container`,
|
|
223
|
+
`challenge-platform`, `_incapsula_resource`, `awswafintegration` now count only on pages without content or with a
|
|
224
|
+
blocking status; markers and text are read from `<body>` up to 1 MB.
|
|
225
|
+
|
|
226
|
+
## 5.3.4 — 2026-10
|
|
227
|
+
|
|
228
|
+
### Fixed
|
|
229
|
+
|
|
230
|
+
- **The per-node enrichment limit measures active work, not the HTML-pool queue.** `NodeScanner` wraps
|
|
231
|
+
`enrich_node` in `enrich_budget(seconds)` (`graph_crawler.domain.entities.node`): while a call waits for a thread
|
|
232
|
+
of the HTML executor in `run_in_html_pool` (parse, `_update_from_context`, hashes), the deadline is suspended and
|
|
233
|
+
resumed with the same remainder the moment the work starts. With 5.3.3 the hook and the hashes moved into the
|
|
234
|
+
shared pool, so under load a node's wall-clock depended on how many neighbours stood in front of it: a burst of
|
|
235
|
+
large pages produced a cascade of `Per-node timeout … marking as ERROR` for pages whose own processing took a
|
|
236
|
+
fraction of the limit (and every stage re-queued behind the others). A page whose **own** processing exceeds
|
|
237
|
+
`node_enrich_timeout` still times out and still returns its discovered links. The crawl-level timeout remains the
|
|
238
|
+
upper bound for a node that waits in the queue.
|
|
239
|
+
- **`IsolatedExecutor.run` admits at most `max_workers` tasks at a time** (`threading.BoundedSemaphore`): the task
|
|
240
|
+
`timeout` now measures execution in the child process, not the wait behind other tasks. Previously a burst of
|
|
241
|
+
submissions could expire the timeout of a queued task, which killed the whole pool and failed every task in flight.
|
|
242
|
+
|
|
243
|
+
- **Control commands are no longer dropped silently.** `GraphSpider.process_control_messages` executes only
|
|
244
|
+
`SPIDER_COMMANDS` (`STOP`, `PAUSE`, `RESUME`, `REPRIORITIZE`, `FORCE_VISIT`); any other `CrawlCommand` logs
|
|
245
|
+
`WARNING Unsupported control command '<value>' from <sender> is ignored (supported: ...)` once per command type
|
|
246
|
+
per crawl (repeats at `DEBUG`) and the crawl goes on. `REPRIORITIZE` without `url`/`priority` and `FORCE_VISIT`
|
|
247
|
+
without `url` log a `WARNING` with the received `data` instead of doing nothing.
|
|
248
|
+
|
|
249
|
+
### Added
|
|
250
|
+
|
|
251
|
+
- `EnrichBudget` (`pause()` / `resume()`) and `enrich_budget()` in `graph_crawler.domain.entities.node`.
|
|
252
|
+
- `SPIDER_COMMANDS` (`graph_crawler.domain.interfaces`): the commands `GraphSpider` executes.
|
|
253
|
+
|
|
254
|
+
### Deprecated
|
|
255
|
+
|
|
256
|
+
- `CrawlCommand.SKIP_URL`, `CHANGE_STRATEGY`, `SET_DEPTH_LIMIT`, `REQUEST_STATE`, `UPDATE_GOAL` were never executed
|
|
257
|
+
by the spider. They will be removed from the enum in 5.4.0.
|
|
258
|
+
|
|
259
|
+
## 5.3.3 — 2026-10
|
|
260
|
+
|
|
261
|
+
### Added
|
|
262
|
+
|
|
263
|
+
- **`IsolatedExecutor` / `IsolatedTaskError`** (`from graph_crawler import IsolatedExecutor`). Runs CPU-bound functions
|
|
264
|
+
in a `spawn` process pool: a native crash (SIGSEGV in libxml2/lxml, trafilatura, any C extension) or a hang of a
|
|
265
|
+
worker becomes `IsolatedTaskError(reason="crashed" | "timeout" | "unavailable")` in the caller, the pool restarts
|
|
266
|
+
and the crawl goes on. Tasks that shared the broken pool are retried once; a hung worker is killed after
|
|
267
|
+
`timeout` (default `DEFAULT_ISOLATED_TASK_TIMEOUT` = 30 s). Ordinary exceptions of the function propagate as is.
|
|
268
|
+
- **`CrawlerConfig.node_enrich_timeout`** and `crawl(..., node_enrich_timeout=...)` / `async_crawl(...)` /
|
|
269
|
+
`GraphCrawlerClient.crawl(...)`: per-node enrichment limit (HTML parse, plugins, subclass hook, hashes).
|
|
270
|
+
`None` keeps `DEFAULT_NODE_ENRICH_TIMEOUT` (45 s, `shared.constants`). `NodeScanner(driver, enrich_timeout=...)`.
|
|
271
|
+
- `run_in_html_pool(fn, *args)` (`graph_crawler.domain.entities.node`): runs `fn` in the HTML executor under its
|
|
272
|
+
semaphore. `Node.pop_discovered_links()` returns links extracted on `ON_HTML_PARSED` of an interrupted
|
|
273
|
+
`enrich_node`.
|
|
274
|
+
- `URLRuleMatcher.rule_matches(rule, url)`.
|
|
275
|
+
|
|
276
|
+
### Changed
|
|
277
|
+
|
|
278
|
+
- **The `Node._update_from_context` hook and `get_content_hash`/`get_simhash` run in the HTML executor** under the
|
|
279
|
+
same semaphore as HTML parsing (they used the default `asyncio.to_thread` pool, `min(32, cpu+4)` threads, outside
|
|
280
|
+
the CPU budget). Concurrency of heavy subclass code is now bounded by `GRAPH_CRAWLER_HTML_PARSER_WORKERS`
|
|
281
|
+
(default — the cgroup CPU quota); `=1` serialises it completely. Code that is not thread-safe must use its own
|
|
282
|
+
lock or `GRAPH_CRAWLER_HTML_PARSER_WORKERS=1`; native code that can crash — `IsolatedExecutor`.
|
|
283
|
+
- **One URL rule — one decision.** `CrawlScheduler`, `LinkProcessor` and `RuleBasedScheduler` share
|
|
284
|
+
`URLRuleMatcher`: first match in list order, `SmartURLRule` is matched against its `scope` (the scheduler used to
|
|
285
|
+
run `re.search` over the full URL, so `SmartURLRule(r"/help", scope=PATH, should_scan=False)` excluded
|
|
286
|
+
`https://helpware.com/`). `GraphSpider` no longer copies `url_rules` with `should_scan=False` into `PathFilter`
|
|
287
|
+
(it applied them to the path, ignoring order and scope); `excluded_paths` / `included_paths` are unchanged.
|
|
288
|
+
- A URL rule whose `priority` is outside `[MIN_URL_PRIORITY, MAX_URL_PRIORITY]` (`[-100, 100]`) gets a WARNING when
|
|
289
|
+
the scheduler is created: `priority` is a queue position and is clamped; rule precedence is list order.
|
|
290
|
+
- `IDriver.fetch(url, *, referer, extra_headers, cookies)` is the contract of every driver: `BaseDriver`,
|
|
291
|
+
`EscalatingDriver` (caller cookies/headers merged with the host session — the session wins on a cookie name
|
|
292
|
+
clash, the session `User-Agent` is applied on top for HTTP/TLS tiers), `PooledPlaywrightDriver` (`extra_headers`),
|
|
293
|
+
`IAsyncDriver`, `IBrowserDriver`. `BaseDriver.fetch_many` passes per-URL `extra_headers_list` / `cookies_list` to
|
|
294
|
+
`fetch` (they were dropped).
|
|
295
|
+
|
|
296
|
+
### Fixed
|
|
297
|
+
|
|
298
|
+
- **Per-node timeout no longer drops discovered links.** `scan_node` / `scan_batch` return the links extracted on
|
|
299
|
+
`ON_HTML_PARSED` when enrichment exceeds the limit; the node is still `ERROR` / `FAILED`. Large listing pages, the
|
|
300
|
+
first to hit the limit, keep their edges.
|
|
301
|
+
|
|
302
|
+
## 5.3.2 — 2026-06
|
|
303
|
+
|
|
304
|
+
### Added
|
|
305
|
+
|
|
306
|
+
- **Fetch provenance on the node.** `Node.channel`, `bot_block_status`, `served_by_driver`, `served_by_engine`,
|
|
307
|
+
`escalations` and `fetch_info` read `metadata["fetch"]`; `Node.ok` (scanned, 2xx/3xx, not blocked, no error) and
|
|
308
|
+
`Node.is_blocked` separate useful pages from block and error pages.
|
|
309
|
+
- **Plugin routing.** `BaseNodePlugin.domains` (domain and subdomains) and `url_pattern` (regex), on the class or via
|
|
310
|
+
`config={"domains": [...], "url_pattern": ...}`, plus an overridable `applies_to(url)`. An invalid `url_pattern`
|
|
311
|
+
raises `ConfigurationError` on `register()`. Several instances of one plugin class with different routes run
|
|
312
|
+
side by side (they used to replace each other).
|
|
313
|
+
- **Typed plugin output.** `NodePluginContext.set_node_field(name, value)` writes a field declared by a `Node`
|
|
314
|
+
subclass, validated against its annotation (`"100"` → `int`); base fields are read-only. Fields are rolled back
|
|
315
|
+
together with `metadata`/`user_data` when the plugin fails.
|
|
316
|
+
- **Empty extraction warning.** `BaseNodePlugin.expects_nonempty` + `extraction_key`: a WARNING when the plugin left
|
|
317
|
+
`user_data[extraction_key]` empty.
|
|
318
|
+
- **`Graph.search(..., user_data_keys=("jobs",))`** searches the listed `user_data` keys as well.
|
|
319
|
+
- **`Node.public_user_data`** and `RESERVED_USER_DATA_KEYS` (`_link_anchors`, `_custom_fields`, `_node_class`).
|
|
320
|
+
|
|
321
|
+
### Changed
|
|
322
|
+
|
|
323
|
+
- A response with status 400 or higher gets `metadata["error"] = "HTTP <code>"` and is logged as a WARNING
|
|
324
|
+
(`HTTP 404 for <url> — page not scanned`).
|
|
325
|
+
- A bare 403/401/405 without vendor markers is still classified as `none`; the decision is now logged at INFO.
|
|
326
|
+
- Quieter logs: import writes nothing at INFO, platform detection runs once, the start of a crawl is one line,
|
|
327
|
+
and each page is one INFO line with `[channel=…]`, `[block=…]`, `[error=…]`.
|
|
328
|
+
- The temporary `_link_anchors` map is removed from the node once its edges are created (the anchor text stays in
|
|
329
|
+
`edge.metadata["anchor_text"]`). The default JSON export writes `public_user_data`.
|
|
330
|
+
- A third-party plugin that overwrites a reserved `user_data` key gets a WARNING.
|
|
331
|
+
|
|
332
|
+
### Fixed
|
|
333
|
+
|
|
334
|
+
- `load_graph()` without `node_class=` restores a custom `Node` subclass when its module is already imported (the
|
|
335
|
+
class name is stored with the node; nothing is imported from the file). `Graph.copy()`, `union`, `intersection`,
|
|
336
|
+
`difference` and nodes created in a loaded graph keep the node class.
|
|
337
|
+
- `metadata` written by a plugin on `ON_NODE_CREATED` or `ON_AFTER_SCAN` is no longer lost.
|
|
338
|
+
- **Placeholder lists are detected as a JavaScript shell.** A 2xx body with five or more `placeholder` /
|
|
339
|
+
`skeleton` / `shimmer` elements makes `needs_browser` true, so `escalating` goes straight to the browser tier.
|
|
340
|
+
With `driver="http"` the node gets `metadata["js_shell_suspected"]` and a WARNING (once per host) suggests
|
|
341
|
+
`driver="escalating"` or `"playwright"`.
|
|
342
|
+
- **No duplicate edges.** A link repeated on a page (menu + footer + body) created a new `Edge` each time
|
|
343
|
+
(17.5 % of edges on uk.wikipedia); `LinkProcessor` now skips existing `(source, target)` pairs. `Graph.has_edge`
|
|
344
|
+
also sees the backend edge index.
|
|
345
|
+
- **`h1` children are no longer joined with spaces** (`Портал : Поточні події` → `Портал:Поточні події`).
|
|
346
|
+
- **Default ports are normalized** (`http://x:80`, `https://x:443`, empty port).
|
|
347
|
+
- `Graph.normalize_url()` is public; `LinkProcessor` checks the scheduler with the canonical form as well.
|
|
348
|
+
- **`NetworkLogPlugin` waits for network quiet** before publishing the log (`settle_ms`=2000, `quiet_ms`=300;
|
|
349
|
+
`settle_ms=0` restores the old behaviour) — late XHR (search/listing after hydration) was lost.
|
|
350
|
+
- `create_trailing_slash_rule` keeps the root path; an empty path is `/` for PATH-scope rules.
|
|
351
|
+
|
|
352
|
+
## 5.3.1 — 2026-06
|
|
353
|
+
|
|
354
|
+
### Added
|
|
355
|
+
|
|
356
|
+
- **Hydration state globals snapshot.** The Playwright driver serialises `window.__NAME__` objects (`__NUXT__`,
|
|
357
|
+
`__APOLLO_STATE__`, `__INITIAL_STATE__`, `__PINIA__`, …) with `JSON.stringify` after the page has run and embeds
|
|
358
|
+
them into the HTML snapshot as `<script type="application/json" data-agc-state="__NAME__">`. Nuxt 2 ships its
|
|
359
|
+
state as an IIFE (`window.__NUXT__=(function(a,b){return {…}}(…))`) which is JavaScript, not JSON, and was
|
|
360
|
+
invisible to any static state parser. Globals that already exist as a JSON `<script id="__NAME__">`
|
|
361
|
+
(`__NEXT_DATA__`) are not duplicated; `PageFetcher.state_globals_max_chars` (5 MB) caps the snapshot.
|
|
362
|
+
`inject_state_globals(html, states)` is exported for tests and other drivers.
|
|
363
|
+
|
|
364
|
+
## 5.3.0 — 2026-06
|
|
365
|
+
|
|
366
|
+
### Added
|
|
367
|
+
|
|
368
|
+
- **Page actions as public API.** `driver_config={"actions": [PageAction(...) | dict, ...]}` for the Playwright driver:
|
|
369
|
+
`click` / `fill` / `select` / `press` / `wait_for(response=…|selector=…)` / `scroll`, target by `selector`, `text`,
|
|
370
|
+
`placeholder` or `label` (`nth` among visible matches). Actions are validated when the driver is created and run
|
|
371
|
+
after content is ready and before the HTML snapshot and `CONTENT_READY` hooks — the network log is published after
|
|
372
|
+
the actions. Results land in `node.metadata["actions_log"]`, `["requests_after_actions"]` and `["actions_error"]`
|
|
373
|
+
(the first failing action stops the chain with a reason). `from graph_crawler import PageAction`.
|
|
374
|
+
- `engine.page_error_types()` — page error classes of all installed browser SDKs.
|
|
375
|
+
- `PageAction(type="back")`; action errors name the element that intercepts pointer events.
|
|
376
|
+
|
|
377
|
+
### Fixed
|
|
378
|
+
|
|
379
|
+
- Binary request bodies (gzip beacons) no longer raise `UnicodeDecodeError` inside Playwright event listeners
|
|
380
|
+
(`NetworkLogPlugin` and the action recorder read `post_data_buffer` with `errors="replace"`); the error used to break
|
|
381
|
+
the next Playwright call with `TypeError: function takes exactly 5 arguments`.
|
|
382
|
+
|
|
383
|
+
### Fixed
|
|
384
|
+
|
|
385
|
+
- `graph.get_node_by_url(seed)` finds the node after a redirect in the graph returned by `async_crawl`: `add_node`
|
|
386
|
+
restores the alias from `metadata["was_redirected_from"]`.
|
|
387
|
+
|
|
388
|
+
## 5.2.2 — 2026-06
|
|
389
|
+
|
|
390
|
+
### Fixed (test infrastructure)
|
|
391
|
+
|
|
392
|
+
- **The library no longer configures logging.** `GraphCrawlerClient.__init__` called `logging.basicConfig(INFO)`
|
|
393
|
+
when its logger had no handlers, so every crawl printed to stderr and reconfigured the application's root
|
|
394
|
+
logger. Removed; `graph_crawler` gets a `NullHandler`.
|
|
395
|
+
- **Noise demoted:** `HTML executor shutdown` and `lmdb not installed … Falling back to SQLiteEvictionStorage` are
|
|
396
|
+
DEBUG; `channel='chrome' not installed, falling back to bundled Chromium` is a WARNING once per process.
|
|
397
|
+
- **One version source.** `graph_crawler.__version__` (and the default User-Agent) come from the installed
|
|
398
|
+
package metadata (`pyproject.toml`); `__version__.py` no longer hard-codes a number that drifted (5.1.0 vs 5.2.0).
|
|
399
|
+
- **Test suite collects without optional extras.** Tests that need `psutil`, `rapidfuzz`, `stop-words`,
|
|
400
|
+
`html2md-clean` or `playwright` are skipped via `importorskip` instead of failing collection under `-x`.
|
|
401
|
+
`test_div_inline_fix.py` / `test_phase1_fixes.py` tested `html2md_clean` itself (no `graph_crawler` code) and were
|
|
402
|
+
removed from the core suite.
|
|
403
|
+
- GitLab CI (`.gitlab-ci.yml`): offline suite with `[dev]` and a minimal job without extras.
|
|
404
|
+
|
|
405
|
+
## 5.2.1 — 2026-06
|
|
406
|
+
|
|
407
|
+
### Fixed (freelance cases)
|
|
408
|
+
|
|
409
|
+
- **Captured XHR/fetch traffic could not be replayed outside the browser.** `NetworkLogPlugin` recorded the
|
|
410
|
+
URL and method of a request but not its body, so every POST/GraphQL endpoint (robota.ua lists vacancies via
|
|
411
|
+
`POST dracula.robota.ua/?q=getPublishedVacanciesList`) was unusable: a GET of the same URL returns 400.
|
|
412
|
+
Records now carry `post_data` and (on `response`) `request_headers`.
|
|
413
|
+
- **GraphQL responses overwrote each other.** `body_ref` was `sha1(url|status|content-type)` — all operations
|
|
414
|
+
POSTed to one `/graphql` URL shared a key and only the last body survived. The key now includes method and
|
|
415
|
+
request body.
|
|
416
|
+
- **JSON family by default.** The capture allow-list matched exact MIME types only; `application/graphql-
|
|
417
|
+
response+json`, `problem+json`, `vnd.api+json`, `text/json` were dropped. Without an explicit
|
|
418
|
+
`capture_content_types` every `*/json` and `*+json` type is captured (RFC 6839). Default `max_body_kb` 64 → 256
|
|
419
|
+
(listing APIs routinely exceed 64 KB).
|
|
420
|
+
- **Shadow DOM was invisible.** `page.content()` serializes only light DOM: content of open shadow roots (web
|
|
421
|
+
components, embedded price widgets) never reached the node. When a page has open shadow roots, the browser
|
|
422
|
+
driver serializes it with `Element.getHTML({shadowRoots})` and expands the declarative shadow roots into their
|
|
423
|
+
hosts (`shared/utils/shadow_dom.expose_shadow_roots`) — Lexbor, like the HTML spec, does not descend into
|
|
424
|
+
`<template>` content, so a plain declarative-shadow-DOM serialization would still hide it from selectors and link
|
|
425
|
+
extraction. Pages without shadow roots are untouched.
|
|
426
|
+
- `tests/unit/domain/entities/test_graph_new_features.py` still expected the `DeprecationWarning` that
|
|
427
|
+
`export_nodes()` stopped emitting in 4aab7525; it now asserts the opposite.
|
|
428
|
+
|
|
429
|
+
### Fixed (real-site tests)
|
|
430
|
+
|
|
431
|
+
- **Ctrl+C during `crawl()` hung until the crawl-level timeout** (28 s with `timeout=25`; forever without
|
|
432
|
+
one). `asyncio.wait_for` in Python 3.11 swallows the outer cancellation when the awaited coroutine finishes
|
|
433
|
+
at the same moment, so some workers survived `KeyboardInterrupt` and kept crawling. `NodeScanner` and the
|
|
434
|
+
escalation tier budget now use `asyncio.timeout()`; `CrawlCoordinator` cancels its workers on
|
|
435
|
+
`CancelledError`. Ctrl+C now returns in ~0.3 s.
|
|
436
|
+
- **`same_domain=False` was ignored** — without `allowed_domains` the `DomainFilter` default
|
|
437
|
+
(`domain+subdomains`) still applied and external links never entered the graph. Now maps to `allowed_domains=["*"]`.
|
|
438
|
+
- **`graph.metadata["stop_reason"]` was `None` after `timeout=` / `max_pages`** — only stop conditions were
|
|
439
|
+
recorded. Now `"timeout"`, `"max_pages"` or `"limit"`.
|
|
440
|
+
- **Network errors were invisible on the node**: a DNS/connection failure left `response_status=None`,
|
|
441
|
+
`scanned=True` and no message; `get_failed_nodes()` returned `[]`. The error text is now in
|
|
442
|
+
`node.metadata["error"]`, the HTTP status (if any) is kept, and `get_failed_nodes()` with default
|
|
443
|
+
arguments includes network-error nodes.
|
|
444
|
+
- **`storage="json", storage_config={"path": "site.json"}` was silently ignored** (file written to
|
|
445
|
+
`crawler_data/json/graph.json`). `path` is now honoured, like for sqlite (`JSONStorage(file_name=)`).
|
|
446
|
+
- `Graph.export_nodes()` / `Graph.export_edges()` are no longer deprecated: they are the public one-liners the
|
|
447
|
+
README shows and now delegate to `GraphExportUseCase` silently (no `DeprecationWarning`).
|
|
448
|
+
- `tests/manual/real_sites/run_all.py -j N` runs scenarios in parallel; `tests/manual/real_sites/test_user_cases.py`
|
|
449
|
+
— 23 pytest user scenarios on Wikipedia / robota.ua / GitHub / example.com, each hard-capped at 40 s.
|
|
450
|
+
|
|
451
|
+
### Changed
|
|
452
|
+
|
|
453
|
+
- **Engine contract verified end-to-end**. `tests/transport/
|
|
454
|
+
test_engine_contract.py` drives the real `PlaywrightDriver.fetch()` through three engines registered via the
|
|
455
|
+
public `register_engine()` on a recording fake SDK: the call sequence is byte-for-byte identical across
|
|
456
|
+
engines, stealth is injected iff `provides_stealth is False`, and `executable_path`/`ignore_default_args`/
|
|
457
|
+
launch args come only from the engine. No runtime code changed.
|
|
458
|
+
- **`LexborDocument.clean_text()` no longer re-parses the page**. To strip `script/style/noscript/svg/head` without mutating the shared tree it
|
|
459
|
+
serialised the document and built a second `LexborHTMLParser` — i.e. every page with the default
|
|
460
|
+
`TextExtractorPlugin` was parsed twice (`count_parses.py`: 2.0 → **1.0** per page; with `MarkdownPlugin`
|
|
461
|
+
1 parse + 1 clone). It now uses `tree.clone()`.
|
|
462
|
+
- **One `ContentDecoder` for every HTTP tier**. `AsyncDriver` (aiohttp) and `AsyncCurlCffiDriver` (curl_cffi) no longer rely on the client's own
|
|
463
|
+
`response.text`/`.text()`: they read the raw body and decode it through `ContentDecoder`
|
|
464
|
+
(`Content-Type` charset → `<meta charset>` → BOM → chardet → strict utf-8). Pages whose charset is only
|
|
465
|
+
declared in `<meta>` (e.g. windows-1251 without a header) were previously decoded as utf-8 by aiohttp,
|
|
466
|
+
failed, and silently became `html=None` with a "successful" 200. An undecodable body is now reported as
|
|
467
|
+
`FetchResponse.error` (`"ContentDecoder: cannot decode body …"`) with the status code preserved;
|
|
468
|
+
`ConnectionPoolManager.fetch_text()` goes through the same decoder. `FetchResponse.html` is a strict
|
|
469
|
+
`str` field — passing `bytes` raises `ValidationError` instead of being coerced. Browser tiers
|
|
470
|
+
(Playwright `page.content()`, nodriver `tab.get_content()`) already return `str`. Contract test:
|
|
471
|
+
`tests/transport/test_html_is_str_contract.py` (driver matrix returns `str`; cp1251 via meta/header;
|
|
472
|
+
binary → error).
|
|
473
|
+
- **Lazy public facade**. `graph_crawler/__init__.py` now
|
|
474
|
+
eagerly imports only `crawl`, `async_crawl`, `Graph`, `Node`, `save_graph`, `load_graph`,
|
|
475
|
+
`BaseStopCondition`; every other name in `__all__` (drivers, storages, exceptions, settings, views,
|
|
476
|
+
celery tasks, …) resolves on first access via module `__getattr__` and is cached. `aiohttp` is no longer
|
|
477
|
+
loaded by `import graph_crawler`. `bootstrap()` no longer calls `collect_system_metrics()`
|
|
478
|
+
(`psutil.cpu_percent(interval=0.1)` blocked import for 100 ms for a DEBUG line). Import time here:
|
|
479
|
+
470 ms → 228 ms. Optional names (`PlaywrightDriver`, `celery`, `EasyDistributedCrawler`, …) still return
|
|
480
|
+
`None` without their extra.
|
|
481
|
+
- **Escalation re-probe replays the solving tier's User-Agent**.
|
|
482
|
+
Playwright and nodriver now export `navigator.userAgent` as `extras["user_agent"]`;
|
|
483
|
+
`SessionState.absorb` records it (pinned once the session is solved, like `impersonate`/`proxy`) and
|
|
484
|
+
`EscalatingDriver._fetch_tier` sends it as `extra_headers={"User-Agent": …}` to HTTP/TLS tiers together
|
|
485
|
+
with the stored cookies. `cf_clearance` is bound to UA+IP+JA4, so a re-probe on Tier 1 with aiohttp's
|
|
486
|
+
default UA was rejected and the host never descended from the browser tier.
|
|
487
|
+
|
|
488
|
+
### Removed
|
|
489
|
+
|
|
490
|
+
- **`NodePluginContext.parser`**. Node plugins now read the
|
|
491
|
+
single Lexbor tree through the `HTMLDocument`/`HTMLNode` protocols: `LinkExtractorPlugin`
|
|
492
|
+
(`doc.css("a[href]")`, `doc.base_href()`), `MetadataExtractorPlugin`, `TextExtractorPlugin`
|
|
493
|
+
(`doc.clean_text()`, new in the protocol), `StructuredDataPlugin` (OG/Twitter/Microdata/RDFa parsers take
|
|
494
|
+
an `HTMLDocument`; `StructuredDataExtractor.extract(html=, doc=)`), `Email`/`PhoneExtractorPlugin`.
|
|
495
|
+
Migration: `context.parser.find_all(sel)` → `context.html_tree.css(sel)`, `.find()` → `.css_first()`,
|
|
496
|
+
`el.get_attribute()` → `el.attr()`. Fixed along the way in `LexborNode`: `attr()` returns `""` (not
|
|
497
|
+
`None`) for valueless boolean attributes such as `itemscope`, so nested Microdata/RDFa items are now
|
|
498
|
+
detected; `css()`/`css_first()` return descendants only (selectolax included the node itself, which made
|
|
499
|
+
Microdata recurse to `max_nesting_depth`).
|
|
500
|
+
- **`PooledPlaywrightDriver` legacy batch path**: `batch_legacy.py`
|
|
501
|
+
(`LegacyBatch._fetch_batch_legacy`, `_fetch_chain`) is gone. `persistent_browser=False` keeps its
|
|
502
|
+
"launch/stop per batch" semantics through the pool path: after a batch, once no fetch is in flight, the
|
|
503
|
+
pool and the engine session are closed (`_close_pool()` + `_stop_persistent_session()`, shared with
|
|
504
|
+
`close()`). Dead `self.playwright`/`self.browsers` cleanup removed from `close()`.
|
|
505
|
+
- **Escalation.** `classify_block()` rewritten to the
|
|
506
|
+
6.0 decision table: challenge/captcha vendor headers first, then unambiguous body captcha/challenge
|
|
507
|
+
markers (ahead of vendor hard-block headers), then short/blocking-page widgets, status-based verdicts
|
|
508
|
+
and empty-2xx. A bare `401/403/405` with no vendor markers is no longer a block. On the classifier corpus
|
|
509
|
+
it goes 25/37 → **37/37**.
|
|
510
|
+
|
|
511
|
+
- `needs_browser` heuristic (SPA shell: little visible text + SPA root / `<noscript>` JS hint / no
|
|
512
|
+
`<a href>`) is now the default `EscalationPolicy.needs_browser`; on a clean HTTP/TLS response it jumps
|
|
513
|
+
straight to the first browser tier (`escalation_reason="needs_browser"`).
|
|
514
|
+
- Per-tier `TierTimeouts` + `escalation_deadline_s` (60s) budget: a tier is skipped (last response
|
|
515
|
+
returned, browser never started) when the remaining budget is below its `min_budget`; each tier fetch
|
|
516
|
+
is wrapped in `asyncio.wait_for`. New `PER_NODE_FETCH_TIMEOUT` constant with a validator warning.
|
|
517
|
+
- Cross-tier session: `SessionState` / `HostSessionStore` carry solved cookies (`cf_clearance`, `__cf_bm`,
|
|
518
|
+
`datadome`, …) forward and back down to cheaper tiers on re-probe; `impersonate`/`proxy` are pinned once
|
|
519
|
+
a session is solved. curl_cffi pins the impersonate fingerprint; Playwright/nodriver export context
|
|
520
|
+
cookies via `extras["cookies"]`.
|
|
521
|
+
- `extras` contract: every driver stamps `channel`/`driver`/`engine` (via `BaseDriver`/`BaseAsyncDriver`
|
|
522
|
+
`_stamp_extras` + `_finalize_response`) even outside escalation.
|
|
523
|
+
- New Prometheus metrics `crawl_fetch_duration_seconds{channel,engine}`, `crawl_escalations_total`,
|
|
524
|
+
`crawl_escalation_deadline_hits_total`.
|
|
525
|
+
|
|
526
|
+
- **HTML parsing: Lexbor (selectolax) is the only core parser**.
|
|
527
|
+
`beautifulsoup4`, `lxml`, `lxml_html_clean` removed from `[project].dependencies`;
|
|
528
|
+
`BeautifulSoupAdapter`, `LxmlAdapter`, `ScrapyAdapter` deleted. New `HTMLDocument` / `HTMLNode`
|
|
529
|
+
protocols in `domain/value_objects/html_document.py`, implemented by `LexborDocument` in
|
|
530
|
+
`infrastructure/adapters/selectolax_adapter.py`. `<template>` is renamed to `gc-template` before
|
|
531
|
+
parsing so Vue/React SSR links inside templates are not lost. `IParser.parse()` returns `HTMLDocument`;
|
|
532
|
+
`NodePluginContext.html_tree` is now a `LexborDocument`. `get_parser()` accepts `auto|selectolax|lexbor`.
|
|
533
|
+
- `HTMLUtils.extract_links/extract_text/extract_metadata` accept an `HTMLDocument` (string input still
|
|
534
|
+
works but is deprecated).
|
|
535
|
+
- `SitemapParser.parse_large_sitemap_streaming()` uses stdlib `iterparse` by default; lxml is an
|
|
536
|
+
optional fast-path via the new extra `sitemap-fast`.
|
|
537
|
+
|
|
538
|
+
- **`html2md-clean` moved to the optional extra `markdown`** (`pip install "graph-crawler[markdown]"`);
|
|
539
|
+
`MarkdownGenerator/MarkdownOptions/MarkdownResult` re-exports in `graph_crawler.shared.utils` are lazy.
|
|
540
|
+
`MarkdownPlugin` feeds the already-parsed Lexbor tree to html2md-clean 0.5 (`generate_from_tree`) —
|
|
541
|
+
one parse per page instead of 2–4. `import graph_crawler` no longer loads bs4/lxml.
|
|
542
|
+
|
|
543
|
+
- **Dependency map**. Core is now 10 packages: `requests`,
|
|
544
|
+
`httpx`, `pydantic-settings`, `pybloom-live`, `fastapi`, `tenacity`, `stop-words`, `tiktoken`,
|
|
545
|
+
`markdown-it-py` removed from `[project].dependencies`. New extras: `browser` (`playwright` kept as alias),
|
|
546
|
+
`server` (fastapi/uvicorn/websockets), `text` (tiktoken/stop-words), `captcha`; `markdown` also pulls
|
|
547
|
+
`markdown-it-py`; `all` is recursive. Extras `cloudscraper` and `undetected-sync` dropped.
|
|
548
|
+
Extra-gated imports are lazy and fail fast with `requires extra '<name>'`; `transport.PlaywrightDriver/
|
|
549
|
+
RequestsDriver/CloudscraperDriver/RequestsSessionAdapter` are lazy re-exports (ImportError instead of `None`).
|
|
550
|
+
`CrawlerSettings` keeps `GC_*`/`__`/`.env` without pydantic-settings; `CrawlerConfig` is a plain `BaseModel`.
|
|
551
|
+
|
|
552
|
+
- **Transport cleanup**. Deleted: sync drivers
|
|
553
|
+
(`RequestsDriver`, `CloudscraperDriver`, `UndetectedChromeDriver`, `BotasaurusDriver`, `BaseSyncDriver`),
|
|
554
|
+
`AsyncDriverHTTP2` (httpx), `AsyncCloudscraperDriver`, `tls_client.py`, `StealthHTTPDriver`,
|
|
555
|
+
`session_manager/session_adapters/session_protocol`, `shared/utils/captcha_bypass.py`,
|
|
556
|
+
`shared/utils/proxy_manager.py`, packages `transport/http` and `transport/browser`.
|
|
557
|
+
`DriverType` is now `aiohttp | tls | playwright | nodriver | escalating` (+ `asyncio` alias);
|
|
558
|
+
strings `http`/`async`/`asyncio` → `aiohttp` and `stealth`/`curl_cffi` → `tls` are accepted for one
|
|
559
|
+
release with `DeprecationWarning`. One `AsyncDriver` (former `driver_adaptive.py` is now
|
|
560
|
+
`async_http/driver.py`); request hints (`referer`, `extra_headers`, `cookies`) are honoured.
|
|
561
|
+
`requests` is gone from the package: proxy health-check, CAPTCHA solvers/bypass and `SitemapParser`
|
|
562
|
+
use aiohttp (`SitemapParser.parse_sitemap/parse_from_robots` are `asyncio.run` wrappers,
|
|
563
|
+
`http_client=` is deprecated and ignored). `BloomFilter` is pure-python (xxhash) — `pybloom-live`
|
|
564
|
+
no longer needed.
|
|
565
|
+
`import graph_crawler` no longer loads any extra (guarded by `tests/unit/test_import_surface.py`), 3.8 s → 1.7 s here.
|
|
566
|
+
`shared/constants.py` no longer imports aiohttp — `br`/`zstd` are advertised
|
|
567
|
+
by `transport.compression.runtime_browser_headers()` only when the runtime can decode them;
|
|
568
|
+
one driver registry — `application.services.driver_factory` delegates to
|
|
569
|
+
`transport.factory.DriverFactory` (`register_factory`, `registered_names`); `SitemapParser(driver=)`
|
|
570
|
+
fetches robots.txt/sitemaps through the crawl `IDriver` (same tier chain), `SitemapSpider` passes its
|
|
571
|
+
driver, aiohttp is used directly only for binary `.gz`; `tests/transport/test_all_drivers_report_redirects.py`
|
|
572
|
+
is an `IDriver` acceptance matrix (aiohttp / tls / fake playwright / fake nodriver / escalating).
|
|
573
|
+
|
|
574
|
+
- **Escalation**: `EscalationPolicy.reprobe_interval` (5 min) —
|
|
575
|
+
one request per interval re-probes tier 0 for a host remembered on a higher tier (no herd of browser
|
|
576
|
+
re-escalations after TTL); `EscalationPolicy.max_hosts` (10k) — `HostTierMemory` is an LRU and drops
|
|
577
|
+
per-host locks together with entries. In strict mode (`GC_ENGINE_STRICT`) an `ImportError` raised _during_
|
|
578
|
+
`fetch` (lazy browser start without the SDK) now propagates instead of being turned into a network error.
|
|
579
|
+
|
|
580
|
+
- **`extras` contract persisted.** New
|
|
581
|
+
`FetchResponse.fetch_provenance()` / `FETCH_EXTRAS_KEYS`: a JSON-safe slice
|
|
582
|
+
(`bot_block_status, channel, driver, engine, escalations` + `escalation_reason`; cookies/impersonate dropped).
|
|
583
|
+
`NodeScanner` stores it in `node.metadata["fetch"]` for every response, including blocked/error ones, so it
|
|
584
|
+
survives JSON/SQLite/MongoDB/PostgreSQL storage. Contract test `tests/transport/test_extras_contract.py`.
|
|
585
|
+
|
|
586
|
+
### Fixed
|
|
587
|
+
|
|
588
|
+
- Transport `extras` (`bot_block_status`, tier, escalations) were dropped after fetch and never reached the
|
|
589
|
+
node or storage; for blocked pages nothing was transferred at all.
|
|
590
|
+
- `SmartPageFinder` read `html_tree.text` as a property; with Lexbor it is a method — page text was
|
|
591
|
+
silently dropped.
|
|
592
|
+
- `AsyncUndetectedDriver` accepts a plain `dict` config (`AsyncUndetectedConfig.from_mapping`) — building
|
|
593
|
+
the `nodriver` tier through `DriverFactory.create(DriverType.NODRIVER, config={...})` (as `EscalatingDriver`
|
|
594
|
+
does) raised `AttributeError`.
|
|
595
|
+
|
|
596
|
+
## 5.2.0 — 2026-06
|
|
597
|
+
|
|
598
|
+
### Added
|
|
599
|
+
|
|
600
|
+
- `infrastructure/transport/escalation.py`: `classify_block()` (Cloudflare / Akamai / DataDome /
|
|
601
|
+
PerimeterX / Incapsula markers, 403/429/503, empty 200), `EscalationPolicy`, `EscalationTier`,
|
|
602
|
+
`EscalatingDriver` — chain http → tls → browser → undetected with `max_tier`.
|
|
603
|
+
`driver="escalating"` works in `crawl()` / `async_crawl()` / `Crawler` / CLI.
|
|
604
|
+
- `HostTierMemory`: adaptive per-host tier memory — TTL from the moment of decision
|
|
605
|
+
(`GC_TIER_TTL`, default 1200 s), invalidation when the remembered tier gets blocked,
|
|
606
|
+
one escalation per host at a time (per-host lock). Per process.
|
|
607
|
+
- Strict engine mode: `GC_ENGINE_STRICT=1`, `{"browser_engine": {"strict": True}}` or
|
|
608
|
+
`driver_config={"engine_strict": True}` — `ImportError` instead of a silent fallback
|
|
609
|
+
`cloakbrowser → patchright → playwright` or a skipped escalation tier.
|
|
610
|
+
- Browser engine registry in `playwright/engine.py`: `register_engine()`, `resolve_engine()`,
|
|
611
|
+
`registered_engines()`. Built-in engines `cloakbrowser` → `patchright` → `playwright` with
|
|
612
|
+
automatic fail-open. `GC_BROWSER_ENGINE` env selects the engine when config has none.
|
|
613
|
+
`PlaywrightBrowserEngine.effective_name` / `fallback_reason`.
|
|
614
|
+
- `FetchResponse.extras` contract: `bot_block_status`, `channel`, `driver`, `engine`, `escalations`.
|
|
615
|
+
- `DriverType.TLS`, `NODRIVER`, `UNDETECTED`, `BOTASAURUS`, `ESCALATING`; `DriverFactory.create()`
|
|
616
|
+
accepts extra constructor kwargs (`policy=`).
|
|
617
|
+
- Extras: `tls`, `stealth`, `cloak`, `undetected`, `undetected-sync`, `antidetect`, `cloudscraper`, `system`.
|
|
618
|
+
- `README.md`, `CHANGELOG.md`.
|
|
619
|
+
|
|
620
|
+
### Changed
|
|
621
|
+
|
|
622
|
+
- `normalize_protocol` defaults to `False`: `http://` and `https://` are different URLs.
|
|
623
|
+
The crawler follows the real redirect instead of rewriting the scheme itself, so
|
|
624
|
+
http-only hosts stay reachable. Enable it explicitly to merge both schemes on dedup.
|
|
625
|
+
- `Graph.handle_redirect()` stores the alias as the real `Location` the server sent
|
|
626
|
+
(provenance); the normalized form is used only to find and merge nodes.
|
|
627
|
+
- Single `DriverType` enum (`transport/base.py`); `factory.py` re-exports it.
|
|
628
|
+
- `cloudscraper` moved from required dependencies to extra `cloudscraper`.
|
|
629
|
+
- `psutil` is optional (`system` extra); `MemoryGuard` imports it lazily.
|
|
630
|
+
- `requires-python = ">=3.11"` (upper bound removed; classifiers 3.11–3.14).
|
|
631
|
+
- `pytest` runs offline by default (`-m 'not manual'`, no coverage in `addopts`).
|
|
632
|
+
Network / real-site tests live in `tests/manual/` and run with `pytest -m manual tests/manual`.
|
|
633
|
+
- Project URLs point to the GitLab repository.
|
|
634
|
+
|
|
635
|
+
### Fixed
|
|
636
|
+
|
|
637
|
+
- `pooled_driver`: bounded walk over `redirected_from` (infinite loop on objects without a
|
|
638
|
+
`None` terminator); cookies are no longer added twice to a fresh browser.
|
|
639
|
+
- Tests synced with the code: redirect aliases, `html2md-clean` 0.4.0 API, `crawl_coordinator`
|
|
640
|
+
`per_host_max_in_flight`, `.gitignore` lookup at the repository root, `tools/db_cleanup.py`
|
|
641
|
+
CLI allow-listed for `print`. Playwright fake tests skip when `playwright` is not installed.
|
|
642
|
+
|
|
643
|
+
### Removed
|
|
644
|
+
|
|
645
|
+
- `tests/345.py`, `tests/ДОДАТКОВІ_ТЕСТИ.py`, `tests/manual_4a_games_test.py`, `tests/google_maps_results.json`.
|
|
646
|
+
- `EXTRACTION_AUDIT.md` and other analysis / plan documents that described work, not the code.
|
|
647
|
+
- `graph_crawler/data/ARCHITECTURE.md`, `MIGRATION_50_STEPS.md`, `REFACTORING_PLAN.md`, `mkdocs.yml`
|
|
648
|
+
(finished plans / config without content).
|
|
649
|
+
|
|
650
|
+
## 5.1.0
|
|
651
|
+
|
|
652
|
+
Previous release (sync-first API, Playwright pool, adaptive HTTP drivers, storage backends).
|