graph-crawler 5.3.5__tar.gz → 5.3.7__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.5 → graph_crawler-5.3.7}/CHANGELOG.md +37 -0
- {graph_crawler-5.3.5/graph_crawler.egg-info → graph_crawler-5.3.7}/PKG-INFO +1 -1
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/sitemap_parser.py +84 -20
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/sitemap_spider.py +47 -3
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/data/backends/sqlite.py +42 -17
- {graph_crawler-5.3.5 → graph_crawler-5.3.7/graph_crawler.egg-info}/PKG-INFO +1 -1
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/pyproject.toml +1 -1
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/LICENSE +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/MANIFEST.in +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/README.md +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/__version__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/ai/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/_core.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/_distributed.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/_shared.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/_sitemap_distributed.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/async_.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/cli.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/client/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/client/client.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/history_manager.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/project_init.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/server/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/server/crawl_monitor.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/server/dashboard.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/server/rest_api.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/server/webhooks.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/server/websocket_manager.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/stats_collector.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/api/sync.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/bootstrap.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/context/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/context/dependency_registry.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/context/graph_context.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/context/merge_context.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/dto/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/dto/edge_dto.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/dto/graph_dto.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/dto/mappers/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/dto/mappers/edge_mapper.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/dto/mappers/graph_mapper.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/dto/mappers/node_data_codec.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/dto/mappers/node_mapper.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/dto/node_dto.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/dto/utils.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/application_container.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/driver_factory.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/exporters/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/exporters/base_exporter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/exporters/csv_exporter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/exporters/edge_exporter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/exporters/excel_exporter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/exporters/jsonl_exporter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/exporters/node_exporter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/exporters/parquet_exporter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/exporters/sql_exporter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/finalize.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/low_memory_graph_saver.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/resource_budget.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/scheduling/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/scheduling/rule_based_scheduler.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/storage_factory.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/adaptive_throttler.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/async_bridge.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/base_spider.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/celery_batch_spider.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/celery_spider.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/checkpoint.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/crawl_coordinator.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/dead_letter_queue.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/domain_rate_limiter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/filters/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/filters/base.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/filters/domain_filter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/filters/domain_patterns.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/filters/path_filter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/incremental_strategy.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/link_processor.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/multiprocess_spider.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/node_scanner.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/parsers/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/parsers/base.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/parsers/html_parser.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/progress_tracker.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/scheduler.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/serialization_mixin.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/sitemap_processor.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/spider.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/spider_lifecycle.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/crawling/worker_pool.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/graph_export.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/graph_merge/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/graph_merge/graph_operations.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/use_cases/graph_merge/merge_strategies.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/data/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/data/backends/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/data/backends/memory.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/data/interfaces.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/context/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/context/crawl_context.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/entities/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/entities/edge.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/entities/edge_analysis.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/entities/graph.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/entities/graph_operations.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/entities/graph_statistics.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/entities/merge_strategies.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/entities/node.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/entities/redirect_mode.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/entities/registries.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/entities/sitemap_node.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/entities/strategies.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/events/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/events/base.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/events/crawl.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/events/edge.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/events/event_bus.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/events/events.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/events/fetch.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/events/idempotent_subscriber.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/events/legacy_type_map.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/events/llm.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/events/node.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/events/plugin.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/events/scheduler.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/events/storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/adapter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/control_channel.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/crawl_context.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/distributed_spider.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/driver.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/event_bus.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/exporters.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/filter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/merge_context.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/node_interfaces.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/parser.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/plugin_manager.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/processor.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/scanner.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/scheduler.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/scheduling/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/scheduling/frontier.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/scheduling/scheduler.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/spider.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/stop_condition.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/storage/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/storage/base.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/storage/eviction.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/interfaces/storage/queue.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/adjacency_maintenance.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/cycle_detection.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/degree_ranking.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/depth_context.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/display_title.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/duplicate_detection.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/duplicate_selector.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/duplicate_stats.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/edge_filter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/edge_statistics.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/eviction_policy.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/graph_query_cascade.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/graph_statistics.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/graph_traversal.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/language_versions.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/lsh_index.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/minimap_builder.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/node_content_resolver.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/node_view_builder.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/pagination_guards.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/predicate_ops.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/schema_filter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/search_haystack.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/simhash_clustering.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/simhash_fallback.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/similarity_ranking.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/text_search.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/url_clustering.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/url_deduplication.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/url_normalizer.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/url_rule_matcher.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/services/url_tree_builder.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/value_objects/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/value_objects/configs.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/value_objects/domain_patterns.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/value_objects/fetch_options.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/value_objects/html_document.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/value_objects/lifecycle.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/value_objects/models.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/value_objects/page_actions.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/value_objects/redirect_hop.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/value_objects/scheduling/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/value_objects/scheduling/crawl_request.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/value_objects/scheduling/retry_policy.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/value_objects/settings.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/value_objects/url_normalization.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/domain/value_objects/views.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/base.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/cache_middleware.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/chain.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/error_recovery_middleware.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/logging_middleware.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/proxy_health.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/proxy_middleware.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/proxy_models.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/proxy_selection.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/rate_limit_middleware.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/request_response_middleware.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/retry_middleware.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/robots_cache.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/robots_middleware.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/robots_validator.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/middleware/user_agent_middleware.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/builtin/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/builtin/stats_export_plugin.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/crawl_engine/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/crawl_engine/base.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/crawl_engine/priority_provider.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/crawl_engine/smart_crawl.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/engine/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/engine/anti_bot_detection.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/engine/anti_bot_playwright.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/engine/anti_bot_scripts.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/engine/anti_bot_stealth.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/engine/base.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/engine/captcha/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/engine/captcha/detector.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/engine/captcha/models.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/engine/captcha/plugin.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/engine/captcha/services.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/base.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/content_extractors/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/content_extractors/base.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/content_extractors/goose3_extractor.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/content_extractors/newspaper_extractor.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/content_extractors/plugin.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/content_extractors/readability_extractor.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/defaults.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/extractors/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/extractors/email_extractor.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/extractors/phone_extractor.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/extractors/price_extractor.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/links.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/markdown_plugin.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/metadata.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/smart_page_finder.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/structured_data/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/structured_data/constants.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/structured_data/exceptions.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/structured_data/extractor.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/structured_data/options.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/structured_data/parsers/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/structured_data/parsers/base.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/structured_data/parsers/jsonld.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/structured_data/parsers/microdata.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/structured_data/parsers/opengraph.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/structured_data/parsers/rdfa.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/structured_data/parsers/twitter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/structured_data/plugin.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/structured_data/result.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/text.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/node/url_normalizer.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/extensions/plugins/registry.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/adapters/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/adapters/base.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/adapters/selectolax_adapter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/messaging/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/messaging/celery_app.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/messaging/celery_batch.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/messaging/celery_job_task.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/messaging/celery_unified.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/messaging/config.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/messaging/easy_crawler.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/messaging/worker_api.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/auto_storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/base.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/eviction.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/graph_repository.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/json/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/json_storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/lmdb_eviction_storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/memory/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/memory_dedup_store.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/memory_frontier.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/memory_politeness_store.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/memory_storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/mongodb_storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/naming_strategy.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/postgresql_storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/protocols/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/redis_dedup_store.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/redis_politeness_store.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/repository.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/sqlite/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/sqlite_eviction_storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/sqlite_storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/unified/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/unified/file_job_storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/unified/file_queue_storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/unified/memory_job_storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/unified/memory_queue_storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/unified/postgresql_job_storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/unified/postgresql_queue_storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/persistence/unified/unified_storage.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/async_http/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/async_http/config.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/async_http/context.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/async_http/driver.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/async_http/driver_curl_cffi.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/async_http/plugins/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle_adaptive.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/async_http/plugins/headers.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/async_http/plugins/http_cache.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/async_http/plugins/rate_limiter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/async_http/plugins/retry.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/async_http/stages.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/base.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/base_plugin.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/compression/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/compression/strategy.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/connection_pool.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/context.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/core/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/core/base_async.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/core/body_decoder.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/core/mixins.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/core/redirect_utils.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/escalation.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/factory.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/adaptive_semaphore.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/batch_scheduler.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/browser_factory.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/browser_profile.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/config.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/context.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/driver.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/engine.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/fetch_metrics.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/net/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/net/geoip.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/net/proxy_pool.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/net/rate_limiter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/page_actions.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/page_fetch.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/plugins/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/plugins/_network_record.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_detector.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_solver.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/plugins/cookie_consent.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/plugins/form_filler.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/plugins/human_bezier.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/plugins/network_log.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/plugins/ram_adaptive.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/plugins/screenshot.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/plugins/user_profile.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/pool_lifecycle.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/pooled_driver.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/stages.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/playwright/stealth_hints.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/plugin_manager.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/protocols.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/undetected/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/infrastructure/transport/undetected/async_driver.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/native/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/native/bloom_filter.pyx +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/native/html_parser.pyx +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/native/setup.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/native/url_utils.pyx +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/decorators/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/decorators/cache.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/decorators/log.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/decorators/retry.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/decorators/timing.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/error_tracing.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/extensions/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/extensions/memory_guard.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/extensions/state_manager.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/listeners/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/listeners/base.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/listeners/base_metrics_listener.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/listeners/crawl_listener.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/listeners/dlq_listener.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/listeners/error_listener.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/listeners/logging_listener.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/listeners/metrics_listener.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/listeners/node_listener.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/listeners/plugin_listener.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/listeners/storage_listener.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/listeners/url_listener.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/metrics/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/metrics/memory_profiler.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/metrics/metrics_collector.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/metrics/prometheus_metrics.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/metrics_core.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/structured_logging.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/observability/tracing.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/optimizations/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/optimizations/generators.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/optimizations/memory_slots.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/optimizations/simhash_numba.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/py.typed +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/constants.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/dto/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/dto/edge_dto.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/dto/graph_dto.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/dto/node_dto.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/error_handling/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/error_handling/error_handler.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/exceptions.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/platform/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/platform/adaptive_resolver.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/platform/config.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/platform/detector.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/platform/diagnostics.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/platform/metrics.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/security/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/security/url_sanitizer.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/security/url_validator.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/async_compat.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/bloom_filter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/captcha/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/captcha/base.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/captcha/manager.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/celery_config.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/celery_helpers.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/cgroup_limits.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/content_decoder.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/distributed_rate_limiter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/distributed_rate_limiter_backends.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/dns_cache.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/enterprise_ua_generator.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/event_publisher_mixin.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/fast_json.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/fingerprint.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/fingerprint_data.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/fingerprint_generators.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/fingerprint_profile.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/fingerprint_stealth.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/html_utils.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/id_generator.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/isolated_executor.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/memory_optimizer.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/rate_limiter.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/regex_validator.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/shadow_dom.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/text_utils.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/url_patterns.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/url_utils.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/user_agent_rotator.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/validation_helpers.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/visualization.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/shared/utils/visualization_core.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/tools/__init__.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/tools/db_cleanup.py +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler.egg-info/SOURCES.txt +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler.egg-info/dependency_links.txt +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler.egg-info/entry_points.txt +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler.egg-info/requires.txt +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler.egg-info/top_level.txt +0 -0
- {graph_crawler-5.3.5 → graph_crawler-5.3.7}/setup.cfg +0 -0
|
@@ -2,6 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 5.3.7 — 2026-10
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **A crawl worker no longer dies on a canonical redirect while another SQLite statement is running.**
|
|
10
|
+
`SQLiteBackend._sync_execute` treated `sqlite3.OperationalError: cannot commit transaction - SQL statements in
|
|
11
|
+
progress` as a failed write, although the DELETE/UPDATE had already run in the open transaction of the shared
|
|
12
|
+
connection. The fallback `_run_async` then ran the coroutine on a new loop in a thread (`asyncio.Lock ... is bound to
|
|
13
|
+
a different event loop`) and, catching that `RuntimeError`, called `asyncio.run()` inside the running loop — the
|
|
14
|
+
worker failed (`Graph.remove_node` → `delete_node_sync`). Now:
|
|
15
|
+
- a commit refused because of in-progress statements is deferred: the change stays in the open transaction and is
|
|
16
|
+
committed by the next async commit or by `close()`;
|
|
17
|
+
- `_run_async` inside a running loop schedules the write as a task on that loop (errors are logged) instead of
|
|
18
|
+
blocking on a foreign loop; outside a loop it still runs synchronously;
|
|
19
|
+
- `close()` awaits scheduled writes and commits before closing.
|
|
20
|
+
- Tests: `tests/test_sqlite_sync_write_in_loop.py`.
|
|
21
|
+
|
|
22
|
+
## 5.3.6 — 2026-06
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **A timeout on robots.txt / sitemap stops the sitemap phase of the host (1.6, daxx.com).** A down origin behind
|
|
27
|
+
Cloudflare does not always answer 52x: robots.txt came back after ~20 s and every sitemap request hung until the
|
|
28
|
+
client timeout, then the default candidates (`sitemap.xml`, `sitemap_index.xml`) and the driver's retries/tier
|
|
29
|
+
escalation repeated it (80–180 s per domain). Now:
|
|
30
|
+
- `SitemapParser._fetch` bounds the whole request by `SitemapParser.timeout` (covers driver retries and tier
|
|
31
|
+
escalation); a timeout is an answer "origin does not respond", not a reason to try again;
|
|
32
|
+
- the first timeout (no status + timeout error, incl. the driver's `tier timeout after …`) or CF 520–527 marks the
|
|
33
|
+
host `ORIGIN_DOWN` (`SitemapParser.mark_origin_down` / `origin_down_reason`); no further request to that host in
|
|
34
|
+
this sitemap phase — no default candidates, no nested sitemaps, no retries;
|
|
35
|
+
- `result["origin_down_reason"]` = `"timeout"` | `"cf_52x"`; `SitemapSpider.origin_down`,
|
|
36
|
+
`get_stats()["origin_down"]`, `SITEMAP_CRAWL_COMPLETED.data["origin_down"]`, and the robots.txt node gets
|
|
37
|
+
`error_message = "origin_down: <reason>"`. A spider-level `SITEMAP_PARSE_TIMEOUT` also marks the host down.
|
|
38
|
+
- Non-timeout network errors (DNS, connection refused) keep the old behaviour (default candidates are tried).
|
|
39
|
+
Live: daxx.com sitemap phase 80 s → 50 s (robots ~20 s + one 30 s timeout), healthy sites unchanged.
|
|
40
|
+
- Tests: `tests/test_sitemap_origin_down_timeout.py` (8).
|
|
41
|
+
|
|
5
42
|
## 5.3.5 — 2026-06
|
|
6
43
|
|
|
7
44
|
### Fixed
|
|
@@ -64,6 +64,12 @@ _MSG_UNKNOWN_SITEMAP_TYPE = "Unknown sitemap type: %s"
|
|
|
64
64
|
# тому такі файли читаються напряму через aiohttp навіть за наявності драйвера.
|
|
65
65
|
_BINARY_CONTENT_TYPES = ("gzip", "octet-stream")
|
|
66
66
|
|
|
67
|
+
# Причини ORIGIN_DOWN: origin не відповідає — sitemap-фаза хоста зупиняється без ретраїв.
|
|
68
|
+
ORIGIN_DOWN_CF_5XX = "cf_52x"
|
|
69
|
+
ORIGIN_DOWN_TIMEOUT = "timeout"
|
|
70
|
+
# Як різні транспорти формулюють таймаут у ``FetchResponse.error`` (aiohttp, curl_cffi, тир-ланцюг).
|
|
71
|
+
_TIMEOUT_ERROR_MARKERS = ("timeout", "timed out")
|
|
72
|
+
|
|
67
73
|
|
|
68
74
|
@dataclass
|
|
69
75
|
class _Fetched:
|
|
@@ -154,6 +160,10 @@ class SitemapParser:
|
|
|
154
160
|
# щоб уникнути шуму, коли той самий сайт запитується декілька разів.
|
|
155
161
|
self._logged_domain_redirects: set[tuple[str, str]] = set()
|
|
156
162
|
|
|
163
|
+
# Хости з лежачим origin (CF 52x або таймаут запиту): host → причина. Перший такий
|
|
164
|
+
# сигнал зупиняє sitemap-фазу хоста — далі жодного запиту, ні ретраю, ні кандидата.
|
|
165
|
+
self._down_hosts: dict[str, str] = {}
|
|
166
|
+
|
|
157
167
|
self._xml_executor = ThreadPoolExecutor(
|
|
158
168
|
max_workers=4, thread_name_prefix=f"xml_parser_{id(self)}_"
|
|
159
169
|
)
|
|
@@ -191,6 +201,18 @@ class SitemapParser:
|
|
|
191
201
|
return url.lower().endswith(".gz") or any(t in content_type for t in _BINARY_CONTENT_TYPES)
|
|
192
202
|
|
|
193
203
|
async def _fetch(self, url: str) -> _Fetched:
|
|
204
|
+
"""robots.txt/sitemap з верхньою межею ``self.timeout`` на весь запит.
|
|
205
|
+
|
|
206
|
+
Межа покриває і ретраї, і ескалацію тирів драйвера: таймаут — це вже відповідь
|
|
207
|
+
«origin не відповідає», а не привід пробувати ще раз (див. ``ORIGIN_DOWN``).
|
|
208
|
+
"""
|
|
209
|
+
try:
|
|
210
|
+
async with asyncio.timeout(self.timeout):
|
|
211
|
+
return await self._fetch_unbounded(url)
|
|
212
|
+
except TimeoutError:
|
|
213
|
+
return _Fetched(status=None, final_url=url, error=f"timeout after {self.timeout}s")
|
|
214
|
+
|
|
215
|
+
async def _fetch_unbounded(self, url: str) -> _Fetched:
|
|
194
216
|
"""robots.txt/sitemap через ``IDriver`` (тир-ланцюг) або власний aiohttp.
|
|
195
217
|
|
|
196
218
|
``FetchResponse.html`` — текст: бінарні ``.gz`` (за URL або Content-Type)
|
|
@@ -263,20 +285,15 @@ class SitemapParser:
|
|
|
263
285
|
- ``sitemap_indexes``: sitemap-index URL (якщо є).
|
|
264
286
|
"""
|
|
265
287
|
result = {"sitemap_urls": [], "urls": [], "sitemap_indexes": []}
|
|
288
|
+
robots_url = urljoin(base_url, "/robots.txt")
|
|
289
|
+
if self._mark_if_origin_down_cached(robots_url, result):
|
|
290
|
+
return result
|
|
266
291
|
|
|
267
292
|
try:
|
|
268
|
-
robots_url = urljoin(base_url, "/robots.txt")
|
|
269
293
|
fetched = await self._fetch(robots_url)
|
|
270
294
|
|
|
271
|
-
if self.
|
|
272
|
-
|
|
273
|
-
"ORIGIN_DOWN: %s returned %s (Cloudflare origin error) — "
|
|
274
|
-
"stopping sitemap phase without retries",
|
|
275
|
-
robots_url,
|
|
276
|
-
fetched.status,
|
|
277
|
-
)
|
|
278
|
-
result["origin_down"] = True
|
|
279
|
-
return result
|
|
295
|
+
if self._check_origin(robots_url, fetched):
|
|
296
|
+
return self._with_origin_down(robots_url, result)
|
|
280
297
|
|
|
281
298
|
if fetched.status != 200 or fetched.body is None:
|
|
282
299
|
logger.warning("robots.txt not found at %s", robots_url)
|
|
@@ -306,6 +323,7 @@ class SitemapParser:
|
|
|
306
323
|
if isinstance(sitemap_data, dict):
|
|
307
324
|
if sitemap_data.get("origin_down"):
|
|
308
325
|
result["origin_down"] = True
|
|
326
|
+
result["origin_down_reason"] = sitemap_data.get("origin_down_reason")
|
|
309
327
|
result["urls"].extend(sitemap_data.get("urls", []))
|
|
310
328
|
result["sitemap_indexes"].extend(sitemap_data.get("sitemap_indexes", []))
|
|
311
329
|
result.setdefault("lastmod", {}).update(sitemap_data.get("lastmod", {}))
|
|
@@ -315,7 +333,8 @@ class SitemapParser:
|
|
|
315
333
|
|
|
316
334
|
except Exception as e:
|
|
317
335
|
logger.error(_MSG_ROBOTS_READ_ERROR, base_url, e)
|
|
318
|
-
|
|
336
|
+
if not self._mark_if_origin_down_cached(robots_url, result):
|
|
337
|
+
result = await self._try_default_sitemaps_async(base_url, result)
|
|
319
338
|
|
|
320
339
|
result["urls"] = list(set(result["urls"]))
|
|
321
340
|
result["sitemap_indexes"] = list(set(result["sitemap_indexes"]))
|
|
@@ -335,6 +354,7 @@ class SitemapParser:
|
|
|
335
354
|
sitemap_data = await self.parse_sitemap_async(sitemap_url)
|
|
336
355
|
if sitemap_data.get("origin_down"):
|
|
337
356
|
result["origin_down"] = True
|
|
357
|
+
result["origin_down_reason"] = sitemap_data.get("origin_down_reason")
|
|
338
358
|
break
|
|
339
359
|
if sitemap_data["urls"] or sitemap_data["sitemap_indexes"]:
|
|
340
360
|
result["sitemap_urls"].append(sitemap_url)
|
|
@@ -367,19 +387,14 @@ class SitemapParser:
|
|
|
367
387
|
if cached is not None:
|
|
368
388
|
logger.debug("Sitemap cache hit: %s", sitemap_url)
|
|
369
389
|
return cached
|
|
390
|
+
if self._mark_if_origin_down_cached(sitemap_url, result):
|
|
391
|
+
return result
|
|
370
392
|
|
|
371
393
|
try:
|
|
372
394
|
fetched = await self._fetch(sitemap_url)
|
|
373
395
|
|
|
374
|
-
if self.
|
|
375
|
-
|
|
376
|
-
"ORIGIN_DOWN: sitemap %s returned %s (Cloudflare origin error) — "
|
|
377
|
-
"no retries",
|
|
378
|
-
sitemap_url,
|
|
379
|
-
fetched.status,
|
|
380
|
-
)
|
|
381
|
-
result["origin_down"] = True
|
|
382
|
-
return result
|
|
396
|
+
if self._check_origin(sitemap_url, fetched):
|
|
397
|
+
return self._with_origin_down(sitemap_url, result)
|
|
383
398
|
|
|
384
399
|
if fetched.status not in (200, 301, 302) or fetched.body is None:
|
|
385
400
|
logger.warning(
|
|
@@ -562,6 +577,55 @@ class SitemapParser:
|
|
|
562
577
|
return raw.split()[0]
|
|
563
578
|
return None
|
|
564
579
|
|
|
580
|
+
def origin_down_reason(self, url: str) -> str | None:
|
|
581
|
+
"""Причина ORIGIN_DOWN для хоста ``url`` (``cf_52x``/``timeout``) або ``None``."""
|
|
582
|
+
return self._down_hosts.get(urlparse(url).hostname or "")
|
|
583
|
+
|
|
584
|
+
def mark_origin_down(self, url: str, reason: str, detail: str | int | None = None) -> None:
|
|
585
|
+
"""Позначає origin хоста ``url`` лежачим: решта sitemap-фази хоста — без запитів."""
|
|
586
|
+
host = urlparse(url).hostname or ""
|
|
587
|
+
if not host or host in self._down_hosts:
|
|
588
|
+
return
|
|
589
|
+
self._down_hosts[host] = reason
|
|
590
|
+
logger.warning(
|
|
591
|
+
"ORIGIN_DOWN: %s (%s, %s on %s) — stopping sitemap phase for host without retries",
|
|
592
|
+
host,
|
|
593
|
+
reason,
|
|
594
|
+
detail,
|
|
595
|
+
url,
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
def _check_origin(self, url: str, fetched: _Fetched) -> bool:
|
|
599
|
+
"""``True`` і позначка хоста, якщо відповідь каже «origin лежить» (CF 52x або таймаут)."""
|
|
600
|
+
if self._is_origin_down(fetched.status):
|
|
601
|
+
self.mark_origin_down(url, ORIGIN_DOWN_CF_5XX, fetched.status)
|
|
602
|
+
return True
|
|
603
|
+
if self._is_timeout(fetched):
|
|
604
|
+
self.mark_origin_down(url, ORIGIN_DOWN_TIMEOUT, fetched.error)
|
|
605
|
+
return True
|
|
606
|
+
return False
|
|
607
|
+
|
|
608
|
+
def _with_origin_down(self, url: str, result: dict) -> dict:
|
|
609
|
+
result["origin_down"] = True
|
|
610
|
+
result["origin_down_reason"] = self.origin_down_reason(url)
|
|
611
|
+
return result
|
|
612
|
+
|
|
613
|
+
def _mark_if_origin_down_cached(self, url: str, result: dict) -> bool:
|
|
614
|
+
"""Хост уже позначено лежачим — віддаємо ``origin_down`` без запиту."""
|
|
615
|
+
if self.origin_down_reason(url) is None:
|
|
616
|
+
return False
|
|
617
|
+
logger.debug("ORIGIN_DOWN: skipping %s (host already down)", url)
|
|
618
|
+
self._with_origin_down(url, result)
|
|
619
|
+
return True
|
|
620
|
+
|
|
621
|
+
@staticmethod
|
|
622
|
+
def _is_timeout(fetched: _Fetched) -> bool:
|
|
623
|
+
"""Таймаут без відповіді сервера: статусу немає, у помилці — ознака таймауту."""
|
|
624
|
+
if fetched.status is not None or not fetched.error:
|
|
625
|
+
return False
|
|
626
|
+
error = fetched.error.lower()
|
|
627
|
+
return any(marker in error for marker in _TIMEOUT_ERROR_MARKERS)
|
|
628
|
+
|
|
565
629
|
@staticmethod
|
|
566
630
|
def _is_origin_down(status: int | None) -> bool:
|
|
567
631
|
"""``True`` для Cloudflare-origin помилок 520–527 (origin недоступний).
|
|
@@ -23,7 +23,10 @@ from graph_crawler.application.use_cases.crawling.base_spider import (
|
|
|
23
23
|
BaseSpider,
|
|
24
24
|
CrawlerState,
|
|
25
25
|
)
|
|
26
|
-
from graph_crawler.application.use_cases.crawling.sitemap_parser import
|
|
26
|
+
from graph_crawler.application.use_cases.crawling.sitemap_parser import (
|
|
27
|
+
ORIGIN_DOWN_TIMEOUT,
|
|
28
|
+
SitemapParser,
|
|
29
|
+
)
|
|
27
30
|
from graph_crawler.application.use_cases.crawling.sitemap_processor import (
|
|
28
31
|
SitemapProcessor,
|
|
29
32
|
)
|
|
@@ -127,6 +130,8 @@ class SitemapSpider(BaseSpider):
|
|
|
127
130
|
|
|
128
131
|
# Зберігаємо вже оброблені sitemap URLs для детекції циклів
|
|
129
132
|
self._processed_sitemaps: set[str] = set()
|
|
133
|
+
# Причина ORIGIN_DOWN (cf_52x/timeout), якщо sitemap-фазу зупинено через лежачий origin.
|
|
134
|
+
self.origin_down: str | None = None
|
|
130
135
|
|
|
131
136
|
logger.info(
|
|
132
137
|
f"SitemapSpider (async) initialized: "
|
|
@@ -171,7 +176,7 @@ class SitemapSpider(BaseSpider):
|
|
|
171
176
|
sitemap_data = await self._parse_robots_txt(robots_url)
|
|
172
177
|
|
|
173
178
|
# Створюємо Node для robots.txt
|
|
174
|
-
self.processor.create_robots_node(
|
|
179
|
+
robots_node = self.processor.create_robots_node(
|
|
175
180
|
url=robots_url,
|
|
176
181
|
sitemap_urls=sitemap_data.get("sitemap_urls", []),
|
|
177
182
|
error=sitemap_data.get("error"),
|
|
@@ -179,7 +184,13 @@ class SitemapSpider(BaseSpider):
|
|
|
179
184
|
|
|
180
185
|
# Обробляємо кожен знайдений sitemap
|
|
181
186
|
sitemap_urls = sitemap_data.get("sitemap_urls", [])
|
|
182
|
-
if
|
|
187
|
+
if sitemap_data.get("origin_down"):
|
|
188
|
+
logger.warning(
|
|
189
|
+
"Sitemap phase stopped for %s: ORIGIN_DOWN (%s)",
|
|
190
|
+
self.config.url,
|
|
191
|
+
sitemap_data.get("origin_down_reason"),
|
|
192
|
+
)
|
|
193
|
+
elif sitemap_urls:
|
|
183
194
|
# Сортуємо за пріоритетом якщо є url_rules
|
|
184
195
|
if self.url_rules:
|
|
185
196
|
sitemap_urls = self._sort_sitemaps_by_priority(sitemap_urls)
|
|
@@ -198,6 +209,10 @@ class SitemapSpider(BaseSpider):
|
|
|
198
209
|
robots_url,
|
|
199
210
|
)
|
|
200
211
|
|
|
212
|
+
self.origin_down = self._origin_down_reason(robots_url, sitemap_urls)
|
|
213
|
+
if self.origin_down and robots_node is not None:
|
|
214
|
+
robots_node.error_message = f"origin_down: {self.origin_down}"
|
|
215
|
+
|
|
201
216
|
# Завершення
|
|
202
217
|
duration = time.time() - start_time
|
|
203
218
|
stats = self.graph.get_stats()
|
|
@@ -238,6 +253,7 @@ class SitemapSpider(BaseSpider):
|
|
|
238
253
|
"sitemaps_processed": self.sitemaps_processed,
|
|
239
254
|
"urls_extracted": self.urls_extracted,
|
|
240
255
|
"duration": duration,
|
|
256
|
+
"origin_down": self.origin_down,
|
|
241
257
|
},
|
|
242
258
|
)
|
|
243
259
|
)
|
|
@@ -282,6 +298,8 @@ class SitemapSpider(BaseSpider):
|
|
|
282
298
|
return {
|
|
283
299
|
"sitemap_urls": result.get("sitemap_urls", []),
|
|
284
300
|
"error": None,
|
|
301
|
+
"origin_down": bool(result.get("origin_down")),
|
|
302
|
+
"origin_down_reason": result.get("origin_down_reason"),
|
|
285
303
|
}
|
|
286
304
|
|
|
287
305
|
except Exception as e:
|
|
@@ -477,6 +495,11 @@ class SitemapSpider(BaseSpider):
|
|
|
477
495
|
self.sitemaps_skipped += 1
|
|
478
496
|
return
|
|
479
497
|
|
|
498
|
+
if self._parser_down(sitemap_url):
|
|
499
|
+
logger.info("Skipping sitemap (ORIGIN_DOWN): %s", sitemap_url)
|
|
500
|
+
self.sitemaps_skipped += 1
|
|
501
|
+
return
|
|
502
|
+
|
|
480
503
|
logger.info("Processing sitemap: %s (depth=%s, priority=%s)", sitemap_url, depth, priority)
|
|
481
504
|
|
|
482
505
|
try:
|
|
@@ -493,6 +516,9 @@ class SitemapSpider(BaseSpider):
|
|
|
493
516
|
logger.warning(
|
|
494
517
|
"Timeout parsing sitemap (%ss): %s", SITEMAP_PARSE_TIMEOUT, sitemap_url
|
|
495
518
|
)
|
|
519
|
+
mark = getattr(self.parser, "mark_origin_down", None)
|
|
520
|
+
if mark is not None:
|
|
521
|
+
mark(sitemap_url, ORIGIN_DOWN_TIMEOUT, f"parse timeout {SITEMAP_PARSE_TIMEOUT}s")
|
|
496
522
|
self.processor.create_error_node(
|
|
497
523
|
url=sitemap_url,
|
|
498
524
|
parent_url=parent_url,
|
|
@@ -500,6 +526,9 @@ class SitemapSpider(BaseSpider):
|
|
|
500
526
|
depth=depth,
|
|
501
527
|
)
|
|
502
528
|
return
|
|
529
|
+
if sitemap_data.get("origin_down"):
|
|
530
|
+
self.sitemaps_skipped += 1
|
|
531
|
+
return
|
|
503
532
|
has_nested_sitemaps = len(sitemap_data.get("sitemap_indexes", [])) > 0
|
|
504
533
|
has_urls = len(sitemap_data.get("urls", [])) > 0
|
|
505
534
|
|
|
@@ -594,8 +623,23 @@ class SitemapSpider(BaseSpider):
|
|
|
594
623
|
stats["sitemaps_skipped"] = self.sitemaps_skipped
|
|
595
624
|
stats["urls_extracted"] = self.urls_extracted
|
|
596
625
|
stats["url_rules_count"] = len(self.url_rules)
|
|
626
|
+
stats["origin_down"] = self.origin_down
|
|
597
627
|
return stats
|
|
598
628
|
|
|
629
|
+
def _origin_down_reason(self, robots_url: str, sitemap_urls: list[str]) -> str | None:
|
|
630
|
+
"""Перша причина ORIGIN_DOWN серед хоста сайту та хостів його sitemap-ів."""
|
|
631
|
+
for url in (robots_url, *sitemap_urls):
|
|
632
|
+
reason = self._parser_down(url)
|
|
633
|
+
if reason:
|
|
634
|
+
return reason
|
|
635
|
+
return None
|
|
636
|
+
|
|
637
|
+
def _parser_down(self, url: str) -> str | None:
|
|
638
|
+
"""ORIGIN_DOWN хоста від парсера; інжектований парсер без цього API — ``None``."""
|
|
639
|
+
fn = getattr(self.parser, "origin_down_reason", None)
|
|
640
|
+
reason = fn(url) if fn is not None else None
|
|
641
|
+
return reason if isinstance(reason, str) else None
|
|
642
|
+
|
|
599
643
|
async def close(self) -> None:
|
|
600
644
|
"""Async закриває ресурси Spider."""
|
|
601
645
|
await self.driver.close()
|
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import logging
|
|
12
12
|
import os
|
|
13
|
+
import sqlite3
|
|
13
14
|
from collections import OrderedDict, defaultdict
|
|
14
15
|
from collections.abc import Mapping
|
|
15
16
|
from contextlib import asynccontextmanager
|
|
@@ -275,6 +276,8 @@ class SQLiteBackend:
|
|
|
275
276
|
# Task, що тримає transaction(); методи з цього task
|
|
276
277
|
# не беруть lock повторно (asyncio.Lock не reentrant → deadlock)
|
|
277
278
|
self._transaction_task: asyncio.Task | None = None
|
|
279
|
+
# Записи, заплановані *_sync-методами задачами на робочому event loop.
|
|
280
|
+
self._pending_writes: set[asyncio.Task] = set()
|
|
278
281
|
|
|
279
282
|
# Stats
|
|
280
283
|
self._use_cache = True # Use in-memory cache for fast reads
|
|
@@ -425,7 +428,13 @@ class SQLiteBackend:
|
|
|
425
428
|
def _sync_execute(self, statements: list[tuple[str, tuple]]) -> int | None:
|
|
426
429
|
"""Синхронний запис через спільне з'єднання (без ``_run_async``
|
|
427
430
|
і cross-loop ``asyncio.Lock``). None → з'єднання немає; інакше rowcount
|
|
428
|
-
першого statement-у.
|
|
431
|
+
першого statement-у.
|
|
432
|
+
|
|
433
|
+
Якщо на з'єднанні саме відкритий курсор aiosqlite-воркера, SQLite відмовляє
|
|
434
|
+
у commit («SQL statements in progress»). Зміни вже лежать у відкритій
|
|
435
|
+
транзакції цього ж з'єднання й фіксуються найближчим async-commit-ом
|
|
436
|
+
(або в ``close()``), тож запис вважається успішним.
|
|
437
|
+
"""
|
|
429
438
|
conn = self._conn
|
|
430
439
|
raw = getattr(conn, "_connection", None) if conn is not None else None
|
|
431
440
|
if raw is None:
|
|
@@ -436,7 +445,12 @@ class SQLiteBackend:
|
|
|
436
445
|
cur = raw.execute(sql, params)
|
|
437
446
|
if rowcount is None:
|
|
438
447
|
rowcount = cur.rowcount
|
|
439
|
-
|
|
448
|
+
try:
|
|
449
|
+
raw.commit()
|
|
450
|
+
except sqlite3.OperationalError:
|
|
451
|
+
if not raw.in_transaction:
|
|
452
|
+
raise
|
|
453
|
+
logger.debug("SQLiteBackend sync write: commit deferred (statements in progress)")
|
|
440
454
|
return rowcount if rowcount is not None else 0
|
|
441
455
|
except Exception:
|
|
442
456
|
logger.warning("SQLiteBackend sync write failed", exc_info=True)
|
|
@@ -580,7 +594,10 @@ class SQLiteBackend:
|
|
|
580
594
|
|
|
581
595
|
async def close(self) -> None:
|
|
582
596
|
"""Закриває з'єднання."""
|
|
597
|
+
if self._pending_writes:
|
|
598
|
+
await asyncio.gather(*self._pending_writes, return_exceptions=True)
|
|
583
599
|
if self._conn:
|
|
600
|
+
await self._conn.commit()
|
|
584
601
|
await self._conn.close()
|
|
585
602
|
self._conn = None
|
|
586
603
|
self._is_open = False
|
|
@@ -1597,20 +1614,28 @@ class SQLiteBackend:
|
|
|
1597
1614
|
# SYNC WRAPPERS
|
|
1598
1615
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
1599
1616
|
|
|
1600
|
-
def _run_async(self, coro):
|
|
1601
|
-
"""
|
|
1617
|
+
def _run_async(self, coro, result_in_loop: Any):
|
|
1618
|
+
"""Виконує async-запис із sync-коду.
|
|
1619
|
+
|
|
1620
|
+
Поза event loop — синхронно через ``asyncio.run``. Усередині робочого loop
|
|
1621
|
+
дочекатися coroutine неможливо: ``self._lock`` і з'єднання aiosqlite
|
|
1622
|
+
прив'язані до цього loop, а окремий loop у потоці падає з «bound to a
|
|
1623
|
+
different event loop». Тому запис планується задачею на поточному loop,
|
|
1624
|
+
а викликачу повертається ``result_in_loop``.
|
|
1625
|
+
"""
|
|
1602
1626
|
try:
|
|
1603
1627
|
loop = asyncio.get_running_loop()
|
|
1604
|
-
if loop.is_running():
|
|
1605
|
-
# Already in async context - use cache directly
|
|
1606
|
-
import concurrent.futures
|
|
1607
|
-
|
|
1608
|
-
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
1609
|
-
future = executor.submit(asyncio.run, coro)
|
|
1610
|
-
return future.result()
|
|
1611
|
-
return loop.run_until_complete(coro)
|
|
1612
1628
|
except RuntimeError:
|
|
1613
1629
|
return asyncio.run(coro)
|
|
1630
|
+
task = loop.create_task(coro)
|
|
1631
|
+
self._pending_writes.add(task)
|
|
1632
|
+
task.add_done_callback(self._on_pending_write_done)
|
|
1633
|
+
return result_in_loop
|
|
1634
|
+
|
|
1635
|
+
def _on_pending_write_done(self, task: asyncio.Task) -> None:
|
|
1636
|
+
self._pending_writes.discard(task)
|
|
1637
|
+
if not task.cancelled() and task.exception() is not None:
|
|
1638
|
+
logger.warning("SQLiteBackend scheduled write failed", exc_info=task.exception())
|
|
1614
1639
|
|
|
1615
1640
|
def insert_node_sync(self, node: Any) -> Any:
|
|
1616
1641
|
# Use cache directly for sync calls
|
|
@@ -1656,7 +1681,7 @@ class SQLiteBackend:
|
|
|
1656
1681
|
if url is None:
|
|
1657
1682
|
rows = self._sync_fetchall("SELECT url FROM nodes WHERE node_id = ?", (node_id,))
|
|
1658
1683
|
if rows is None:
|
|
1659
|
-
return self._run_async(self.delete_node(node_id))
|
|
1684
|
+
return self._run_async(self.delete_node(node_id), result_in_loop=True)
|
|
1660
1685
|
if not rows:
|
|
1661
1686
|
return False
|
|
1662
1687
|
url = rows[0][0]
|
|
@@ -1670,7 +1695,7 @@ class SQLiteBackend:
|
|
|
1670
1695
|
]
|
|
1671
1696
|
)
|
|
1672
1697
|
if deleted is None:
|
|
1673
|
-
return self._run_async(self.delete_node(node_id))
|
|
1698
|
+
return self._run_async(self.delete_node(node_id), result_in_loop=True)
|
|
1674
1699
|
if self._use_cache:
|
|
1675
1700
|
self._cache_remove(node_id, url)
|
|
1676
1701
|
self._edges = [
|
|
@@ -1764,8 +1789,8 @@ class SQLiteBackend:
|
|
|
1764
1789
|
|
|
1765
1790
|
Проблема залишається на рівні **диску**: SQLite-рядок не оновлюється,
|
|
1766
1791
|
поки ніхто явно не викличе ``update_node``. Цей метод вирішує проблему:
|
|
1767
|
-
|
|
1768
|
-
|
|
1792
|
+
пише рядок напряму через ``_sync_execute``; якщо це неможливо — передає
|
|
1793
|
+
``update_node(node)`` у ``_run_async``.
|
|
1769
1794
|
|
|
1770
1795
|
Args:
|
|
1771
1796
|
node: Ноду з оновленими полями (``node_id`` має існувати у БД).
|
|
@@ -1813,7 +1838,7 @@ class SQLiteBackend:
|
|
|
1813
1838
|
]
|
|
1814
1839
|
)
|
|
1815
1840
|
if rowcount is None:
|
|
1816
|
-
return self._run_async(self.update_node(node))
|
|
1841
|
+
return self._run_async(self.update_node(node), result_in_loop=True)
|
|
1817
1842
|
if rowcount > 0:
|
|
1818
1843
|
self._dirty_node_ids.discard(node.node_id)
|
|
1819
1844
|
return True
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "graph-crawler"
|
|
7
|
-
version = "5.3.
|
|
7
|
+
version = "5.3.7"
|
|
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.3.5 → graph_crawler-5.3.7}/graph_crawler/application/context/dependency_registry.py
RENAMED
|
File without changes
|
{graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/context/graph_context.py
RENAMED
|
File without changes
|
{graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/context/merge_context.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/dto/mappers/__init__.py
RENAMED
|
File without changes
|
{graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/dto/mappers/edge_mapper.py
RENAMED
|
File without changes
|
{graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/dto/mappers/graph_mapper.py
RENAMED
|
File without changes
|
{graph_crawler-5.3.5 → graph_crawler-5.3.7}/graph_crawler/application/dto/mappers/node_data_codec.py
RENAMED
|
File without changes
|
{graph_crawler-5.3.5 → graph_crawler-5.3.7}/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.3.5 → graph_crawler-5.3.7}/graph_crawler/application/services/driver_factory.py
RENAMED
|
File without changes
|
{graph_crawler-5.3.5 → graph_crawler-5.3.7}/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
|