graph-crawler 5.3.3__tar.gz → 5.3.4__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.3.3 → graph_crawler-5.3.4}/CHANGELOG.md +33 -0
- {graph_crawler-5.3.3/graph_crawler.egg-info → graph_crawler-5.3.4}/PKG-INFO +56 -65
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/README.md +55 -64
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/node_scanner.py +5 -5
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/spider.py +72 -36
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/entities/node.py +80 -4
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/__init__.py +2 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/control_channel.py +27 -17
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/isolated_executor.py +7 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4/graph_crawler.egg-info}/PKG-INFO +56 -65
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/pyproject.toml +1 -1
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/LICENSE +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/MANIFEST.in +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/__version__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/ai/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/_core.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/_distributed.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/_shared.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/_sitemap_distributed.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/async_.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/cli.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/client/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/client/client.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/history_manager.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/project_init.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/server/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/server/crawl_monitor.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/server/dashboard.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/server/rest_api.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/server/webhooks.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/server/websocket_manager.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/stats_collector.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/api/sync.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/bootstrap.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/context/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/context/dependency_registry.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/context/graph_context.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/context/merge_context.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/dto/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/dto/edge_dto.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/dto/graph_dto.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/dto/mappers/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/dto/mappers/edge_mapper.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/dto/mappers/graph_mapper.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/dto/mappers/node_data_codec.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/dto/mappers/node_mapper.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/dto/node_dto.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/dto/utils.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/application_container.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/driver_factory.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/exporters/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/exporters/base_exporter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/exporters/csv_exporter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/exporters/edge_exporter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/exporters/excel_exporter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/exporters/jsonl_exporter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/exporters/node_exporter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/exporters/parquet_exporter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/exporters/sql_exporter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/finalize.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/low_memory_graph_saver.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/resource_budget.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/scheduling/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/scheduling/rule_based_scheduler.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/services/storage_factory.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/adaptive_throttler.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/async_bridge.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/base_spider.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/celery_batch_spider.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/celery_spider.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/checkpoint.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/crawl_coordinator.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/dead_letter_queue.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/domain_rate_limiter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/filters/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/filters/base.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/filters/domain_filter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/filters/domain_patterns.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/filters/path_filter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/incremental_strategy.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/link_processor.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/multiprocess_spider.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/parsers/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/parsers/base.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/parsers/html_parser.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/progress_tracker.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/scheduler.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/serialization_mixin.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/sitemap_parser.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/sitemap_processor.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/sitemap_spider.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/spider_lifecycle.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/worker_pool.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/graph_export.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/graph_merge/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/graph_merge/graph_operations.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/graph_merge/merge_strategies.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/data/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/data/backends/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/data/backends/memory.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/data/backends/sqlite.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/data/interfaces.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/context/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/context/crawl_context.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/entities/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/entities/edge.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/entities/edge_analysis.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/entities/graph.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/entities/graph_operations.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/entities/graph_statistics.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/entities/merge_strategies.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/entities/redirect_mode.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/entities/registries.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/entities/sitemap_node.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/entities/strategies.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/events/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/events/base.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/events/crawl.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/events/edge.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/events/event_bus.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/events/events.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/events/fetch.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/events/idempotent_subscriber.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/events/legacy_type_map.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/events/llm.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/events/node.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/events/plugin.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/events/scheduler.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/events/storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/adapter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/crawl_context.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/distributed_spider.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/driver.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/event_bus.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/exporters.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/filter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/merge_context.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/node_interfaces.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/parser.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/plugin_manager.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/processor.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/scanner.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/scheduler.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/scheduling/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/scheduling/frontier.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/scheduling/scheduler.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/spider.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/stop_condition.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/storage/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/storage/base.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/storage/eviction.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/interfaces/storage/queue.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/adjacency_maintenance.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/cycle_detection.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/degree_ranking.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/depth_context.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/display_title.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/duplicate_detection.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/duplicate_selector.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/duplicate_stats.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/edge_filter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/edge_statistics.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/eviction_policy.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/graph_query_cascade.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/graph_statistics.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/graph_traversal.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/language_versions.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/lsh_index.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/minimap_builder.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/node_content_resolver.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/node_view_builder.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/pagination_guards.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/predicate_ops.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/schema_filter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/search_haystack.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/simhash_clustering.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/simhash_fallback.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/similarity_ranking.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/text_search.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/url_clustering.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/url_deduplication.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/url_normalizer.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/url_rule_matcher.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/services/url_tree_builder.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/value_objects/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/value_objects/configs.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/value_objects/domain_patterns.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/value_objects/fetch_options.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/value_objects/html_document.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/value_objects/lifecycle.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/value_objects/models.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/value_objects/page_actions.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/value_objects/redirect_hop.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/value_objects/scheduling/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/value_objects/scheduling/crawl_request.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/value_objects/scheduling/retry_policy.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/value_objects/settings.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/value_objects/url_normalization.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/domain/value_objects/views.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/base.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/cache_middleware.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/chain.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/error_recovery_middleware.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/logging_middleware.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/proxy_health.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/proxy_middleware.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/proxy_models.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/proxy_selection.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/rate_limit_middleware.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/request_response_middleware.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/retry_middleware.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/robots_cache.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/robots_middleware.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/robots_validator.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/middleware/user_agent_middleware.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/builtin/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/builtin/stats_export_plugin.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/crawl_engine/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/crawl_engine/base.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/crawl_engine/priority_provider.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/crawl_engine/smart_crawl.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/engine/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/engine/anti_bot_detection.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/engine/anti_bot_playwright.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/engine/anti_bot_scripts.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/engine/anti_bot_stealth.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/engine/base.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/engine/captcha/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/engine/captcha/detector.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/engine/captcha/models.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/engine/captcha/plugin.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/engine/captcha/services.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/base.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/content_extractors/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/content_extractors/base.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/content_extractors/goose3_extractor.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/content_extractors/newspaper_extractor.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/content_extractors/plugin.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/content_extractors/readability_extractor.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/defaults.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/extractors/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/extractors/email_extractor.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/extractors/phone_extractor.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/extractors/price_extractor.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/links.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/markdown_plugin.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/metadata.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/smart_page_finder.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/structured_data/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/structured_data/constants.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/structured_data/exceptions.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/structured_data/extractor.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/structured_data/options.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/base.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/jsonld.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/microdata.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/opengraph.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/rdfa.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/twitter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/structured_data/plugin.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/structured_data/result.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/text.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/node/url_normalizer.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/extensions/plugins/registry.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/adapters/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/adapters/base.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/adapters/selectolax_adapter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/messaging/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/messaging/celery_app.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/messaging/celery_batch.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/messaging/celery_job_task.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/messaging/celery_unified.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/messaging/config.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/messaging/easy_crawler.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/messaging/worker_api.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/auto_storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/base.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/eviction.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/graph_repository.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/json/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/json_storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/lmdb_eviction_storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/memory/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/memory_dedup_store.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/memory_frontier.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/memory_politeness_store.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/memory_storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/mongodb_storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/naming_strategy.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/postgresql_storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/protocols/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/redis_dedup_store.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/redis_politeness_store.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/repository.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/sqlite/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/sqlite_eviction_storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/sqlite_storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/unified/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/unified/file_job_storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/unified/file_queue_storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/unified/memory_job_storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/unified/memory_queue_storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/unified/postgresql_job_storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/unified/postgresql_queue_storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/persistence/unified/unified_storage.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/async_http/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/async_http/config.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/async_http/context.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/async_http/driver.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/async_http/driver_curl_cffi.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/async_http/plugins/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle_adaptive.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/async_http/plugins/headers.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/async_http/plugins/http_cache.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/async_http/plugins/rate_limiter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/async_http/plugins/retry.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/async_http/stages.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/base.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/base_plugin.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/compression/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/compression/strategy.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/connection_pool.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/context.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/core/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/core/base_async.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/core/body_decoder.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/core/mixins.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/core/redirect_utils.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/escalation.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/factory.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/adaptive_semaphore.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/batch_scheduler.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/browser_factory.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/browser_profile.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/config.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/context.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/driver.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/engine.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/fetch_metrics.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/net/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/net/geoip.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/net/proxy_pool.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/net/rate_limiter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/page_actions.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/page_fetch.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/plugins/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/plugins/_network_record.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_detector.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_solver.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/plugins/cookie_consent.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/plugins/form_filler.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/plugins/human_bezier.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/plugins/network_log.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/plugins/ram_adaptive.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/plugins/screenshot.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/plugins/user_profile.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/pool_lifecycle.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/pooled_driver.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/stages.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/playwright/stealth_hints.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/plugin_manager.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/protocols.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/undetected/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/infrastructure/transport/undetected/async_driver.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/native/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/native/bloom_filter.pyx +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/native/html_parser.pyx +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/native/setup.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/native/url_utils.pyx +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/decorators/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/decorators/cache.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/decorators/log.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/decorators/retry.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/decorators/timing.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/error_tracing.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/extensions/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/extensions/memory_guard.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/extensions/state_manager.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/listeners/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/listeners/base.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/listeners/base_metrics_listener.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/listeners/crawl_listener.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/listeners/dlq_listener.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/listeners/error_listener.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/listeners/logging_listener.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/listeners/metrics_listener.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/listeners/node_listener.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/listeners/plugin_listener.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/listeners/storage_listener.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/listeners/url_listener.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/metrics/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/metrics/memory_profiler.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/metrics/metrics_collector.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/metrics/prometheus_metrics.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/metrics_core.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/structured_logging.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/observability/tracing.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/optimizations/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/optimizations/generators.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/optimizations/memory_slots.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/optimizations/simhash_numba.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/py.typed +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/constants.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/dto/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/dto/edge_dto.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/dto/graph_dto.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/dto/node_dto.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/error_handling/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/error_handling/error_handler.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/exceptions.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/platform/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/platform/adaptive_resolver.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/platform/config.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/platform/detector.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/platform/diagnostics.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/platform/metrics.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/security/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/security/url_sanitizer.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/security/url_validator.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/async_compat.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/bloom_filter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/captcha/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/captcha/base.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/captcha/manager.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/celery_config.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/celery_helpers.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/cgroup_limits.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/content_decoder.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/distributed_rate_limiter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/distributed_rate_limiter_backends.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/dns_cache.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/enterprise_ua_generator.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/event_publisher_mixin.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/fast_json.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/fingerprint.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/fingerprint_data.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/fingerprint_generators.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/fingerprint_profile.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/fingerprint_stealth.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/html_utils.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/id_generator.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/memory_optimizer.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/rate_limiter.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/regex_validator.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/shadow_dom.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/text_utils.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/url_patterns.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/url_utils.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/user_agent_rotator.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/validation_helpers.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/visualization.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/shared/utils/visualization_core.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/tools/__init__.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/tools/db_cleanup.py +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler.egg-info/SOURCES.txt +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler.egg-info/dependency_links.txt +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler.egg-info/entry_points.txt +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler.egg-info/requires.txt +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler.egg-info/top_level.txt +0 -0
- {graph_crawler-5.3.3 → graph_crawler-5.3.4}/setup.cfg +0 -0
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.3.4 — 2026-10
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **The per-node enrichment limit measures active work, not the HTML-pool queue.** `NodeScanner` wraps
|
|
8
|
+
`enrich_node` in `enrich_budget(seconds)` (`graph_crawler.domain.entities.node`): while a call waits for a thread
|
|
9
|
+
of the HTML executor in `run_in_html_pool` (parse, `_update_from_context`, hashes), the deadline is suspended and
|
|
10
|
+
resumed with the same remainder the moment the work starts. With 5.3.3 the hook and the hashes moved into the
|
|
11
|
+
shared pool, so under load a node's wall-clock depended on how many neighbours stood in front of it: a burst of
|
|
12
|
+
large pages produced a cascade of `Per-node timeout … marking as ERROR` for pages whose own processing took a
|
|
13
|
+
fraction of the limit (and every stage re-queued behind the others). A page whose **own** processing exceeds
|
|
14
|
+
`node_enrich_timeout` still times out and still returns its discovered links. The crawl-level timeout remains the
|
|
15
|
+
upper bound for a node that waits in the queue.
|
|
16
|
+
- **`IsolatedExecutor.run` admits at most `max_workers` tasks at a time** (`threading.BoundedSemaphore`): the task
|
|
17
|
+
`timeout` now measures execution in the child process, not the wait behind other tasks. Previously a burst of
|
|
18
|
+
submissions could expire the timeout of a queued task, which killed the whole pool and failed every task in flight.
|
|
19
|
+
|
|
20
|
+
- **Control commands are no longer dropped silently.** `GraphSpider.process_control_messages` executes only
|
|
21
|
+
`SPIDER_COMMANDS` (`STOP`, `PAUSE`, `RESUME`, `REPRIORITIZE`, `FORCE_VISIT`); any other `CrawlCommand` logs
|
|
22
|
+
`WARNING Unsupported control command '<value>' from <sender> is ignored (supported: ...)` once per command type
|
|
23
|
+
per crawl (repeats at `DEBUG`) and the crawl goes on. `REPRIORITIZE` without `url`/`priority` and `FORCE_VISIT`
|
|
24
|
+
without `url` log a `WARNING` with the received `data` instead of doing nothing.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- `EnrichBudget` (`pause()` / `resume()`) and `enrich_budget()` in `graph_crawler.domain.entities.node`.
|
|
29
|
+
- `SPIDER_COMMANDS` (`graph_crawler.domain.interfaces`): the commands `GraphSpider` executes.
|
|
30
|
+
|
|
31
|
+
### Deprecated
|
|
32
|
+
|
|
33
|
+
- `CrawlCommand.SKIP_URL`, `CHANGE_STRATEGY`, `SET_DEPTH_LIMIT`, `REQUEST_STATE`, `UPDATE_GOAL` were never executed
|
|
34
|
+
by the spider. They will be removed from the enum in 5.4.0.
|
|
35
|
+
|
|
3
36
|
## 5.3.3 — 2026-10
|
|
4
37
|
|
|
5
38
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graph-crawler
|
|
3
|
-
Version: 5.3.
|
|
3
|
+
Version: 5.3.4
|
|
4
4
|
Summary: Web crawler that builds a graph of a site. HTTP → TLS → browser escalation with pluggable browser engines.
|
|
5
5
|
Author: 0-EternalJunior-0
|
|
6
6
|
Maintainer: 0-EternalJunior-0
|
|
@@ -139,34 +139,20 @@ pip install "graph-crawler[browser]" # + browser (stock Playwright)
|
|
|
139
139
|
pip install "graph-crawler[antidetect]" # + TLS, patchright, CloakBrowser, nodriver
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
| `
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
148
|
-
| `
|
|
149
|
-
| `
|
|
150
|
-
| `
|
|
151
|
-
| `
|
|
152
|
-
| `
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
|
|
156
|
-
| extra | what it adds |
|
|
157
|
-
|--------------|---------------------------------------------------------------------|
|
|
158
|
-
| `browser` | `driver="playwright"` (`playwright` is an alias of this extra) |
|
|
159
|
-
| `tls` | `curl_cffi` — TLS fingerprint tier, `driver="tls"` |
|
|
160
|
-
| `stealth` | `patchright` + `playwright-stealth` — engine `"patchright"` |
|
|
161
|
-
| `cloak` | `cloakbrowser` — engine `"cloakbrowser"` (stealth in the binary) |
|
|
162
|
-
| `undetected` | `nodriver` — last tier, `driver="nodriver"` |
|
|
163
|
-
| `antidetect` | the four extras above |
|
|
164
|
-
| `server` | FastAPI dashboard / REST / WebSocket |
|
|
165
|
-
| `text` | `tiktoken` + `stop-words` |
|
|
166
|
-
| `markdown` | `html2md-clean` + `markdown-it-py` — `MarkdownPlugin` |
|
|
167
|
-
| `fuzzy` | `rapidfuzz` — `graph.search(..., mode="fuzzy")` |
|
|
168
|
-
| `mongodb`, `postgresql`, `celery`, `viz`, `articles`, `sitemap-fast`, `performance`, `system`, `all` | storage backends, distributed mode and other extras |
|
|
169
|
-
>>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
|
|
142
|
+
| extra | what it adds |
|
|
143
|
+
| -------------- | ------------------------------------------------------------------------------- |
|
|
144
|
+
| `browser` | `driver="playwright"`, stock Playwright (`playwright` is an alias of this extra) |
|
|
145
|
+
| `tls` | `curl_cffi` — TLS fingerprint tier, `driver="tls"` |
|
|
146
|
+
| `stealth` | `patchright` + `playwright-stealth` — engine `"patchright"` |
|
|
147
|
+
| `cloak` | `cloakbrowser` — engine `"cloakbrowser"` (stealth in the binary) |
|
|
148
|
+
| `undetected` | `nodriver` — last tier, `driver="nodriver"` |
|
|
149
|
+
| `antidetect` | the five extras above |
|
|
150
|
+
| `server` | FastAPI dashboard / REST / WebSocket (`graph_crawler.api.server`) |
|
|
151
|
+
| `text` | `tiktoken` + `stop-words` — `count_tokens`, `extract_keywords` |
|
|
152
|
+
| `markdown` | `html2md-clean` + `markdown-it-py` — `MarkdownPlugin` |
|
|
153
|
+
| `fuzzy` | `rapidfuzz` — `graph.search(..., mode="fuzzy")` |
|
|
154
|
+
| `sitemap-fast` | `lxml` sitemap parser |
|
|
155
|
+
| `mongodb`, `postgresql`, `celery`, `viz`, `articles`, `performance`, `system`, `all` | storage backends, distributed mode and other extras |
|
|
170
156
|
|
|
171
157
|
After installing a browser extra, download the browser once:
|
|
172
158
|
|
|
@@ -267,23 +253,13 @@ so `node.ok` is `False` for error pages.
|
|
|
267
253
|
|
|
268
254
|
`driver=` accepts a name or a driver instance.
|
|
269
255
|
|
|
270
|
-
|
|
271
|
-
|
|
|
272
|
-
|
|
|
273
|
-
| `
|
|
274
|
-
| `
|
|
275
|
-
| `
|
|
276
|
-
| `
|
|
277
|
-
| `escalating` | all of them | whatever is installed | http → tls → browser → undetected |
|
|
278
|
-
=======
|
|
279
|
-
| name | tier | needs | notes |
|
|
280
|
-
|--------------|-------------|-----------------------|-------------------------------------------------|
|
|
281
|
-
| `aiohttp` / `http` | http | — | default |
|
|
282
|
-
| `tls` | tls | `[tls]` | Chrome TLS fingerprint, no JavaScript |
|
|
283
|
-
| `playwright` | browser | `[browser]` | real browser; engine chosen by `browser_engine` |
|
|
284
|
-
| `nodriver` | undetected | `[undetected]` | CDP without webdriver |
|
|
285
|
-
| `escalating` | all of them | whatever is installed | http → tls → browser → undetected |
|
|
286
|
-
>>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
|
|
256
|
+
| name | tier | needs | notes |
|
|
257
|
+
| ----------------------------- | ----------- | --------------------- | ----------------------------------------------- |
|
|
258
|
+
| `aiohttp` / `http` / `async` | http | — | default |
|
|
259
|
+
| `tls` / `curl_cffi` | tls | `[tls]` | Chrome TLS fingerprint, no JavaScript |
|
|
260
|
+
| `playwright` | browser | `[browser]` | real browser; engine chosen by `browser_engine` |
|
|
261
|
+
| `nodriver` | undetected | `[undetected]` | CDP without webdriver |
|
|
262
|
+
| `escalating` | all of them | whatever is installed | http → tls → browser → undetected |
|
|
287
263
|
|
|
288
264
|
`escalating` creates tiers lazily: a browser starts only when a cheaper tier is
|
|
289
265
|
blocked. Tiers whose dependency is not installed are skipped.
|
|
@@ -299,13 +275,8 @@ Every response goes through `classify_block()`:
|
|
|
299
275
|
| `none` | normal page |
|
|
300
276
|
| `soft_block` | 429, plain 503, or 200 with an empty body |
|
|
301
277
|
| `challenge` | Cloudflare "Just a moment", `cf-mitigated: challenge`, Incapsula, DDoS-Guard |
|
|
302
|
-
<<<<<<< HEAD
|
|
303
|
-
| `hard_block` | 403 "Access Denied", Akamai `_abck`, DataDome headers |
|
|
304
|
-
| `captcha` | PerimeterX, DataDome captcha, reCAPTCHA / hCaptcha / Turnstile markup |
|
|
305
|
-
=======
|
|
306
278
|
| `hard_block` | 403/401/405 **with** a vendor or "Access Denied" marker (Akamai `_abck`, DataDome/Incapsula headers, `x-iinfo`, Cloudflare `server`) |
|
|
307
|
-
| `captcha` | PerimeterX, DataDome captcha, reCAPTCHA / hCaptcha / Turnstile markup
|
|
308
|
-
>>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
|
|
279
|
+
| `captcha` | PerimeterX, DataDome captcha, reCAPTCHA / hCaptcha / Turnstile markup |
|
|
309
280
|
|
|
310
281
|
Anything other than `none`, and network errors, move the request to the next tier.
|
|
311
282
|
|
|
@@ -375,25 +346,14 @@ policy = EscalationPolicy(
|
|
|
375
346
|
|
|
376
347
|
### Browser engines
|
|
377
348
|
|
|
378
|
-
|
|
379
|
-
The browser tier is stock `PlaywrightDriver`; the _engine_ decides which
|
|
380
|
-
Playwright module and which Chromium binary it launches.
|
|
381
|
-
|
|
382
|
-
| engine | what it is | falls back to |
|
|
383
|
-
| -------------- | ------------------------------------------------------------- | ------------- |
|
|
384
|
-
| `cloakbrowser` | Chromium with fingerprint patches compiled into the binary | `patchright` |
|
|
385
|
-
| `patchright` | Playwright fork without automation leaks (`Runtime.enable` …) | `playwright` |
|
|
386
|
-
| `playwright` | stock | — |
|
|
387
|
-
=======
|
|
388
|
-
The browser tier is `PlaywrightDriver`; the *engine* decides which Playwright
|
|
349
|
+
The browser tier is `PlaywrightDriver`; the _engine_ decides which Playwright
|
|
389
350
|
module and which Chromium binary it launches.
|
|
390
351
|
|
|
391
352
|
| engine | what it is | falls back to |
|
|
392
|
-
|
|
353
|
+
| -------------- | ------------------------------------------------------------- | ------------- |
|
|
393
354
|
| `cloakbrowser` | Chromium with fingerprint patches compiled into the binary | `patchright` |
|
|
394
355
|
| `patchright` | Playwright fork without automation leaks (`Runtime.enable` …) | `playwright` |
|
|
395
356
|
| `playwright` | stock Playwright | — |
|
|
396
|
-
>>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
|
|
397
357
|
|
|
398
358
|
A fallback is logged once at `INFO` with the reason. To get an `ImportError`
|
|
399
359
|
instead of a silent downgrade, set `GC_ENGINE_STRICT=1`, or pass
|
|
@@ -456,6 +416,37 @@ A target is found by `selector`, `text`, `placeholder` or `label` (`nth` picks
|
|
|
456
416
|
among visible matches). The first failing action stops the chain and is written
|
|
457
417
|
to `node.metadata["actions_error"]`.
|
|
458
418
|
|
|
419
|
+
## Controlling a running crawl
|
|
420
|
+
|
|
421
|
+
`async_crawl(..., control_channel=channel)` (and `crawl`, `GraphCrawlerClient.crawl`)
|
|
422
|
+
reads commands from the channel between pages:
|
|
423
|
+
|
|
424
|
+
```python
|
|
425
|
+
import graph_crawler as gc
|
|
426
|
+
from graph_crawler.domain.interfaces import AsyncQueueControlChannel, force_visit_command, stop_command
|
|
427
|
+
|
|
428
|
+
channel = AsyncQueueControlChannel()
|
|
429
|
+
channel.send(force_visit_command("https://example.com/hidden-page"))
|
|
430
|
+
graph = await gc.async_crawl("https://example.com", control_channel=channel)
|
|
431
|
+
|
|
432
|
+
# from a plugin, a callback or another task while the crawl runs:
|
|
433
|
+
channel.send(stop_command("enough data")) # graph.metadata["stop_reason"] == "enough data"
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
| command | `data` | effect |
|
|
437
|
+
| -------------- | ----------------------- | ---------------------------------------------- |
|
|
438
|
+
| `STOP` | `reason` (optional) | stops the crawl, `stop_reason` = `reason` |
|
|
439
|
+
| `PAUSE` | — | pauses until `RESUME` or `STOP` |
|
|
440
|
+
| `RESUME` | — | continues a paused crawl |
|
|
441
|
+
| `REPRIORITIZE` | `url`, `priority` | moves a queued URL in the queue |
|
|
442
|
+
| `FORCE_VISIT` | `url` | adds the URL to the queue with top priority |
|
|
443
|
+
|
|
444
|
+
`SPIDER_COMMANDS` is this set. `SKIP_URL`, `CHANGE_STRATEGY`, `SET_DEPTH_LIMIT`,
|
|
445
|
+
`REQUEST_STATE` and `UPDATE_GOAL` are declared in `CrawlCommand` but not executed:
|
|
446
|
+
the spider logs `WARNING Unsupported control command ...` (once per command type
|
|
447
|
+
per crawl) and goes on; they will be removed in the next minor release. A supported
|
|
448
|
+
command without its required `data` fields is also skipped with a `WARNING`.
|
|
449
|
+
|
|
459
450
|
## Plugins and custom nodes
|
|
460
451
|
|
|
461
452
|
A node plugin runs on every fetched page and writes to `user_data`, `metadata`
|
|
@@ -18,34 +18,20 @@ pip install "graph-crawler[browser]" # + browser (stock Playwright)
|
|
|
18
18
|
pip install "graph-crawler[antidetect]" # + TLS, patchright, CloakBrowser, nodriver
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
|
|
35
|
-
| extra | what it adds |
|
|
36
|
-
|--------------|---------------------------------------------------------------------|
|
|
37
|
-
| `browser` | `driver="playwright"` (`playwright` is an alias of this extra) |
|
|
38
|
-
| `tls` | `curl_cffi` — TLS fingerprint tier, `driver="tls"` |
|
|
39
|
-
| `stealth` | `patchright` + `playwright-stealth` — engine `"patchright"` |
|
|
40
|
-
| `cloak` | `cloakbrowser` — engine `"cloakbrowser"` (stealth in the binary) |
|
|
41
|
-
| `undetected` | `nodriver` — last tier, `driver="nodriver"` |
|
|
42
|
-
| `antidetect` | the four extras above |
|
|
43
|
-
| `server` | FastAPI dashboard / REST / WebSocket |
|
|
44
|
-
| `text` | `tiktoken` + `stop-words` |
|
|
45
|
-
| `markdown` | `html2md-clean` + `markdown-it-py` — `MarkdownPlugin` |
|
|
46
|
-
| `fuzzy` | `rapidfuzz` — `graph.search(..., mode="fuzzy")` |
|
|
47
|
-
| `mongodb`, `postgresql`, `celery`, `viz`, `articles`, `sitemap-fast`, `performance`, `system`, `all` | storage backends, distributed mode and other extras |
|
|
48
|
-
>>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
|
|
21
|
+
| extra | what it adds |
|
|
22
|
+
| -------------- | ------------------------------------------------------------------------------- |
|
|
23
|
+
| `browser` | `driver="playwright"`, stock Playwright (`playwright` is an alias of this extra) |
|
|
24
|
+
| `tls` | `curl_cffi` — TLS fingerprint tier, `driver="tls"` |
|
|
25
|
+
| `stealth` | `patchright` + `playwright-stealth` — engine `"patchright"` |
|
|
26
|
+
| `cloak` | `cloakbrowser` — engine `"cloakbrowser"` (stealth in the binary) |
|
|
27
|
+
| `undetected` | `nodriver` — last tier, `driver="nodriver"` |
|
|
28
|
+
| `antidetect` | the five extras above |
|
|
29
|
+
| `server` | FastAPI dashboard / REST / WebSocket (`graph_crawler.api.server`) |
|
|
30
|
+
| `text` | `tiktoken` + `stop-words` — `count_tokens`, `extract_keywords` |
|
|
31
|
+
| `markdown` | `html2md-clean` + `markdown-it-py` — `MarkdownPlugin` |
|
|
32
|
+
| `fuzzy` | `rapidfuzz` — `graph.search(..., mode="fuzzy")` |
|
|
33
|
+
| `sitemap-fast` | `lxml` sitemap parser |
|
|
34
|
+
| `mongodb`, `postgresql`, `celery`, `viz`, `articles`, `performance`, `system`, `all` | storage backends, distributed mode and other extras |
|
|
49
35
|
|
|
50
36
|
After installing a browser extra, download the browser once:
|
|
51
37
|
|
|
@@ -146,23 +132,13 @@ so `node.ok` is `False` for error pages.
|
|
|
146
132
|
|
|
147
133
|
`driver=` accepts a name or a driver instance.
|
|
148
134
|
|
|
149
|
-
|
|
150
|
-
|
|
|
151
|
-
|
|
|
152
|
-
| `
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `escalating` | all of them | whatever is installed | http → tls → browser → undetected |
|
|
157
|
-
=======
|
|
158
|
-
| name | tier | needs | notes |
|
|
159
|
-
|--------------|-------------|-----------------------|-------------------------------------------------|
|
|
160
|
-
| `aiohttp` / `http` | http | — | default |
|
|
161
|
-
| `tls` | tls | `[tls]` | Chrome TLS fingerprint, no JavaScript |
|
|
162
|
-
| `playwright` | browser | `[browser]` | real browser; engine chosen by `browser_engine` |
|
|
163
|
-
| `nodriver` | undetected | `[undetected]` | CDP without webdriver |
|
|
164
|
-
| `escalating` | all of them | whatever is installed | http → tls → browser → undetected |
|
|
165
|
-
>>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
|
|
135
|
+
| name | tier | needs | notes |
|
|
136
|
+
| ----------------------------- | ----------- | --------------------- | ----------------------------------------------- |
|
|
137
|
+
| `aiohttp` / `http` / `async` | http | — | default |
|
|
138
|
+
| `tls` / `curl_cffi` | tls | `[tls]` | Chrome TLS fingerprint, no JavaScript |
|
|
139
|
+
| `playwright` | browser | `[browser]` | real browser; engine chosen by `browser_engine` |
|
|
140
|
+
| `nodriver` | undetected | `[undetected]` | CDP without webdriver |
|
|
141
|
+
| `escalating` | all of them | whatever is installed | http → tls → browser → undetected |
|
|
166
142
|
|
|
167
143
|
`escalating` creates tiers lazily: a browser starts only when a cheaper tier is
|
|
168
144
|
blocked. Tiers whose dependency is not installed are skipped.
|
|
@@ -178,13 +154,8 @@ Every response goes through `classify_block()`:
|
|
|
178
154
|
| `none` | normal page |
|
|
179
155
|
| `soft_block` | 429, plain 503, or 200 with an empty body |
|
|
180
156
|
| `challenge` | Cloudflare "Just a moment", `cf-mitigated: challenge`, Incapsula, DDoS-Guard |
|
|
181
|
-
<<<<<<< HEAD
|
|
182
|
-
| `hard_block` | 403 "Access Denied", Akamai `_abck`, DataDome headers |
|
|
183
|
-
| `captcha` | PerimeterX, DataDome captcha, reCAPTCHA / hCaptcha / Turnstile markup |
|
|
184
|
-
=======
|
|
185
157
|
| `hard_block` | 403/401/405 **with** a vendor or "Access Denied" marker (Akamai `_abck`, DataDome/Incapsula headers, `x-iinfo`, Cloudflare `server`) |
|
|
186
|
-
| `captcha` | PerimeterX, DataDome captcha, reCAPTCHA / hCaptcha / Turnstile markup
|
|
187
|
-
>>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
|
|
158
|
+
| `captcha` | PerimeterX, DataDome captcha, reCAPTCHA / hCaptcha / Turnstile markup |
|
|
188
159
|
|
|
189
160
|
Anything other than `none`, and network errors, move the request to the next tier.
|
|
190
161
|
|
|
@@ -254,25 +225,14 @@ policy = EscalationPolicy(
|
|
|
254
225
|
|
|
255
226
|
### Browser engines
|
|
256
227
|
|
|
257
|
-
|
|
258
|
-
The browser tier is stock `PlaywrightDriver`; the _engine_ decides which
|
|
259
|
-
Playwright module and which Chromium binary it launches.
|
|
260
|
-
|
|
261
|
-
| engine | what it is | falls back to |
|
|
262
|
-
| -------------- | ------------------------------------------------------------- | ------------- |
|
|
263
|
-
| `cloakbrowser` | Chromium with fingerprint patches compiled into the binary | `patchright` |
|
|
264
|
-
| `patchright` | Playwright fork without automation leaks (`Runtime.enable` …) | `playwright` |
|
|
265
|
-
| `playwright` | stock | — |
|
|
266
|
-
=======
|
|
267
|
-
The browser tier is `PlaywrightDriver`; the *engine* decides which Playwright
|
|
228
|
+
The browser tier is `PlaywrightDriver`; the _engine_ decides which Playwright
|
|
268
229
|
module and which Chromium binary it launches.
|
|
269
230
|
|
|
270
231
|
| engine | what it is | falls back to |
|
|
271
|
-
|
|
232
|
+
| -------------- | ------------------------------------------------------------- | ------------- |
|
|
272
233
|
| `cloakbrowser` | Chromium with fingerprint patches compiled into the binary | `patchright` |
|
|
273
234
|
| `patchright` | Playwright fork without automation leaks (`Runtime.enable` …) | `playwright` |
|
|
274
235
|
| `playwright` | stock Playwright | — |
|
|
275
|
-
>>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
|
|
276
236
|
|
|
277
237
|
A fallback is logged once at `INFO` with the reason. To get an `ImportError`
|
|
278
238
|
instead of a silent downgrade, set `GC_ENGINE_STRICT=1`, or pass
|
|
@@ -335,6 +295,37 @@ A target is found by `selector`, `text`, `placeholder` or `label` (`nth` picks
|
|
|
335
295
|
among visible matches). The first failing action stops the chain and is written
|
|
336
296
|
to `node.metadata["actions_error"]`.
|
|
337
297
|
|
|
298
|
+
## Controlling a running crawl
|
|
299
|
+
|
|
300
|
+
`async_crawl(..., control_channel=channel)` (and `crawl`, `GraphCrawlerClient.crawl`)
|
|
301
|
+
reads commands from the channel between pages:
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
import graph_crawler as gc
|
|
305
|
+
from graph_crawler.domain.interfaces import AsyncQueueControlChannel, force_visit_command, stop_command
|
|
306
|
+
|
|
307
|
+
channel = AsyncQueueControlChannel()
|
|
308
|
+
channel.send(force_visit_command("https://example.com/hidden-page"))
|
|
309
|
+
graph = await gc.async_crawl("https://example.com", control_channel=channel)
|
|
310
|
+
|
|
311
|
+
# from a plugin, a callback or another task while the crawl runs:
|
|
312
|
+
channel.send(stop_command("enough data")) # graph.metadata["stop_reason"] == "enough data"
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
| command | `data` | effect |
|
|
316
|
+
| -------------- | ----------------------- | ---------------------------------------------- |
|
|
317
|
+
| `STOP` | `reason` (optional) | stops the crawl, `stop_reason` = `reason` |
|
|
318
|
+
| `PAUSE` | — | pauses until `RESUME` or `STOP` |
|
|
319
|
+
| `RESUME` | — | continues a paused crawl |
|
|
320
|
+
| `REPRIORITIZE` | `url`, `priority` | moves a queued URL in the queue |
|
|
321
|
+
| `FORCE_VISIT` | `url` | adds the URL to the queue with top priority |
|
|
322
|
+
|
|
323
|
+
`SPIDER_COMMANDS` is this set. `SKIP_URL`, `CHANGE_STRATEGY`, `SET_DEPTH_LIMIT`,
|
|
324
|
+
`REQUEST_STATE` and `UPDATE_GOAL` are declared in `CrawlCommand` but not executed:
|
|
325
|
+
the spider logs `WARNING Unsupported control command ...` (once per command type
|
|
326
|
+
per crawl) and goes on; they will be removed in the next minor release. A supported
|
|
327
|
+
command without its required `data` fields is also skipped with a `WARNING`.
|
|
328
|
+
|
|
338
329
|
## Plugins and custom nodes
|
|
339
330
|
|
|
340
331
|
A node plugin runs on every fetched page and writes to `user_data`, `metadata`
|
|
@@ -13,7 +13,7 @@ import re
|
|
|
13
13
|
from typing import Any
|
|
14
14
|
from urllib.parse import urlsplit
|
|
15
15
|
|
|
16
|
-
from graph_crawler.domain.entities.node import Node
|
|
16
|
+
from graph_crawler.domain.entities.node import Node, enrich_budget
|
|
17
17
|
from graph_crawler.domain.interfaces.driver import IDriver
|
|
18
18
|
from graph_crawler.domain.interfaces.node_interfaces import IRetainsRawResponse
|
|
19
19
|
from graph_crawler.domain.value_objects.lifecycle import NodeLifecycle
|
|
@@ -627,9 +627,9 @@ class NodeScanner:
|
|
|
627
627
|
if result is not None and result.html is not None:
|
|
628
628
|
html = result.html
|
|
629
629
|
try:
|
|
630
|
-
# asyncio.timeout
|
|
631
|
-
# зовнішнє скасування
|
|
632
|
-
async with
|
|
630
|
+
# enrich_budget (над asyncio.timeout, не wait_for: у Python 3.11 wait_for
|
|
631
|
+
# губить зовнішнє скасування). Черга html-пулу в ліміт не входить.
|
|
632
|
+
async with enrich_budget(self.enrich_timeout):
|
|
633
633
|
links = await node.enrich_node(
|
|
634
634
|
html,
|
|
635
635
|
crawl_context=crawl_context,
|
|
@@ -799,7 +799,7 @@ class NodeScanner:
|
|
|
799
799
|
loop = asyncio.get_running_loop()
|
|
800
800
|
started = loop.time()
|
|
801
801
|
try:
|
|
802
|
-
async with
|
|
802
|
+
async with enrich_budget(self.enrich_timeout):
|
|
803
803
|
return await enrich_single_node(node, result)
|
|
804
804
|
except TimeoutError:
|
|
805
805
|
return (node, self._on_enrich_timeout(node), None)
|
{graph_crawler-5.3.3 → graph_crawler-5.3.4}/graph_crawler/application/use_cases/crawling/spider.py
RENAMED
|
@@ -41,7 +41,9 @@ from graph_crawler.domain.entities.graph import Graph
|
|
|
41
41
|
from graph_crawler.domain.entities.node import Node
|
|
42
42
|
from graph_crawler.domain.events import CrawlerEvent, EventBus, EventType
|
|
43
43
|
from graph_crawler.domain.interfaces.control_channel import (
|
|
44
|
+
SPIDER_COMMANDS,
|
|
44
45
|
AsyncQueueControlChannel,
|
|
46
|
+
ControlMessage,
|
|
45
47
|
CrawlCommand,
|
|
46
48
|
IControlChannel,
|
|
47
49
|
)
|
|
@@ -104,6 +106,7 @@ class GraphSpider(BaseSpider):
|
|
|
104
106
|
self._stop_mode = stop_mode
|
|
105
107
|
self._stop_reason: str | None = None
|
|
106
108
|
self._control_channel = control_channel or AsyncQueueControlChannel()
|
|
109
|
+
self._warned_unsupported_commands: set[CrawlCommand] = set()
|
|
107
110
|
|
|
108
111
|
# Domain-граф — приватне поле, назовні не витікає (публічний API — DTO).
|
|
109
112
|
if graph is not None:
|
|
@@ -745,52 +748,85 @@ class GraphSpider(BaseSpider):
|
|
|
745
748
|
return self._control_channel
|
|
746
749
|
|
|
747
750
|
def process_control_messages(self) -> str | None:
|
|
748
|
-
"""
|
|
749
|
-
Обробити всі команди з control channel.
|
|
751
|
+
"""Виконує всі команди з control channel; повертає причину зупинки, якщо прийшов ``STOP``.
|
|
750
752
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
+
Команда поза ``SPIDER_COMMANDS`` або без обов'язкових полів у ``data`` не виконується
|
|
754
|
+
і дає WARNING (непідтримувана — раз на тип команди за краул).
|
|
753
755
|
"""
|
|
754
756
|
while self._control_channel.has_pending():
|
|
755
757
|
msg = self._control_channel.receive()
|
|
756
758
|
if msg is None:
|
|
757
759
|
break
|
|
758
|
-
|
|
759
760
|
logger.debug("Processing control message: %s from %s", msg.command.value, msg.sender)
|
|
761
|
+
if msg.command not in SPIDER_COMMANDS:
|
|
762
|
+
self._warn_unsupported_command(msg)
|
|
763
|
+
continue
|
|
764
|
+
stop_reason = self._execute_command(msg)
|
|
765
|
+
if stop_reason is not None:
|
|
766
|
+
return stop_reason
|
|
767
|
+
return None
|
|
760
768
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
priority = msg.data.get("priority")
|
|
778
|
-
if url and priority is not None:
|
|
779
|
-
self._scheduler.reprioritize_url(url, priority)
|
|
780
|
-
logger.debug("Reprioritized %s to %s", url, priority)
|
|
781
|
-
|
|
782
|
-
elif msg.command == CrawlCommand.FORCE_VISIT:
|
|
783
|
-
url = msg.data.get("url")
|
|
784
|
-
if url:
|
|
785
|
-
# Додаємо URL в чергу з високим пріоритетом
|
|
786
|
-
node_class = self.config.custom_node_class or Node
|
|
787
|
-
node = node_class(url=url, depth=0, plugin_manager=self.node_plugin_manager)
|
|
788
|
-
self._graph.add_node(node)
|
|
789
|
-
self._scheduler.add_node(node, priority=100)
|
|
790
|
-
logger.debug("Force visit scheduled: %s", url)
|
|
791
|
-
|
|
769
|
+
def _execute_command(self, msg: ControlMessage) -> str | None:
|
|
770
|
+
if msg.command == CrawlCommand.STOP:
|
|
771
|
+
reason = msg.data.get("reason", "Stop requested via control channel")
|
|
772
|
+
logger.info("Stop command received: %s", reason)
|
|
773
|
+
return reason
|
|
774
|
+
if msg.command == CrawlCommand.PAUSE:
|
|
775
|
+
self._state = CrawlerState.PAUSED
|
|
776
|
+
logger.info("Pause command received")
|
|
777
|
+
elif msg.command == CrawlCommand.RESUME:
|
|
778
|
+
if self._state == CrawlerState.PAUSED:
|
|
779
|
+
self._state = CrawlerState.RUNNING
|
|
780
|
+
logger.info("Resume command received")
|
|
781
|
+
elif msg.command == CrawlCommand.REPRIORITIZE:
|
|
782
|
+
self._reprioritize(msg)
|
|
783
|
+
elif msg.command == CrawlCommand.FORCE_VISIT:
|
|
784
|
+
self._force_visit(msg)
|
|
792
785
|
return None
|
|
793
786
|
|
|
787
|
+
def _reprioritize(self, msg: ControlMessage) -> None:
|
|
788
|
+
url = msg.data.get("url")
|
|
789
|
+
priority = msg.data.get("priority")
|
|
790
|
+
if not url or priority is None:
|
|
791
|
+
self._warn_invalid_command(msg, "requires data['url'] and data['priority']")
|
|
792
|
+
return
|
|
793
|
+
self._scheduler.reprioritize_url(url, priority)
|
|
794
|
+
logger.debug("Reprioritized %s to %s", url, priority)
|
|
795
|
+
|
|
796
|
+
def _force_visit(self, msg: ControlMessage) -> None:
|
|
797
|
+
url = msg.data.get("url")
|
|
798
|
+
if not url:
|
|
799
|
+
self._warn_invalid_command(msg, "requires data['url']")
|
|
800
|
+
return
|
|
801
|
+
node_class = self.config.custom_node_class or Node
|
|
802
|
+
node = node_class(url=url, depth=0, plugin_manager=self.node_plugin_manager)
|
|
803
|
+
self._graph.add_node(node)
|
|
804
|
+
self._scheduler.add_node(node, priority=100)
|
|
805
|
+
logger.debug("Force visit scheduled: %s", url)
|
|
806
|
+
|
|
807
|
+
def _warn_unsupported_command(self, msg: ControlMessage) -> None:
|
|
808
|
+
if msg.command in self._warned_unsupported_commands:
|
|
809
|
+
logger.debug("Unsupported control command ignored: %s from %s", msg.command.value, msg.sender)
|
|
810
|
+
return
|
|
811
|
+
self._warned_unsupported_commands.add(msg.command)
|
|
812
|
+
supported = ", ".join(sorted(command.value for command in SPIDER_COMMANDS))
|
|
813
|
+
logger.warning(
|
|
814
|
+
"Unsupported control command %r from %s is ignored (supported: %s)",
|
|
815
|
+
msg.command.value,
|
|
816
|
+
msg.sender,
|
|
817
|
+
supported,
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
@staticmethod
|
|
821
|
+
def _warn_invalid_command(msg: ControlMessage, problem: str) -> None:
|
|
822
|
+
logger.warning(
|
|
823
|
+
"Control command %r from %s is ignored: %s, got %r",
|
|
824
|
+
msg.command.value,
|
|
825
|
+
msg.sender,
|
|
826
|
+
problem,
|
|
827
|
+
msg.data,
|
|
828
|
+
)
|
|
829
|
+
|
|
794
830
|
async def check_control_channel(self) -> str | None:
|
|
795
831
|
"""Асинхронна обгортка над ``process_control_messages``."""
|
|
796
832
|
return self.process_control_messages()
|