graph-crawler 5.4.1__tar.gz → 5.4.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- graph_crawler-5.4.3/PKG-INFO +130 -0
- graph_crawler-5.4.3/README.md +8 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/crawl_coordinator.py +65 -1
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/worker_pool.py +4 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/data/backends/memory.py +2 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/data/backends/sqlite.py +18 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/entities/graph.py +21 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/entities/node.py +2 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/browser_profile.py +1 -1
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/net/__init__.py +1 -1
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/net/geoip.py +0 -1
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/net/proxy_pool.py +0 -1
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/net/rate_limiter.py +0 -1
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/page_fetch.py +40 -8
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/plugins/human_bezier.py +0 -1
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/pooled_driver.py +1 -1
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/constants.py +0 -2
- graph_crawler-5.4.3/graph_crawler.egg-info/PKG-INFO +130 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler.egg-info/SOURCES.txt +0 -1
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/pyproject.toml +1 -1
- graph_crawler-5.4.1/CHANGELOG.md +0 -637
- graph_crawler-5.4.1/PKG-INFO +0 -523
- graph_crawler-5.4.1/README.md +0 -401
- graph_crawler-5.4.1/graph_crawler.egg-info/PKG-INFO +0 -523
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/LICENSE +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/MANIFEST.in +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/__version__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/ai/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/_core.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/_distributed.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/_shared.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/_sitemap_distributed.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/async_.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/cli.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/client/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/client/client.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/history_manager.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/project_init.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/server/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/server/crawl_monitor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/server/dashboard.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/server/rest_api.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/server/webhooks.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/server/websocket_manager.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/stats_collector.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/api/sync.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/bootstrap.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/context/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/context/dependency_registry.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/context/graph_context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/context/merge_context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/dto/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/dto/edge_dto.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/dto/graph_dto.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/dto/mappers/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/dto/mappers/edge_mapper.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/dto/mappers/graph_mapper.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/dto/mappers/node_data_codec.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/dto/mappers/node_mapper.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/dto/node_dto.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/dto/utils.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/application_container.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/driver_factory.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/exporters/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/exporters/base_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/exporters/csv_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/exporters/edge_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/exporters/excel_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/exporters/jsonl_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/exporters/node_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/exporters/parquet_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/exporters/sql_exporter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/finalize.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/low_memory_graph_saver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/resource_budget.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/scheduling/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/scheduling/rule_based_scheduler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/services/storage_factory.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/adaptive_throttler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/async_bridge.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/base_spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/celery_batch_spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/celery_spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/checkpoint.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/dead_letter_queue.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/domain_rate_limiter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/filters/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/filters/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/filters/domain_filter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/filters/domain_patterns.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/filters/path_filter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/incremental_strategy.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/link_processor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/multiprocess_spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/node_scanner.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/parsers/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/parsers/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/parsers/html_parser.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/progress_tracker.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/scheduler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/serialization_mixin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/sitemap_parser.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/sitemap_processor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/sitemap_spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/crawling/spider_lifecycle.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/graph_export.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/graph_merge/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/graph_merge/graph_operations.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/application/use_cases/graph_merge/merge_strategies.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/data/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/data/backends/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/data/interfaces.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/context/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/context/crawl_context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/entities/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/entities/edge.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/entities/edge_analysis.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/entities/graph_operations.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/entities/graph_statistics.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/entities/merge_strategies.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/entities/redirect_mode.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/entities/registries.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/entities/sitemap_node.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/entities/strategies.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/events/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/events/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/events/crawl.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/events/edge.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/events/event_bus.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/events/events.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/events/fetch.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/events/idempotent_subscriber.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/events/legacy_type_map.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/events/llm.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/events/node.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/events/plugin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/events/scheduler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/events/storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/adapter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/control_channel.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/crawl_context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/distributed_spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/driver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/event_bus.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/exporters.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/filter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/merge_context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/node_interfaces.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/parser.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/plugin_manager.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/processor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/scanner.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/scheduler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/scheduling/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/scheduling/frontier.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/scheduling/scheduler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/spider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/stop_condition.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/storage/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/storage/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/storage/eviction.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/interfaces/storage/queue.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/adjacency_maintenance.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/cycle_detection.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/degree_ranking.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/depth_context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/display_title.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/duplicate_detection.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/duplicate_selector.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/duplicate_stats.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/edge_filter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/edge_statistics.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/eviction_policy.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/graph_query_cascade.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/graph_statistics.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/graph_traversal.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/language_versions.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/lsh_index.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/minimap_builder.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/node_content_resolver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/node_view_builder.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/pagination_guards.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/predicate_ops.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/schema_filter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/search_haystack.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/simhash_clustering.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/simhash_fallback.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/similarity_ranking.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/text_search.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/url_clustering.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/url_deduplication.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/url_normalizer.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/url_rule_matcher.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/services/url_tree_builder.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/value_objects/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/value_objects/configs.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/value_objects/domain_patterns.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/value_objects/fetch_options.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/value_objects/html_document.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/value_objects/lifecycle.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/value_objects/models.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/value_objects/page_actions.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/value_objects/redirect_hop.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/value_objects/scheduling/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/value_objects/scheduling/crawl_request.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/value_objects/scheduling/retry_policy.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/value_objects/settings.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/value_objects/url_normalization.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/domain/value_objects/views.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/cache_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/chain.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/error_recovery_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/logging_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/proxy_health.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/proxy_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/proxy_models.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/proxy_selection.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/rate_limit_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/request_response_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/retry_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/robots_cache.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/robots_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/robots_validator.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/middleware/user_agent_middleware.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/builtin/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/builtin/stats_export_plugin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/crawl_engine/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/crawl_engine/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/crawl_engine/priority_provider.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/crawl_engine/smart_crawl.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/engine/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/engine/anti_bot_detection.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/engine/anti_bot_playwright.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/engine/anti_bot_scripts.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/engine/anti_bot_stealth.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/engine/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/engine/captcha/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/engine/captcha/detector.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/engine/captcha/models.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/engine/captcha/plugin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/engine/captcha/services.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/content_extractors/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/content_extractors/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/content_extractors/goose3_extractor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/content_extractors/newspaper_extractor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/content_extractors/plugin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/content_extractors/readability_extractor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/defaults.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/extractors/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/extractors/email_extractor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/extractors/phone_extractor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/extractors/price_extractor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/links.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/markdown_plugin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/metadata.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/smart_page_finder.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/structured_data/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/structured_data/constants.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/structured_data/exceptions.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/structured_data/extractor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/structured_data/options.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/structured_data/parsers/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/structured_data/parsers/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/structured_data/parsers/jsonld.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/structured_data/parsers/microdata.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/structured_data/parsers/opengraph.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/structured_data/parsers/rdfa.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/structured_data/parsers/twitter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/structured_data/plugin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/structured_data/result.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/text.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/node/url_normalizer.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/extensions/plugins/registry.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/adapters/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/adapters/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/adapters/selectolax_adapter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/messaging/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/messaging/celery_app.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/messaging/celery_batch.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/messaging/celery_job_task.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/messaging/celery_unified.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/messaging/config.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/messaging/easy_crawler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/messaging/worker_api.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/auto_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/eviction.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/graph_repository.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/json/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/json_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/lmdb_eviction_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/memory/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/memory_dedup_store.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/memory_frontier.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/memory_politeness_store.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/memory_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/mongodb_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/naming_strategy.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/postgresql_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/protocols/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/redis_dedup_store.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/redis_politeness_store.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/repository.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/sqlite/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/sqlite_eviction_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/sqlite_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/unified/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/unified/file_job_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/unified/file_queue_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/unified/memory_job_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/unified/memory_queue_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/unified/postgresql_job_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/unified/postgresql_queue_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/persistence/unified/unified_storage.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/async_http/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/async_http/config.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/async_http/context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/async_http/driver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/async_http/driver_curl_cffi.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/async_http/plugins/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle_adaptive.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/async_http/plugins/headers.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/async_http/plugins/http_cache.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/async_http/plugins/rate_limiter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/async_http/plugins/retry.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/async_http/stages.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/base_plugin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/compression/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/compression/strategy.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/connection_pool.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/core/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/core/base_async.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/core/body_decoder.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/core/body_limit.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/core/mixins.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/core/redirect_utils.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/escalation.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/factory.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/adaptive_semaphore.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/batch_scheduler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/browser_factory.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/config.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/context.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/driver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/engine.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/fetch_metrics.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/page_actions.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/plugins/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/plugins/_network_record.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_detector.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_solver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/plugins/cookie_consent.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/plugins/form_filler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/plugins/network_log.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/plugins/ram_adaptive.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/plugins/screenshot.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/plugins/user_profile.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/pool_lifecycle.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/stages.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/playwright/stealth_hints.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/plugin_manager.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/protocols.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/undetected/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/infrastructure/transport/undetected/async_driver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/native/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/native/bloom_filter.pyx +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/native/html_parser.pyx +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/native/setup.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/native/url_utils.pyx +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/error_tracing.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/extensions/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/extensions/memory_guard.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/extensions/state_manager.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/listeners/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/listeners/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/listeners/base_metrics_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/listeners/crawl_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/listeners/dlq_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/listeners/error_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/listeners/logging_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/listeners/metrics_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/listeners/node_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/listeners/plugin_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/listeners/storage_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/listeners/url_listener.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/metrics/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/metrics/memory_profiler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/metrics/metrics_collector.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/metrics/prometheus_metrics.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/metrics_core.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/structured_logging.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/observability/tracing.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/optimizations/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/optimizations/generators.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/optimizations/memory_slots.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/optimizations/simhash_numba.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/py.typed +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/dto/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/dto/edge_dto.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/dto/graph_dto.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/dto/node_dto.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/error_handling/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/error_handling/error_handler.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/exceptions.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/platform/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/platform/adaptive_resolver.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/platform/config.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/platform/detector.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/platform/diagnostics.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/platform/metrics.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/security/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/security/url_sanitizer.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/security/url_validator.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/async_compat.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/best_effort.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/bloom_filter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/captcha/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/captcha/base.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/captcha/manager.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/celery_config.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/celery_helpers.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/cgroup_limits.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/content_decoder.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/distributed_rate_limiter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/distributed_rate_limiter_backends.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/dns_cache.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/enterprise_ua_generator.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/event_publisher_mixin.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/fast_json.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/fingerprint.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/fingerprint_data.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/fingerprint_generators.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/fingerprint_profile.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/fingerprint_stealth.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/html_utils.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/id_generator.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/isolated_executor.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/memory_optimizer.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/rate_limiter.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/regex_validator.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/shadow_dom.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/text_utils.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/url_patterns.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/url_utils.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/user_agent_rotator.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/validation_helpers.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/visualization.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/shared/utils/visualization_core.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/tools/__init__.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler/tools/db_cleanup.py +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler.egg-info/dependency_links.txt +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler.egg-info/entry_points.txt +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler.egg-info/requires.txt +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/graph_crawler.egg-info/top_level.txt +0 -0
- {graph_crawler-5.4.1 → graph_crawler-5.4.3}/setup.cfg +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: graph-crawler
|
|
3
|
+
Version: 5.4.3
|
|
4
|
+
Summary: Web crawler that builds a graph of a site. HTTP → TLS → browser escalation with pluggable browser engines.
|
|
5
|
+
Author: 0-EternalJunior-0
|
|
6
|
+
Maintainer: 0-EternalJunior-0
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://gitlab.com/demoprogrammer/web_graf
|
|
9
|
+
Project-URL: Documentation, https://gitlab.com/demoprogrammer/web_graf/-/blob/main/web_graf/README.md
|
|
10
|
+
Project-URL: Repository, https://gitlab.com/demoprogrammer/web_graf
|
|
11
|
+
Project-URL: Changelog, https://gitlab.com/demoprogrammer/web_graf/-/blob/main/web_graf/CHANGELOG.md
|
|
12
|
+
Project-URL: Bug Tracker, https://gitlab.com/demoprogrammer/web_graf/-/issues
|
|
13
|
+
Keywords: web,crawler,scraper,graph,spider,playwright,anti-bot,tls-fingerprint
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Operating System :: OS Independent
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: aiohttp>=3.13.0
|
|
28
|
+
Requires-Dist: backports.zstd>=1.6.0; python_version < "3.14"
|
|
29
|
+
Requires-Dist: Brotli
|
|
30
|
+
Requires-Dist: selectolax<1.0.0,>=0.4.0
|
|
31
|
+
Requires-Dist: defusedxml>=0.7.0
|
|
32
|
+
Requires-Dist: pydantic>=2.12.0
|
|
33
|
+
Requires-Dist: orjson>=3.11.0
|
|
34
|
+
Requires-Dist: aiofiles>=24.1.0
|
|
35
|
+
Requires-Dist: aiosqlite>=0.20.0
|
|
36
|
+
Requires-Dist: typing_extensions>=4.5; python_version < "3.12"
|
|
37
|
+
Requires-Dist: xxhash>=3.0.0
|
|
38
|
+
Requires-Dist: resilix<2,>=1.1
|
|
39
|
+
Provides-Extra: native
|
|
40
|
+
Requires-Dist: cython>=3.0.0; extra == "native"
|
|
41
|
+
Requires-Dist: mmh3>=5.0.0; extra == "native"
|
|
42
|
+
Provides-Extra: browser
|
|
43
|
+
Requires-Dist: playwright>=1.58.0; extra == "browser"
|
|
44
|
+
Provides-Extra: playwright
|
|
45
|
+
Requires-Dist: graph-crawler[browser]; extra == "playwright"
|
|
46
|
+
Provides-Extra: tls
|
|
47
|
+
Requires-Dist: curl_cffi>=0.7; extra == "tls"
|
|
48
|
+
Provides-Extra: stealth
|
|
49
|
+
Requires-Dist: patchright>=1.52; extra == "stealth"
|
|
50
|
+
Requires-Dist: playwright-stealth>=1.0; extra == "stealth"
|
|
51
|
+
Provides-Extra: cloak
|
|
52
|
+
Requires-Dist: cloakbrowser>=0.5; extra == "cloak"
|
|
53
|
+
Requires-Dist: playwright>=1.58.0; extra == "cloak"
|
|
54
|
+
Provides-Extra: undetected
|
|
55
|
+
Requires-Dist: nodriver>=0.38; extra == "undetected"
|
|
56
|
+
Provides-Extra: antidetect
|
|
57
|
+
Requires-Dist: graph-crawler[browser,cloak,stealth,tls,undetected]; extra == "antidetect"
|
|
58
|
+
Provides-Extra: server
|
|
59
|
+
Requires-Dist: fastapi>=0.135.0; extra == "server"
|
|
60
|
+
Requires-Dist: uvicorn>=0.30; extra == "server"
|
|
61
|
+
Requires-Dist: websockets>=13; extra == "server"
|
|
62
|
+
Provides-Extra: text
|
|
63
|
+
Requires-Dist: tiktoken>=0.13.0; extra == "text"
|
|
64
|
+
Requires-Dist: stop-words>=2018.7.23; extra == "text"
|
|
65
|
+
Provides-Extra: captcha
|
|
66
|
+
Provides-Extra: mongodb
|
|
67
|
+
Requires-Dist: pymongo[srv]>=4.9.0; extra == "mongodb"
|
|
68
|
+
Provides-Extra: postgresql
|
|
69
|
+
Requires-Dist: asyncpg>=0.30.0; extra == "postgresql"
|
|
70
|
+
Provides-Extra: newspaper
|
|
71
|
+
Requires-Dist: newspaper3k>=0.2.8; extra == "newspaper"
|
|
72
|
+
Provides-Extra: goose
|
|
73
|
+
Requires-Dist: goose3>=3.1.0; extra == "goose"
|
|
74
|
+
Provides-Extra: readability
|
|
75
|
+
Requires-Dist: readability-lxml>=0.8.0; extra == "readability"
|
|
76
|
+
Provides-Extra: markdown
|
|
77
|
+
Requires-Dist: html2md-clean>=0.5.0; extra == "markdown"
|
|
78
|
+
Requires-Dist: markdown-it-py>=4.0.0; extra == "markdown"
|
|
79
|
+
Provides-Extra: sitemap-fast
|
|
80
|
+
Requires-Dist: lxml>=5.1.0; extra == "sitemap-fast"
|
|
81
|
+
Provides-Extra: articles
|
|
82
|
+
Requires-Dist: newspaper3k>=0.2.8; extra == "articles"
|
|
83
|
+
Requires-Dist: goose3>=3.1.0; extra == "articles"
|
|
84
|
+
Requires-Dist: readability-lxml>=0.8.0; extra == "articles"
|
|
85
|
+
Provides-Extra: viz
|
|
86
|
+
Requires-Dist: pyvis>=0.3.0; extra == "viz"
|
|
87
|
+
Requires-Dist: networkx>=3.6; extra == "viz"
|
|
88
|
+
Provides-Extra: celery
|
|
89
|
+
Requires-Dist: celery>=5.5.0; extra == "celery"
|
|
90
|
+
Requires-Dist: redis>=5.2.0; extra == "celery"
|
|
91
|
+
Provides-Extra: performance
|
|
92
|
+
Requires-Dist: aiodns>=3.6.0; extra == "performance"
|
|
93
|
+
Requires-Dist: uvloop>=0.22.0; platform_system != "Windows" and extra == "performance"
|
|
94
|
+
Provides-Extra: fuzzy
|
|
95
|
+
Requires-Dist: rapidfuzz>=3.6.0; extra == "fuzzy"
|
|
96
|
+
Provides-Extra: dev
|
|
97
|
+
Requires-Dist: pytest>=9.0.0; extra == "dev"
|
|
98
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
|
|
99
|
+
Requires-Dist: pytest-cov>=7.0.0; extra == "dev"
|
|
100
|
+
Requires-Dist: black>=24.0.0; extra == "dev"
|
|
101
|
+
Requires-Dist: ruff>=0.8.0; extra == "dev"
|
|
102
|
+
Requires-Dist: mypy>=1.13.0; extra == "dev"
|
|
103
|
+
Requires-Dist: psutil; extra == "dev"
|
|
104
|
+
Requires-Dist: rapidfuzz>=3.6.0; extra == "dev"
|
|
105
|
+
Requires-Dist: pytest-timeout>=2.3.0; extra == "dev"
|
|
106
|
+
Requires-Dist: pytest-xdist>=3.5.0; extra == "dev"
|
|
107
|
+
Requires-Dist: build>=1.2.0; extra == "dev"
|
|
108
|
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
109
|
+
Requires-Dist: playwright>=1.58.0; extra == "dev"
|
|
110
|
+
Requires-Dist: curl_cffi>=0.7; extra == "dev"
|
|
111
|
+
Requires-Dist: html2md-clean>=0.5.0; extra == "dev"
|
|
112
|
+
Requires-Dist: markdown-it-py>=4.0.0; extra == "dev"
|
|
113
|
+
Requires-Dist: tiktoken>=0.13.0; extra == "dev"
|
|
114
|
+
Requires-Dist: stop-words>=2018.7.23; extra == "dev"
|
|
115
|
+
Requires-Dist: fastapi>=0.135.0; extra == "dev"
|
|
116
|
+
Requires-Dist: httpx>=0.28; extra == "dev"
|
|
117
|
+
Provides-Extra: system
|
|
118
|
+
Requires-Dist: psutil>=5.9.0; extra == "system"
|
|
119
|
+
Provides-Extra: all
|
|
120
|
+
Requires-Dist: graph-crawler[antidetect,articles,celery,fuzzy,markdown,mongodb,performance,postgresql,server,sitemap-fast,system,text,viz]; extra == "all"
|
|
121
|
+
Dynamic: license-file
|
|
122
|
+
|
|
123
|
+
# graph-crawler
|
|
124
|
+
|
|
125
|
+
Веб-краулер, що будує граф сайту (сторінки — ноди, посилання — ребра) з ескалацією HTTP → TLS-fingerprint → браузер.
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pip install graph-crawler # лише HTTP
|
|
129
|
+
pip install "graph-crawler[browser]" # + Playwright
|
|
130
|
+
```
|
|
@@ -42,6 +42,7 @@ from graph_crawler.shared.constants import (
|
|
|
42
42
|
BROWSER_PAGE_RAM_ESTIMATE_MB,
|
|
43
43
|
BROWSER_RAM_BASELINE_MB,
|
|
44
44
|
DEFAULT_BATCH_SIZE,
|
|
45
|
+
DEFAULT_NODE_ENRICH_TIMEOUT,
|
|
45
46
|
MAX_PAGES_LIMIT,
|
|
46
47
|
)
|
|
47
48
|
from graph_crawler.shared.utils.cgroup_limits import (
|
|
@@ -85,6 +86,12 @@ logger = logging.getLogger(__name__)
|
|
|
85
86
|
# Скільки секунд після crawl-level timeout даємо in-flight нодам дозавершитись.
|
|
86
87
|
TIMEOUT_DRAIN_GRACE_S = 1.0
|
|
87
88
|
|
|
89
|
+
# NEW-3-5: браузерний пул «завис», якщо жодна нода не завершилась за
|
|
90
|
+
# 2 × fetch_timeout + enrich-таймаут + запас (найдовший законний шлях однієї ноди).
|
|
91
|
+
STALL_FETCH_TIMEOUT_MULTIPLIER = 2.0
|
|
92
|
+
STALL_GRACE_S = 10.0
|
|
93
|
+
STALL_CHECK_INTERVAL_S = 5.0
|
|
94
|
+
|
|
88
95
|
|
|
89
96
|
def _stats_for_log(stats: dict) -> dict:
|
|
90
97
|
"""Копія stats для логу без шумового ``redirect_deduped_scanned=0``.
|
|
@@ -425,6 +432,14 @@ class CrawlCoordinator:
|
|
|
425
432
|
asyncio.create_task(self._worker_loop(i, state), name=f"crawl-worker-{i}")
|
|
426
433
|
for i in range(pool_size)
|
|
427
434
|
]
|
|
435
|
+
stall_timeout = self._resolve_stall_timeout()
|
|
436
|
+
watchdog = (
|
|
437
|
+
asyncio.create_task(
|
|
438
|
+
self._stall_watchdog(workers, state, stall_timeout), name="crawl-stall-watchdog"
|
|
439
|
+
)
|
|
440
|
+
if stall_timeout
|
|
441
|
+
else None
|
|
442
|
+
)
|
|
428
443
|
try:
|
|
429
444
|
await self._await_workers(workers, state)
|
|
430
445
|
except asyncio.CancelledError:
|
|
@@ -436,6 +451,9 @@ class CrawlCoordinator:
|
|
|
436
451
|
await asyncio.gather(*workers, return_exceptions=True)
|
|
437
452
|
raise
|
|
438
453
|
finally:
|
|
454
|
+
if watchdog is not None:
|
|
455
|
+
watchdog.cancel()
|
|
456
|
+
await asyncio.gather(watchdog, return_exceptions=True)
|
|
439
457
|
self._pool_state = None
|
|
440
458
|
|
|
441
459
|
if state.stop_reason == "timeout":
|
|
@@ -444,6 +462,8 @@ class CrawlCoordinator:
|
|
|
444
462
|
self.spider, "stop_reason", None
|
|
445
463
|
):
|
|
446
464
|
self._record_stop(state.stop_reason, "limit")
|
|
465
|
+
if state.stop_reason == "stalled" and not self.scheduler.is_empty():
|
|
466
|
+
self._record_stop("stalled", "limit")
|
|
447
467
|
|
|
448
468
|
logger.debug(
|
|
449
469
|
"Worker pool finished: %d nodes processed, reason=%s, %.1fs",
|
|
@@ -457,7 +477,8 @@ class CrawlCoordinator:
|
|
|
457
477
|
async def _await_workers(self, workers: list[asyncio.Task], state: PoolState) -> None:
|
|
458
478
|
"""Чекає воркерів; при crawl-level timeout скасовує in-flight (інакше ретраї тягнуть хвіст)."""
|
|
459
479
|
if self.timeout is None or not self.start_time:
|
|
460
|
-
await asyncio.
|
|
480
|
+
done, _ = await asyncio.wait(workers)
|
|
481
|
+
self._raise_worker_errors(done)
|
|
461
482
|
return
|
|
462
483
|
deadline = self.start_time + self.timeout + TIMEOUT_DRAIN_GRACE_S
|
|
463
484
|
done, pending = await asyncio.wait(workers, timeout=max(0.0, deadline - time.time()))
|
|
@@ -478,10 +499,53 @@ class CrawlCoordinator:
|
|
|
478
499
|
for task in pending:
|
|
479
500
|
task.cancel()
|
|
480
501
|
await asyncio.gather(*pending, return_exceptions=True)
|
|
502
|
+
self._raise_worker_errors(done)
|
|
503
|
+
|
|
504
|
+
@staticmethod
|
|
505
|
+
def _raise_worker_errors(done: set[asyncio.Task]) -> None:
|
|
506
|
+
"""Скасований сторожем простою воркер — не помилка; справжню помилку воркера прокидаємо."""
|
|
481
507
|
for task in done:
|
|
482
508
|
if not task.cancelled() and task.exception() is not None:
|
|
483
509
|
raise task.exception()
|
|
484
510
|
|
|
511
|
+
def _resolve_stall_timeout(self) -> float | None:
|
|
512
|
+
"""Поріг «пул завис» (NEW-3-5): лише для браузерних драйверів, що мають ``fetch_timeout``."""
|
|
513
|
+
if not _is_js_heavy_driver(self.driver):
|
|
514
|
+
return None
|
|
515
|
+
fetch_timeout = getattr(self.driver, "fetch_timeout", None)
|
|
516
|
+
if isinstance(fetch_timeout, bool) or not isinstance(fetch_timeout, (int, float)):
|
|
517
|
+
return None
|
|
518
|
+
if fetch_timeout <= 0:
|
|
519
|
+
return None
|
|
520
|
+
enrich_timeout = (
|
|
521
|
+
getattr(self.config, "node_enrich_timeout", None) or DEFAULT_NODE_ENRICH_TIMEOUT
|
|
522
|
+
)
|
|
523
|
+
return STALL_FETCH_TIMEOUT_MULTIPLIER * fetch_timeout + enrich_timeout + STALL_GRACE_S
|
|
524
|
+
|
|
525
|
+
async def _stall_watchdog(
|
|
526
|
+
self, workers: list[asyncio.Task], state: PoolState, stall_timeout: float
|
|
527
|
+
) -> None:
|
|
528
|
+
"""Жодна нода не завершилась за ``stall_timeout`` — вкладки зависли: скасовуємо їх, фаза завершується."""
|
|
529
|
+
interval = min(STALL_CHECK_INTERVAL_S, stall_timeout / 4)
|
|
530
|
+
while not state.stop:
|
|
531
|
+
await asyncio.sleep(interval)
|
|
532
|
+
idle = time.monotonic() - state.last_progress_at
|
|
533
|
+
if state.in_flight == 0 or idle < stall_timeout:
|
|
534
|
+
continue
|
|
535
|
+
logger.warning(
|
|
536
|
+
"Worker pool stalled: no page finished for %.0fs (limit %.0fs), in_flight=%d, "
|
|
537
|
+
"queue=%d, stuck=%s - cancelling stuck workers",
|
|
538
|
+
idle,
|
|
539
|
+
stall_timeout,
|
|
540
|
+
state.in_flight,
|
|
541
|
+
self.scheduler.size() if hasattr(self.scheduler, "size") else 0,
|
|
542
|
+
list(state.in_flight_urls)[:5],
|
|
543
|
+
)
|
|
544
|
+
state.request_stop("stalled")
|
|
545
|
+
for task in workers:
|
|
546
|
+
task.cancel()
|
|
547
|
+
return
|
|
548
|
+
|
|
485
549
|
def _resolve_worker_pool_size(self) -> int:
|
|
486
550
|
"""N воркерів: override з конфігу → конкурентність драйвера → кап для JS-heavy.
|
|
487
551
|
|
|
@@ -15,6 +15,7 @@ from __future__ import annotations
|
|
|
15
15
|
|
|
16
16
|
import asyncio
|
|
17
17
|
import logging
|
|
18
|
+
import time
|
|
18
19
|
from collections import defaultdict
|
|
19
20
|
from urllib.parse import urlsplit
|
|
20
21
|
|
|
@@ -119,6 +120,8 @@ class PoolState:
|
|
|
119
120
|
self.stop = False
|
|
120
121
|
self.stop_reason: str | None = None
|
|
121
122
|
self.wakeup = asyncio.Event()
|
|
123
|
+
# NEW-3-5: час останньої завершеної ноди — сторож простою пулу.
|
|
124
|
+
self.last_progress_at = time.monotonic()
|
|
122
125
|
|
|
123
126
|
def request_stop(self, reason: str) -> None:
|
|
124
127
|
if not self.stop:
|
|
@@ -141,6 +144,7 @@ class PoolState:
|
|
|
141
144
|
"""Повертає True, якщо eviction-watermark просунувся."""
|
|
142
145
|
self.in_flight -= 1
|
|
143
146
|
self.completed += 1
|
|
147
|
+
self.last_progress_at = time.monotonic()
|
|
144
148
|
if node_id:
|
|
145
149
|
self.in_flight_node_ids.discard(node_id)
|
|
146
150
|
self.in_flight_urls.pop(url, None)
|
|
@@ -182,6 +182,8 @@ class MemoryBackend:
|
|
|
182
182
|
async with self._lock:
|
|
183
183
|
count = 0
|
|
184
184
|
for node in nodes:
|
|
185
|
+
if getattr(node, "_removed_from_graph", False):
|
|
186
|
+
continue
|
|
185
187
|
normalized_url = self._normalize_url(node.url)
|
|
186
188
|
node.url = normalized_url
|
|
187
189
|
new_scanned = bool(getattr(node, "scanned", False))
|
|
@@ -235,6 +235,11 @@ class SQLiteBackend:
|
|
|
235
235
|
"""
|
|
236
236
|
self.db_path = db_path
|
|
237
237
|
self.journal_mode = journal_mode
|
|
238
|
+
# Скільки відкладених insert-ів пропущено через tombstone (діагностика).
|
|
239
|
+
self.tombstone_skips = 0
|
|
240
|
+
# URL нод, видалених з графа (remove_node): відкладений insert_nodes_batch
|
|
241
|
+
# їх не воскрешає; явний insert_node/insert_node_sync tombstone знімає.
|
|
242
|
+
self._tombstoned_urls: set[str] = set()
|
|
238
243
|
self._conn = None
|
|
239
244
|
self._is_open = False
|
|
240
245
|
|
|
@@ -895,6 +900,7 @@ class SQLiteBackend:
|
|
|
895
900
|
# Normalize URL
|
|
896
901
|
original_url = node.url
|
|
897
902
|
normalized_url = self._normalize_url(original_url)
|
|
903
|
+
self._tombstoned_urls.discard(normalized_url)
|
|
898
904
|
|
|
899
905
|
if original_url != normalized_url:
|
|
900
906
|
node.url = normalized_url
|
|
@@ -1002,7 +1008,16 @@ class SQLiteBackend:
|
|
|
1002
1008
|
for node in nodes:
|
|
1003
1009
|
from graph_crawler.shared.utils.id_generator import generate_node_id
|
|
1004
1010
|
|
|
1011
|
+
# Tombstone: нода вже видалена з графа (редірект на існуючу) —
|
|
1012
|
+
# відкладений write-through не повинен її воскресити.
|
|
1013
|
+
if getattr(node, "_removed_from_graph", False):
|
|
1014
|
+
self.tombstone_skips += 1
|
|
1015
|
+
continue
|
|
1016
|
+
|
|
1005
1017
|
normalized_url = self._normalize_url(node.url)
|
|
1018
|
+
if normalized_url in self._tombstoned_urls:
|
|
1019
|
+
self.tombstone_skips += 1
|
|
1020
|
+
continue
|
|
1006
1021
|
node.url = normalized_url
|
|
1007
1022
|
|
|
1008
1023
|
# BUGFIX /: пере-обчислити node_id після
|
|
@@ -1241,6 +1256,7 @@ class SQLiteBackend:
|
|
|
1241
1256
|
else:
|
|
1242
1257
|
url = node.url
|
|
1243
1258
|
|
|
1259
|
+
self._tombstoned_urls.add(self._normalize_url(url))
|
|
1244
1260
|
# Delete from DB
|
|
1245
1261
|
await self._conn.execute("DELETE FROM nodes WHERE node_id = ?", (node_id,))
|
|
1246
1262
|
await self._conn.execute(
|
|
@@ -1647,6 +1663,7 @@ class SQLiteBackend:
|
|
|
1647
1663
|
from graph_crawler.shared.utils.id_generator import generate_node_id
|
|
1648
1664
|
|
|
1649
1665
|
normalized_url = self._normalize_url(node.url)
|
|
1666
|
+
self._tombstoned_urls.discard(normalized_url)
|
|
1650
1667
|
cached = self._cache_get_by_url(normalized_url)
|
|
1651
1668
|
if cached is not None:
|
|
1652
1669
|
return cached
|
|
@@ -1690,6 +1707,7 @@ class SQLiteBackend:
|
|
|
1690
1707
|
if not rows:
|
|
1691
1708
|
return False
|
|
1692
1709
|
url = rows[0][0]
|
|
1710
|
+
self._tombstoned_urls.add(self._normalize_url(url))
|
|
1693
1711
|
deleted = self._sync_execute(
|
|
1694
1712
|
[
|
|
1695
1713
|
("DELETE FROM nodes WHERE node_id = ?", (node_id,)),
|
|
@@ -124,6 +124,21 @@ from graph_crawler.shared.exceptions import GraphIntegrityError
|
|
|
124
124
|
logger = logging.getLogger(__name__)
|
|
125
125
|
|
|
126
126
|
|
|
127
|
+
|
|
128
|
+
def _set_removed_flag(node: Any, value: bool) -> None:
|
|
129
|
+
try:
|
|
130
|
+
node._removed_from_graph = value
|
|
131
|
+
except (AttributeError, ValueError, TypeError):
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def is_removed_from_graph(node: Any) -> bool:
|
|
136
|
+
"""Чи нода видалена з графа (tombstone від ``Graph.remove_node``)."""
|
|
137
|
+
try:
|
|
138
|
+
return bool(getattr(node, "_removed_from_graph", False))
|
|
139
|
+
except Exception: # noqa: BLE001
|
|
140
|
+
return False
|
|
141
|
+
|
|
127
142
|
class Graph:
|
|
128
143
|
"""Менеджер графу: CRUD над вузлами/ребрами, URL-дедуплікація, evict у RAM/на диск.
|
|
129
144
|
|
|
@@ -759,6 +774,8 @@ class Graph:
|
|
|
759
774
|
self._stale_node_id_map[node.node_id] = expected_id
|
|
760
775
|
node.node_id = expected_id
|
|
761
776
|
|
|
777
|
+
# Явне повторне додавання знімає tombstone.
|
|
778
|
+
_set_removed_flag(node, False)
|
|
762
779
|
return node
|
|
763
780
|
|
|
764
781
|
def add_node(self, node: Node, overwrite: bool = False) -> Node:
|
|
@@ -1339,6 +1356,10 @@ class Graph:
|
|
|
1339
1356
|
if not node:
|
|
1340
1357
|
return False
|
|
1341
1358
|
|
|
1359
|
+
# Tombstone до видалення з backend: drain write-through, що тримає посилання
|
|
1360
|
+
# на цей об'єкт, після DELETE не має зробити INSERT (зомбі-нода).
|
|
1361
|
+
_set_removed_flag(node, True)
|
|
1362
|
+
|
|
1342
1363
|
# BUGFIX: у backend-режимі backend — джерело правди для
|
|
1343
1364
|
# edges/adjacency. Делегуємо йому видалення (він почистить БД + власний
|
|
1344
1365
|
# кеш edges + adjacency), інакше лишаються "мертві" edges у backend →
|
|
@@ -358,6 +358,8 @@ class Node(BaseModel):
|
|
|
358
358
|
_lifecycle_lock: threading.Lock = PrivateAttr(default_factory=threading.Lock)
|
|
359
359
|
# Лінки з ON_HTML_PARSED поточного enrich_node: переживають його переривання (per-node timeout).
|
|
360
360
|
_discovered_links: list[str] = PrivateAttr(default_factory=list)
|
|
361
|
+
# Tombstone: Graph.remove_node видалив ноду — відкладений write-through не має її воскресити.
|
|
362
|
+
_removed_from_graph: bool = PrivateAttr(default=False)
|
|
361
363
|
|
|
362
364
|
# Контент і обхід: хеші рахує enrich_node (дедуплікація, near-duplicates);
|
|
363
365
|
# priority — з URL-правил для черги; lifecycle_stage змінює лише transition_state.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""BrowserProfile — єдине джерело істини для fingerprint.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
UA, platform, screen,
|
|
4
4
|
viewport, WebGL, hardwareConcurrency, languages, locale/timezone задавались у
|
|
5
5
|
різних місцях (constants, StealthPlugin, EnhancedStealthPlugin, UserProfilePlugin)
|
|
6
6
|
і між собою НЕ узгоджувались — це головна причина детекту. Тепер усе виводиться
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""GeoResolver — виведення locale/timezone з проксі-запису (Крок 3).
|
|
2
2
|
|
|
3
|
-
ANTIBOT_AUDIT_pooled_driver.md §2.6:
|
|
4
3
|
IP з Німеччини + `timeZone: "UTC"` + `Accept-Language: en-US` +
|
|
5
4
|
Windows-UA на Linux — чотири незалежні суперечності в ОДНОМУ запиті.
|
|
6
5
|
Для Cloudflare/DataDome достатньо challenge без JS-аналізу.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""DomainRateLimiter — per-domain concurrency + мінімальний інтервал (Крок 3).
|
|
2
2
|
|
|
3
|
-
ANTIBOT_AUDIT_pooled_driver.md §3.2:
|
|
4
3
|
Наявний `calculate_optimal_jitter` збільшує затримку між стартами,
|
|
5
4
|
але **не обмежує concurrency per-domain**: 25 URL одного домену
|
|
6
5
|
підуть у 25 паралельних вкладок з одного IP → 429/challenge раніше,
|
|
@@ -28,6 +28,7 @@ from graph_crawler.infrastructure.transport.core.redirect_utils import (
|
|
|
28
28
|
)
|
|
29
29
|
from graph_crawler.infrastructure.transport.playwright.context import BrowserContext
|
|
30
30
|
from graph_crawler.infrastructure.transport.playwright.stages import BrowserStage
|
|
31
|
+
from graph_crawler.shared.utils.best_effort import close_quietly
|
|
31
32
|
from graph_crawler.shared.utils.shadow_dom import expose_shadow_roots
|
|
32
33
|
|
|
33
34
|
logger = logging.getLogger(__name__)
|
|
@@ -49,6 +50,11 @@ logger = logging.getLogger(__name__)
|
|
|
49
50
|
# =============================================================================
|
|
50
51
|
PARTIAL_TIMEOUT_STATUS = 504
|
|
51
52
|
|
|
53
|
+
# NEW-3-5: після fetch_timeout вкладку ще питають про частковий HTML і адресу; зависла вкладка
|
|
54
|
+
# (скрипт сторінки тримає її) не відповідає ніколи — без ліміту воркер чекав до кінця фази.
|
|
55
|
+
TIMEOUT_RECOVERY_S = 5.0
|
|
56
|
+
FROZEN_PAGE_CLOSE_S = 3.0
|
|
57
|
+
|
|
52
58
|
# ``page.content()`` серіалізує лише light DOM: вміст відкритих shadow root'ів (веб-компоненти,
|
|
53
59
|
# Lit/Stencil/Salesforce LWC, вбудовані віджети цін) у HTML не потрапляє, і парсер «не бачить»
|
|
54
60
|
# того, що бачить користувач. Якщо на сторінці є відкриті shadow root'и, серіалізуємо DOM
|
|
@@ -228,12 +234,7 @@ class PageFetcher:
|
|
|
228
234
|
timeout=float(self.fetch_timeout),
|
|
229
235
|
)
|
|
230
236
|
except asyncio.TimeoutError:
|
|
231
|
-
|
|
232
|
-
partial_html = None
|
|
233
|
-
async with protect.suppress(
|
|
234
|
-
Exception, log=logger.debug, message="_fetch_with_page_impl"
|
|
235
|
-
):
|
|
236
|
-
partial_html = await page.content()
|
|
237
|
+
partial_html, frozen = await self._partial_html_after_timeout(page, url)
|
|
237
238
|
partial_html = self._drop_oversized_partial(partial_html, url)
|
|
238
239
|
|
|
239
240
|
error_msg = f"Fetch timeout ({self.fetch_timeout}s) for {url}"
|
|
@@ -261,8 +262,18 @@ class PageFetcher:
|
|
|
261
262
|
)
|
|
262
263
|
# Навіть у fetch-timeout page.url може містити final URL.
|
|
263
264
|
# Зберігаємо провенанс, щоб graph.handle_redirect() спрацював
|
|
264
|
-
# для timeout-нод з JS-редіректом.
|
|
265
|
-
ft_final, ft_chain, ft_hops =
|
|
265
|
+
# для timeout-нод з JS-редіректом. Завислу вкладку не питаємо (NEW-3-5).
|
|
266
|
+
ft_final, ft_chain, ft_hops = url, [], []
|
|
267
|
+
if not frozen:
|
|
268
|
+
try:
|
|
269
|
+
ft_final, ft_chain, ft_hops = await asyncio.wait_for(
|
|
270
|
+
self._build_playwright_provenance(url, page, None),
|
|
271
|
+
timeout=TIMEOUT_RECOVERY_S,
|
|
272
|
+
)
|
|
273
|
+
except asyncio.TimeoutError:
|
|
274
|
+
frozen = True
|
|
275
|
+
if frozen:
|
|
276
|
+
await self._close_frozen_page(page, url)
|
|
266
277
|
return self._finalize_response(
|
|
267
278
|
FetchResponse(
|
|
268
279
|
url=url,
|
|
@@ -282,6 +293,27 @@ class PageFetcher:
|
|
|
282
293
|
logger.error(error_msg)
|
|
283
294
|
return FetchResponse(url=url, html=None, status_code=None, headers={}, error=error_msg)
|
|
284
295
|
|
|
296
|
+
@staticmethod
|
|
297
|
+
async def _partial_html_after_timeout(page: Any, url: str) -> tuple[str | None, bool]:
|
|
298
|
+
"""Частковий HTML після fetch_timeout і ознака «вкладка зависла» (не відповіла за ``TIMEOUT_RECOVERY_S``)."""
|
|
299
|
+
try:
|
|
300
|
+
return await asyncio.wait_for(page.content(), timeout=TIMEOUT_RECOVERY_S), False
|
|
301
|
+
except asyncio.TimeoutError:
|
|
302
|
+
logger.warning(
|
|
303
|
+
"Browser tab frozen after fetch timeout: page.content() did not answer in %.0fs for %s",
|
|
304
|
+
TIMEOUT_RECOVERY_S,
|
|
305
|
+
url,
|
|
306
|
+
)
|
|
307
|
+
return None, True
|
|
308
|
+
except Exception as e: # noqa: BLE001 — сторінка закрилась/переходить
|
|
309
|
+
logger.debug("partial content after timeout unavailable for %s: %s", url, e)
|
|
310
|
+
return None, False
|
|
311
|
+
|
|
312
|
+
@staticmethod
|
|
313
|
+
async def _close_frozen_page(page: Any, url: str) -> None:
|
|
314
|
+
"""Закрита вкладка не повертається в пул — пул відкриє нову замість завислої."""
|
|
315
|
+
await close_quietly(page.close(), name=f"frozen page {url}", timeout=FROZEN_PAGE_CLOSE_S)
|
|
316
|
+
|
|
285
317
|
@staticmethod
|
|
286
318
|
def _log_fetch_timeout(
|
|
287
319
|
error_msg: str,
|
|
@@ -570,7 +570,7 @@ class PooledPlaywrightDriver(PageFetcher, BrowserPool, BatchScheduler, BaseDrive
|
|
|
570
570
|
) -> list[FetchResponse]:
|
|
571
571
|
"""Async паралельне завантаження багатьох сторінок.
|
|
572
572
|
|
|
573
|
-
|
|
573
|
+
Ці параметри
|
|
574
574
|
мовчки ігнорувались — тепер `referer` передається у `page.goto`
|
|
575
575
|
per-URL, а `cookies` / `extra_headers` мержаться і застосовуються
|
|
576
576
|
на рівні контексту (Playwright не дає per-URL headers/cookies при
|
|
@@ -660,7 +660,6 @@ DEFAULT_POST_NAVIGATION_WAIT_NETWORKIDLE_MS = 5000
|
|
|
660
660
|
"""
|
|
661
661
|
|
|
662
662
|
# Playwright Stealth Mode Arguments
|
|
663
|
-
# Аудит ANTIBOT_AUDIT_pooled_driver.md §1.3 / §1.14 / Крок 0:
|
|
664
663
|
# прибрано `--disable-web-security` та `--disable-features=IsolateOrigins,site-per-process`
|
|
665
664
|
# (вимкнення Site Isolation детектується як anti-stealth сигнал у реальному Chrome
|
|
666
665
|
# такої конфігурації не буває) та `--disable-background-networking`
|
|
@@ -747,7 +746,6 @@ F15 (§11.2): 600 → 500 за реальним заміром — PSS дере
|
|
|
747
746
|
Перевизначається env ``GRAPH_CRAWLER_BROWSER_RAM_BASELINE_MB``."""
|
|
748
747
|
|
|
749
748
|
# Дефолтні ресурси для блокування (економія трафіку та RAM)
|
|
750
|
-
# Аудит ANTIBOT_AUDIT_pooled_driver.md §2.5 / §3.5 / Крок 0:
|
|
751
749
|
# прибрано "font" і "stylesheet" — жоден реальний браузер не вантажить HTML
|
|
752
750
|
# без CSS/шрифтів; порожній network waterfall (нуль .css/.woff2) + зламаний
|
|
753
751
|
# layout під getBoundingClientRect() = поведінковий маркер рівня «скрапер».
|