graph-crawler 5.4.1__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.1 → graph_crawler-5.4.2}/CHANGELOG.md +15 -0
- {graph_crawler-5.4.1/graph_crawler.egg-info → graph_crawler-5.4.2}/PKG-INFO +1 -1
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/crawl_coordinator.py +65 -1
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/worker_pool.py +4 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/page_fetch.py +40 -8
- {graph_crawler-5.4.1 → graph_crawler-5.4.2/graph_crawler.egg-info}/PKG-INFO +1 -1
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/pyproject.toml +1 -1
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/LICENSE +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/MANIFEST.in +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/README.md +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/__version__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/ai/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/_core.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/_distributed.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/_shared.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/_sitemap_distributed.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/async_.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/cli.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/client/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/client/client.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/history_manager.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/project_init.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/server/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/server/crawl_monitor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/server/dashboard.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/server/rest_api.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/server/webhooks.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/server/websocket_manager.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/stats_collector.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/sync.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/bootstrap.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/context/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/context/dependency_registry.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/context/graph_context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/context/merge_context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/edge_dto.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/graph_dto.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/mappers/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/mappers/edge_mapper.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/mappers/graph_mapper.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/mappers/node_data_codec.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/mappers/node_mapper.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/node_dto.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/utils.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/application_container.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/driver_factory.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/base_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/csv_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/edge_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/excel_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/jsonl_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/node_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/parquet_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/sql_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/finalize.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/low_memory_graph_saver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/resource_budget.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/scheduling/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/scheduling/rule_based_scheduler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/storage_factory.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/adaptive_throttler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/async_bridge.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/base_spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/celery_batch_spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/celery_spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/checkpoint.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/dead_letter_queue.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/domain_rate_limiter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/filters/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/filters/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/filters/domain_filter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/filters/domain_patterns.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/filters/path_filter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/incremental_strategy.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/link_processor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/multiprocess_spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/node_scanner.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/parsers/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/parsers/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/parsers/html_parser.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/progress_tracker.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/scheduler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/serialization_mixin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/sitemap_parser.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/sitemap_processor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/sitemap_spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/spider_lifecycle.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/graph_export.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/graph_merge/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/graph_merge/graph_operations.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/graph_merge/merge_strategies.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/data/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/data/backends/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/data/backends/memory.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/data/backends/sqlite.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/data/interfaces.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/context/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/context/crawl_context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/edge.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/edge_analysis.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/graph.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/graph_operations.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/graph_statistics.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/merge_strategies.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/node.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/redirect_mode.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/registries.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/sitemap_node.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/strategies.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/crawl.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/edge.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/event_bus.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/events.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/fetch.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/idempotent_subscriber.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/legacy_type_map.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/llm.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/node.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/plugin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/scheduler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/adapter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/control_channel.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/crawl_context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/distributed_spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/driver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/event_bus.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/exporters.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/filter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/merge_context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/node_interfaces.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/parser.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/plugin_manager.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/processor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/scanner.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/scheduler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/scheduling/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/scheduling/frontier.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/scheduling/scheduler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/stop_condition.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/storage/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/storage/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/storage/eviction.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/storage/queue.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/adjacency_maintenance.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/cycle_detection.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/degree_ranking.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/depth_context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/display_title.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/duplicate_detection.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/duplicate_selector.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/duplicate_stats.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/edge_filter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/edge_statistics.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/eviction_policy.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/graph_query_cascade.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/graph_statistics.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/graph_traversal.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/language_versions.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/lsh_index.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/minimap_builder.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/node_content_resolver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/node_view_builder.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/pagination_guards.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/predicate_ops.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/schema_filter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/search_haystack.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/simhash_clustering.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/simhash_fallback.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/similarity_ranking.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/text_search.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/url_clustering.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/url_deduplication.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/url_normalizer.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/url_rule_matcher.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/url_tree_builder.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/configs.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/domain_patterns.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/fetch_options.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/html_document.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/lifecycle.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/models.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/page_actions.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/redirect_hop.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/scheduling/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/scheduling/crawl_request.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/scheduling/retry_policy.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/settings.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/url_normalization.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/views.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/cache_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/chain.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/error_recovery_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/logging_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/proxy_health.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/proxy_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/proxy_models.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/proxy_selection.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/rate_limit_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/request_response_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/retry_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/robots_cache.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/robots_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/robots_validator.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/user_agent_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/builtin/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/builtin/stats_export_plugin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/crawl_engine/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/crawl_engine/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/crawl_engine/priority_provider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/crawl_engine/smart_crawl.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/anti_bot_detection.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/anti_bot_playwright.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/anti_bot_scripts.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/anti_bot_stealth.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/captcha/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/captcha/detector.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/captcha/models.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/captcha/plugin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/captcha/services.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/content_extractors/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/content_extractors/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/content_extractors/goose3_extractor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/content_extractors/newspaper_extractor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/content_extractors/plugin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/content_extractors/readability_extractor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/defaults.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/extractors/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/extractors/email_extractor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/extractors/phone_extractor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/extractors/price_extractor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/links.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/markdown_plugin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/metadata.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/smart_page_finder.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/constants.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/exceptions.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/extractor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/options.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/jsonld.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/microdata.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/opengraph.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/rdfa.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/twitter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/plugin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/result.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/text.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/url_normalizer.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/registry.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/adapters/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/adapters/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/adapters/selectolax_adapter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/celery_app.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/celery_batch.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/celery_job_task.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/celery_unified.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/config.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/easy_crawler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/worker_api.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/auto_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/eviction.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/graph_repository.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/json/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/json_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/lmdb_eviction_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/memory/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/memory_dedup_store.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/memory_frontier.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/memory_politeness_store.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/memory_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/mongodb_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/naming_strategy.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/postgresql_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/protocols/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/redis_dedup_store.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/redis_politeness_store.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/repository.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/sqlite/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/sqlite_eviction_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/sqlite_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/file_job_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/file_queue_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/memory_job_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/memory_queue_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/postgresql_job_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/postgresql_queue_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/unified_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/config.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/driver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/driver_curl_cffi.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/plugins/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle_adaptive.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/plugins/headers.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/plugins/http_cache.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/plugins/rate_limiter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/plugins/retry.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/stages.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/base_plugin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/compression/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/compression/strategy.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/connection_pool.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/core/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/core/base_async.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/core/body_decoder.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/core/body_limit.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/core/mixins.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/core/redirect_utils.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/escalation.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/factory.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/adaptive_semaphore.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/batch_scheduler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/browser_factory.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/browser_profile.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/config.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/driver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/engine.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/fetch_metrics.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/net/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/net/geoip.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/net/proxy_pool.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/net/rate_limiter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/page_actions.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/_network_record.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_detector.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_solver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/cookie_consent.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/form_filler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/human_bezier.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/network_log.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/ram_adaptive.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/screenshot.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/user_profile.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/pool_lifecycle.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/pooled_driver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/stages.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/stealth_hints.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/plugin_manager.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/protocols.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/undetected/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/undetected/async_driver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/native/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/native/bloom_filter.pyx +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/native/html_parser.pyx +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/native/setup.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/native/url_utils.pyx +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/error_tracing.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/extensions/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/extensions/memory_guard.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/extensions/state_manager.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/base_metrics_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/crawl_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/dlq_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/error_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/logging_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/metrics_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/node_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/plugin_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/storage_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/url_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/metrics/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/metrics/memory_profiler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/metrics/metrics_collector.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/metrics/prometheus_metrics.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/metrics_core.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/structured_logging.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/tracing.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/optimizations/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/optimizations/generators.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/optimizations/memory_slots.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/optimizations/simhash_numba.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/py.typed +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/constants.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/dto/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/dto/edge_dto.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/dto/graph_dto.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/dto/node_dto.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/error_handling/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/error_handling/error_handler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/exceptions.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/platform/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/platform/adaptive_resolver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/platform/config.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/platform/detector.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/platform/diagnostics.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/platform/metrics.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/security/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/security/url_sanitizer.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/security/url_validator.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/async_compat.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/best_effort.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/bloom_filter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/captcha/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/captcha/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/captcha/manager.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/celery_config.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/celery_helpers.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/cgroup_limits.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/content_decoder.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/distributed_rate_limiter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/distributed_rate_limiter_backends.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/dns_cache.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/enterprise_ua_generator.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/event_publisher_mixin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/fast_json.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/fingerprint.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/fingerprint_data.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/fingerprint_generators.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/fingerprint_profile.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/fingerprint_stealth.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/html_utils.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/id_generator.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/isolated_executor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/memory_optimizer.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/rate_limiter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/regex_validator.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/shadow_dom.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/text_utils.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/url_patterns.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/url_utils.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/user_agent_rotator.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/validation_helpers.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/visualization.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/visualization_core.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/tools/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/tools/db_cleanup.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler.egg-info/SOURCES.txt +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler.egg-info/dependency_links.txt +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler.egg-info/entry_points.txt +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler.egg-info/requires.txt +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler.egg-info/top_level.txt +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.2}/setup.cfg +0 -0
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
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
|
+
|
|
3
18
|
## 5.4.1 — 2026-10
|
|
4
19
|
|
|
5
20
|
### Fixed
|
|
@@ -42,6 +42,7 @@ from graph_crawler.shared.constants import (
|
|
|
42
42
|
BROWSER_PAGE_RAM_ESTIMATE_MB,
|
|
43
43
|
BROWSER_RAM_BASELINE_MB,
|
|
44
44
|
DEFAULT_BATCH_SIZE,
|
|
45
|
+
DEFAULT_NODE_ENRICH_TIMEOUT,
|
|
45
46
|
MAX_PAGES_LIMIT,
|
|
46
47
|
)
|
|
47
48
|
from graph_crawler.shared.utils.cgroup_limits import (
|
|
@@ -85,6 +86,12 @@ logger = logging.getLogger(__name__)
|
|
|
85
86
|
# Скільки секунд після crawl-level timeout даємо in-flight нодам дозавершитись.
|
|
86
87
|
TIMEOUT_DRAIN_GRACE_S = 1.0
|
|
87
88
|
|
|
89
|
+
# NEW-3-5: браузерний пул «завис», якщо жодна нода не завершилась за
|
|
90
|
+
# 2 × fetch_timeout + enrich-таймаут + запас (найдовший законний шлях однієї ноди).
|
|
91
|
+
STALL_FETCH_TIMEOUT_MULTIPLIER = 2.0
|
|
92
|
+
STALL_GRACE_S = 10.0
|
|
93
|
+
STALL_CHECK_INTERVAL_S = 5.0
|
|
94
|
+
|
|
88
95
|
|
|
89
96
|
def _stats_for_log(stats: dict) -> dict:
|
|
90
97
|
"""Копія stats для логу без шумового ``redirect_deduped_scanned=0``.
|
|
@@ -425,6 +432,14 @@ class CrawlCoordinator:
|
|
|
425
432
|
asyncio.create_task(self._worker_loop(i, state), name=f"crawl-worker-{i}")
|
|
426
433
|
for i in range(pool_size)
|
|
427
434
|
]
|
|
435
|
+
stall_timeout = self._resolve_stall_timeout()
|
|
436
|
+
watchdog = (
|
|
437
|
+
asyncio.create_task(
|
|
438
|
+
self._stall_watchdog(workers, state, stall_timeout), name="crawl-stall-watchdog"
|
|
439
|
+
)
|
|
440
|
+
if stall_timeout
|
|
441
|
+
else None
|
|
442
|
+
)
|
|
428
443
|
try:
|
|
429
444
|
await self._await_workers(workers, state)
|
|
430
445
|
except asyncio.CancelledError:
|
|
@@ -436,6 +451,9 @@ class CrawlCoordinator:
|
|
|
436
451
|
await asyncio.gather(*workers, return_exceptions=True)
|
|
437
452
|
raise
|
|
438
453
|
finally:
|
|
454
|
+
if watchdog is not None:
|
|
455
|
+
watchdog.cancel()
|
|
456
|
+
await asyncio.gather(watchdog, return_exceptions=True)
|
|
439
457
|
self._pool_state = None
|
|
440
458
|
|
|
441
459
|
if state.stop_reason == "timeout":
|
|
@@ -444,6 +462,8 @@ class CrawlCoordinator:
|
|
|
444
462
|
self.spider, "stop_reason", None
|
|
445
463
|
):
|
|
446
464
|
self._record_stop(state.stop_reason, "limit")
|
|
465
|
+
if state.stop_reason == "stalled" and not self.scheduler.is_empty():
|
|
466
|
+
self._record_stop("stalled", "limit")
|
|
447
467
|
|
|
448
468
|
logger.debug(
|
|
449
469
|
"Worker pool finished: %d nodes processed, reason=%s, %.1fs",
|
|
@@ -457,7 +477,8 @@ class CrawlCoordinator:
|
|
|
457
477
|
async def _await_workers(self, workers: list[asyncio.Task], state: PoolState) -> None:
|
|
458
478
|
"""Чекає воркерів; при crawl-level timeout скасовує in-flight (інакше ретраї тягнуть хвіст)."""
|
|
459
479
|
if self.timeout is None or not self.start_time:
|
|
460
|
-
await asyncio.
|
|
480
|
+
done, _ = await asyncio.wait(workers)
|
|
481
|
+
self._raise_worker_errors(done)
|
|
461
482
|
return
|
|
462
483
|
deadline = self.start_time + self.timeout + TIMEOUT_DRAIN_GRACE_S
|
|
463
484
|
done, pending = await asyncio.wait(workers, timeout=max(0.0, deadline - time.time()))
|
|
@@ -478,10 +499,53 @@ class CrawlCoordinator:
|
|
|
478
499
|
for task in pending:
|
|
479
500
|
task.cancel()
|
|
480
501
|
await asyncio.gather(*pending, return_exceptions=True)
|
|
502
|
+
self._raise_worker_errors(done)
|
|
503
|
+
|
|
504
|
+
@staticmethod
|
|
505
|
+
def _raise_worker_errors(done: set[asyncio.Task]) -> None:
|
|
506
|
+
"""Скасований сторожем простою воркер — не помилка; справжню помилку воркера прокидаємо."""
|
|
481
507
|
for task in done:
|
|
482
508
|
if not task.cancelled() and task.exception() is not None:
|
|
483
509
|
raise task.exception()
|
|
484
510
|
|
|
511
|
+
def _resolve_stall_timeout(self) -> float | None:
|
|
512
|
+
"""Поріг «пул завис» (NEW-3-5): лише для браузерних драйверів, що мають ``fetch_timeout``."""
|
|
513
|
+
if not _is_js_heavy_driver(self.driver):
|
|
514
|
+
return None
|
|
515
|
+
fetch_timeout = getattr(self.driver, "fetch_timeout", None)
|
|
516
|
+
if isinstance(fetch_timeout, bool) or not isinstance(fetch_timeout, (int, float)):
|
|
517
|
+
return None
|
|
518
|
+
if fetch_timeout <= 0:
|
|
519
|
+
return None
|
|
520
|
+
enrich_timeout = (
|
|
521
|
+
getattr(self.config, "node_enrich_timeout", None) or DEFAULT_NODE_ENRICH_TIMEOUT
|
|
522
|
+
)
|
|
523
|
+
return STALL_FETCH_TIMEOUT_MULTIPLIER * fetch_timeout + enrich_timeout + STALL_GRACE_S
|
|
524
|
+
|
|
525
|
+
async def _stall_watchdog(
|
|
526
|
+
self, workers: list[asyncio.Task], state: PoolState, stall_timeout: float
|
|
527
|
+
) -> None:
|
|
528
|
+
"""Жодна нода не завершилась за ``stall_timeout`` — вкладки зависли: скасовуємо їх, фаза завершується."""
|
|
529
|
+
interval = min(STALL_CHECK_INTERVAL_S, stall_timeout / 4)
|
|
530
|
+
while not state.stop:
|
|
531
|
+
await asyncio.sleep(interval)
|
|
532
|
+
idle = time.monotonic() - state.last_progress_at
|
|
533
|
+
if state.in_flight == 0 or idle < stall_timeout:
|
|
534
|
+
continue
|
|
535
|
+
logger.warning(
|
|
536
|
+
"Worker pool stalled: no page finished for %.0fs (limit %.0fs), in_flight=%d, "
|
|
537
|
+
"queue=%d, stuck=%s - cancelling stuck workers",
|
|
538
|
+
idle,
|
|
539
|
+
stall_timeout,
|
|
540
|
+
state.in_flight,
|
|
541
|
+
self.scheduler.size() if hasattr(self.scheduler, "size") else 0,
|
|
542
|
+
list(state.in_flight_urls)[:5],
|
|
543
|
+
)
|
|
544
|
+
state.request_stop("stalled")
|
|
545
|
+
for task in workers:
|
|
546
|
+
task.cancel()
|
|
547
|
+
return
|
|
548
|
+
|
|
485
549
|
def _resolve_worker_pool_size(self) -> int:
|
|
486
550
|
"""N воркерів: override з конфігу → конкурентність драйвера → кап для JS-heavy.
|
|
487
551
|
|
|
@@ -15,6 +15,7 @@ from __future__ import annotations
|
|
|
15
15
|
|
|
16
16
|
import asyncio
|
|
17
17
|
import logging
|
|
18
|
+
import time
|
|
18
19
|
from collections import defaultdict
|
|
19
20
|
from urllib.parse import urlsplit
|
|
20
21
|
|
|
@@ -119,6 +120,8 @@ class PoolState:
|
|
|
119
120
|
self.stop = False
|
|
120
121
|
self.stop_reason: str | None = None
|
|
121
122
|
self.wakeup = asyncio.Event()
|
|
123
|
+
# NEW-3-5: час останньої завершеної ноди — сторож простою пулу.
|
|
124
|
+
self.last_progress_at = time.monotonic()
|
|
122
125
|
|
|
123
126
|
def request_stop(self, reason: str) -> None:
|
|
124
127
|
if not self.stop:
|
|
@@ -141,6 +144,7 @@ class PoolState:
|
|
|
141
144
|
"""Повертає True, якщо eviction-watermark просунувся."""
|
|
142
145
|
self.in_flight -= 1
|
|
143
146
|
self.completed += 1
|
|
147
|
+
self.last_progress_at = time.monotonic()
|
|
144
148
|
if node_id:
|
|
145
149
|
self.in_flight_node_ids.discard(node_id)
|
|
146
150
|
self.in_flight_urls.pop(url, None)
|
|
@@ -28,6 +28,7 @@ from graph_crawler.infrastructure.transport.core.redirect_utils import (
|
|
|
28
28
|
)
|
|
29
29
|
from graph_crawler.infrastructure.transport.playwright.context import BrowserContext
|
|
30
30
|
from graph_crawler.infrastructure.transport.playwright.stages import BrowserStage
|
|
31
|
+
from graph_crawler.shared.utils.best_effort import close_quietly
|
|
31
32
|
from graph_crawler.shared.utils.shadow_dom import expose_shadow_roots
|
|
32
33
|
|
|
33
34
|
logger = logging.getLogger(__name__)
|
|
@@ -49,6 +50,11 @@ logger = logging.getLogger(__name__)
|
|
|
49
50
|
# =============================================================================
|
|
50
51
|
PARTIAL_TIMEOUT_STATUS = 504
|
|
51
52
|
|
|
53
|
+
# NEW-3-5: після fetch_timeout вкладку ще питають про частковий HTML і адресу; зависла вкладка
|
|
54
|
+
# (скрипт сторінки тримає її) не відповідає ніколи — без ліміту воркер чекав до кінця фази.
|
|
55
|
+
TIMEOUT_RECOVERY_S = 5.0
|
|
56
|
+
FROZEN_PAGE_CLOSE_S = 3.0
|
|
57
|
+
|
|
52
58
|
# ``page.content()`` серіалізує лише light DOM: вміст відкритих shadow root'ів (веб-компоненти,
|
|
53
59
|
# Lit/Stencil/Salesforce LWC, вбудовані віджети цін) у HTML не потрапляє, і парсер «не бачить»
|
|
54
60
|
# того, що бачить користувач. Якщо на сторінці є відкриті shadow root'и, серіалізуємо DOM
|
|
@@ -228,12 +234,7 @@ class PageFetcher:
|
|
|
228
234
|
timeout=float(self.fetch_timeout),
|
|
229
235
|
)
|
|
230
236
|
except asyncio.TimeoutError:
|
|
231
|
-
|
|
232
|
-
partial_html = None
|
|
233
|
-
async with protect.suppress(
|
|
234
|
-
Exception, log=logger.debug, message="_fetch_with_page_impl"
|
|
235
|
-
):
|
|
236
|
-
partial_html = await page.content()
|
|
237
|
+
partial_html, frozen = await self._partial_html_after_timeout(page, url)
|
|
237
238
|
partial_html = self._drop_oversized_partial(partial_html, url)
|
|
238
239
|
|
|
239
240
|
error_msg = f"Fetch timeout ({self.fetch_timeout}s) for {url}"
|
|
@@ -261,8 +262,18 @@ class PageFetcher:
|
|
|
261
262
|
)
|
|
262
263
|
# Навіть у fetch-timeout page.url може містити final URL.
|
|
263
264
|
# Зберігаємо провенанс, щоб graph.handle_redirect() спрацював
|
|
264
|
-
# для timeout-нод з JS-редіректом.
|
|
265
|
-
ft_final, ft_chain, ft_hops =
|
|
265
|
+
# для timeout-нод з JS-редіректом. Завислу вкладку не питаємо (NEW-3-5).
|
|
266
|
+
ft_final, ft_chain, ft_hops = url, [], []
|
|
267
|
+
if not frozen:
|
|
268
|
+
try:
|
|
269
|
+
ft_final, ft_chain, ft_hops = await asyncio.wait_for(
|
|
270
|
+
self._build_playwright_provenance(url, page, None),
|
|
271
|
+
timeout=TIMEOUT_RECOVERY_S,
|
|
272
|
+
)
|
|
273
|
+
except asyncio.TimeoutError:
|
|
274
|
+
frozen = True
|
|
275
|
+
if frozen:
|
|
276
|
+
await self._close_frozen_page(page, url)
|
|
266
277
|
return self._finalize_response(
|
|
267
278
|
FetchResponse(
|
|
268
279
|
url=url,
|
|
@@ -282,6 +293,27 @@ class PageFetcher:
|
|
|
282
293
|
logger.error(error_msg)
|
|
283
294
|
return FetchResponse(url=url, html=None, status_code=None, headers={}, error=error_msg)
|
|
284
295
|
|
|
296
|
+
@staticmethod
|
|
297
|
+
async def _partial_html_after_timeout(page: Any, url: str) -> tuple[str | None, bool]:
|
|
298
|
+
"""Частковий HTML після fetch_timeout і ознака «вкладка зависла» (не відповіла за ``TIMEOUT_RECOVERY_S``)."""
|
|
299
|
+
try:
|
|
300
|
+
return await asyncio.wait_for(page.content(), timeout=TIMEOUT_RECOVERY_S), False
|
|
301
|
+
except asyncio.TimeoutError:
|
|
302
|
+
logger.warning(
|
|
303
|
+
"Browser tab frozen after fetch timeout: page.content() did not answer in %.0fs for %s",
|
|
304
|
+
TIMEOUT_RECOVERY_S,
|
|
305
|
+
url,
|
|
306
|
+
)
|
|
307
|
+
return None, True
|
|
308
|
+
except Exception as e: # noqa: BLE001 — сторінка закрилась/переходить
|
|
309
|
+
logger.debug("partial content after timeout unavailable for %s: %s", url, e)
|
|
310
|
+
return None, False
|
|
311
|
+
|
|
312
|
+
@staticmethod
|
|
313
|
+
async def _close_frozen_page(page: Any, url: str) -> None:
|
|
314
|
+
"""Закрита вкладка не повертається в пул — пул відкриє нову замість завислої."""
|
|
315
|
+
await close_quietly(page.close(), name=f"frozen page {url}", timeout=FROZEN_PAGE_CLOSE_S)
|
|
316
|
+
|
|
285
317
|
@staticmethod
|
|
286
318
|
def _log_fetch_timeout(
|
|
287
319
|
error_msg: str,
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "graph-crawler"
|
|
7
|
-
version = "5.4.
|
|
7
|
+
version = "5.4.2"
|
|
8
8
|
description = "Web crawler that builds a graph of a site. HTTP → TLS → browser escalation with pluggable browser engines."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/context/dependency_registry.py
RENAMED
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/context/graph_context.py
RENAMED
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/context/merge_context.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/mappers/__init__.py
RENAMED
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/mappers/edge_mapper.py
RENAMED
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/mappers/graph_mapper.py
RENAMED
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/mappers/node_data_codec.py
RENAMED
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/mappers/node_mapper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/driver_factory.py
RENAMED
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/resource_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/storage_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/spider.py
RENAMED
|
File without changes
|
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/graph_export.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/graph_operations.py
RENAMED
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/graph_statistics.py
RENAMED
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/merge_strategies.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/idempotent_subscriber.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|