graph-crawler 4.1.2__tar.gz → 4.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {graph_crawler-4.1.2/graph_crawler.egg-info → graph_crawler-4.1.4}/PKG-INFO +5 -1
- graph_crawler-4.1.4/docs/JS_REDIRECT_ANALYSIS.md +184 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/api/API.md +208 -38
- graph_crawler-4.1.4/docs/api/REST_API.md +372 -0
- graph_crawler-4.1.2/docs/INDEX.md → graph_crawler-4.1.4/docs/index.md +1 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/__init__.py +6 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/__version__.py +1 -1
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/_graph_tools.py +11 -6
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/_query_prompts.py +118 -2
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/graph_query.py +132 -58
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/graph_walker.py +108 -12
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/bedrock_model.py +192 -32
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/retry_wrapper.py +85 -74
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/recording_model.py +23 -3
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/async_.py +22 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/sync.py +9 -2
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/crawl_coordinator.py +65 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/link_processor.py +16 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/node_scanner.py +151 -3
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/spider.py +36 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/data/backends/sqlite.py +32 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/edge.py +72 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/graph.py +208 -6
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/graph_operations.py +50 -0
- graph_crawler-4.1.4/graph_crawler/domain/interfaces/driver.py +88 -0
- graph_crawler-4.1.4/graph_crawler/domain/interfaces/scanner.py +57 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/sqlite_storage.py +123 -103
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/driver.py +91 -8
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/base.py +23 -3
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/driver.py +269 -24
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/__init__.py +6 -0
- graph_crawler-4.1.4/graph_crawler/shared/utils/text_utils.py +385 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4/graph_crawler.egg-info}/PKG-INFO +5 -1
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler.egg-info/SOURCES.txt +3 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler.egg-info/requires.txt +4 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/pyproject.toml +9 -1
- graph_crawler-4.1.2/graph_crawler/domain/interfaces/driver.py +0 -52
- graph_crawler-4.1.2/graph_crawler/domain/interfaces/scanner.py +0 -39
- graph_crawler-4.1.2/graph_crawler/shared/utils/text_utils.py +0 -243
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/LICENSE +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/MANIFEST.in +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/README.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/AI_AGENT_AUDIT.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/AI_AGENT_REVIEW.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/AI_LAYER_QUALITY_REPORT.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/advanced/distributed-crawling.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/advanced/hooks-auth.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/advanced/proxy-security.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/advanced/session-management.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/architecture/ARCHITECTURE_OVERVIEW.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/architecture/COMMUNICATION_CHANNELS.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/architecture/COMPONENT_CATALOG.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/architecture/EXTENSION_POINTS.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/architecture/FACTORY_LIFECYCLE.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/architecture/LAYER_SPECIFICATION.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/architecture/PLUGIN_SYSTEM.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/core/cache-modes.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/core/deep-crawling.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/core/graph-operations.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/core/simple-crawling.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/core/url-rules.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/extraction/custom-extractors.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/extraction/plugins.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/extraction/structured-data.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/getting-started/examples.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/getting-started/installation.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/getting-started/quickstart.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/_run_recorder.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/agent.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/extraction_plugin.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/_response_normalizer.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/_schema_utils.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/anthropic_model.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/emergent_model.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/openai_model.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/query_debug.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/views.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/_core.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/_distributed.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/_shared.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/_sitemap_distributed.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/cli.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/client/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/client/client.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/crawl_monitor.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/dashboard.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/history_manager.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/project_init.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/rest_api.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/stats_collector.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/webhooks.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/websocket_manager.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/bootstrap.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/context/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/context/dependency_registry.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/context/graph_context.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/context/merge_context.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/edge_dto.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/graph_dto.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/mappers/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/mappers/edge_mapper.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/mappers/graph_mapper.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/mappers/node_mapper.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/node_dto.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/utils.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/application_container.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/driver_factory.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/base_exporter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/csv_exporter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/edge_exporter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/excel_exporter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/jsonl_exporter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/node_exporter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/parquet_exporter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/sql_exporter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/low_memory_graph_saver.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/storage_factory.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/adaptive_throttler.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/base_spider.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/celery_batch_spider.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/celery_spider.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/checkpoint.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/dead_letter_queue.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/domain_rate_limiter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/filters/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/filters/base.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/filters/domain_filter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/filters/domain_patterns.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/filters/path_filter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/incremental_strategy.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/multiprocess_spider.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/parsers/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/parsers/base.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/parsers/html_parser.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/progress_tracker.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/scheduler.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/serialization_mixin.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/sitemap_parser.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/sitemap_processor.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/sitemap_spider.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/spider_lifecycle.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/spider_refactored.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/graph_export.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/data/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/data/backends/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/data/backends/memory.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/data/interfaces.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/context/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/context/crawl_context.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/edge_analysis.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/graph_statistics.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/merge_strategies.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/node.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/registries.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/sitemap_node.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/strategies.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/events/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/events/event_bus.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/events/events.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/adapter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/control_channel.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/distributed_spider.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/event_bus.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/eviction_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/exporters.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/filter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/language_model.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/merge_context.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/node_interfaces.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/parser.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/plugin_manager.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/processor.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/scheduler.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/spider.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/stop_condition.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/unified_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/value_objects/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/value_objects/configs.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/value_objects/domain_patterns.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/value_objects/lifecycle.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/value_objects/models.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/value_objects/settings.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/value_objects/url_normalization.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/base.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/cache_middleware.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/chain.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/error_recovery_middleware.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/logging_middleware.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/proxy_health.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/proxy_middleware.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/proxy_models.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/proxy_selection.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/rate_limit_middleware.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/request_response_middleware.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/retry_middleware.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/robots_cache.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/robots_middleware.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/robots_validator.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/user_agent_middleware.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/base.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/builtin/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/builtin/stats_export_plugin.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/crawl_engine/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/crawl_engine/base.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/crawl_engine/priority_provider.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/crawl_engine/smart_crawl.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/crawl_engine/vector_crawl.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/anti_bot_detection.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/anti_bot_playwright.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/anti_bot_scripts.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/anti_bot_stealth.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/captcha/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/captcha/detector.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/captcha/models.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/captcha/plugin.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/captcha/services.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/base.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/content_extractors/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/content_extractors/base.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/content_extractors/goose3_extractor.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/content_extractors/newspaper_extractor.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/content_extractors/plugin.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/content_extractors/readability_extractor.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/defaults.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/extractors/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/extractors/email_extractor.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/extractors/phone_extractor.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/extractors/price_extractor.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/links.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/markdown_plugin.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/metadata.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/smart_page_finder.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/constants.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/exceptions.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/extractor.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/options.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/base.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/jsonld.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/microdata.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/opengraph.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/rdfa.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/twitter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/plugin.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/result.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/text.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/url_normalizer.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/vectorization/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/vectorization/batch_vectorizer.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/vectorization/realtime_vectorizer.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/vectorization/utils.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/adapters/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/adapters/base.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/adapters/beautifulsoup_adapter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/adapters/lxml_adapter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/adapters/scrapy_adapter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/adapters/selectolax_adapter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/celery_app.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/celery_batch.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/celery_job_task.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/celery_unified.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/config.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/easy_crawler.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/worker_api.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/auto_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/base.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/eviction.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/graph_repository.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/json/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/json_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/lmdb_eviction_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/memory/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/memory_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/mongodb_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/naming_strategy.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/postgresql_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/protocols/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/repository.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/sqlite/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/sqlite_eviction_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/file_job_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/file_queue_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/memory_job_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/memory_queue_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/postgresql_job_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/postgresql_queue_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/unified_storage.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/config.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/context.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/driver_cloudscraper.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/driver_curl_cffi.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/driver_httpx.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/driver_v4.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle_v2.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/headers.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/http_cache.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/rate_limiter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/retry.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/stealth_driver.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/stages.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/base_plugin.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/botasaurus_driver.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/browser/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/compression/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/compression/strategy.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/connection_pool.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/context.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/core/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/core/base_async.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/core/base_sync.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/core/mixins.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/factory.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/http/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/config.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/context.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_detector.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_solver.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/cloudflare.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/compatibility.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/enhanced_cloudflare.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/enhanced_stealth.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/form_filler.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/human_behavior.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/ram_adaptive.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/screenshot.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/stealth.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/user_profile.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/pooled_driver.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/stages.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/plugin_manager.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/protocols.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/session_adapters.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/session_manager.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/session_protocol.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/sync/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/sync/cloudscraper_driver.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/sync/requests_driver.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/tls_client.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/undetected/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/undetected/async_driver.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/undetected/driver.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/native/README.md +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/native/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/native/bloom_filter.pyx +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/native/html_parser.pyx +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/native/setup.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/native/url_utils.pyx +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/decorators/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/decorators/cache.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/decorators/log.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/decorators/retry.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/decorators/timing.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/error_tracing.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/extensions/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/extensions/memory_guard.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/extensions/state_manager.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/base.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/base_metrics_listener.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/crawl_listener.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/dlq_listener.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/error_listener.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/logging_listener.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/metrics_listener.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/node_listener.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/plugin_listener.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/storage_listener.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/url_listener.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/metrics/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/metrics/memory_profiler.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/metrics/metrics_collector.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/metrics/prometheus_metrics.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/metrics_core.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/structured_logging.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/tracing.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/optimizations/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/optimizations/generators.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/optimizations/memory_slots.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/optimizations/simhash_numba.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/py.typed +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/constants.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/dto/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/dto/edge_dto.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/dto/graph_dto.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/dto/node_dto.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/error_handling/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/error_handling/error_handler.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/exceptions.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/platform/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/platform/adaptive_resolver.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/platform/config.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/platform/detector.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/platform/diagnostics.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/platform/metrics.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/security/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/security/url_sanitizer.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/security/url_validator.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/async_compat.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/bloom_filter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/captcha/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/captcha/base.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/captcha/manager.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/captcha_bypass.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/celery_config.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/celery_helpers.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/distributed_rate_limiter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/distributed_rate_limiter_backends.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/dns_cache.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/enterprise_ua_generator.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/event_publisher_mixin.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/fast_json.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/fingerprint.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/fingerprint_data.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/fingerprint_generators.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/fingerprint_profile.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/fingerprint_stealth.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/html_utils.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/id_generator.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/adapters/__init__.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/adapters/base.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/adapters/bs4_adapter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/adapters/selectolax_adapter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/ast.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/converter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/generator.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/options.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/renderer.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/result.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/memory_optimizer.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/proxy_manager.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/rate_limiter.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/regex_validator.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/url_patterns.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/url_utils.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/user_agent_rotator.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/validation_helpers.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/visualization.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/visualization_core.py +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler.egg-info/dependency_links.txt +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler.egg-info/entry_points.txt +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler.egg-info/top_level.txt +0 -0
- {graph_crawler-4.1.2 → graph_crawler-4.1.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graph-crawler
|
|
3
|
-
Version: 4.1.
|
|
3
|
+
Version: 4.1.4
|
|
4
4
|
Summary: Sync-First бібліотека для побудови графу веб-сайтів - просто як requests!
|
|
5
5
|
Author: 0-EternalJunior-0
|
|
6
6
|
Maintainer: 0-EternalJunior-0
|
|
@@ -38,6 +38,10 @@ Requires-Dist: aiofiles>=24.1.0
|
|
|
38
38
|
Requires-Dist: aiosqlite>=0.20.0
|
|
39
39
|
Requires-Dist: pybloom-live
|
|
40
40
|
Requires-Dist: fastapi>=0.135.0
|
|
41
|
+
Requires-Dist: tenacity>=9.0.0
|
|
42
|
+
Requires-Dist: stop-words>=2018.7.23
|
|
43
|
+
Requires-Dist: tiktoken>=0.13.0
|
|
44
|
+
Requires-Dist: markdown-it-py>=4.0.0
|
|
41
45
|
Provides-Extra: native
|
|
42
46
|
Requires-Dist: cython>=3.0.0; extra == "native"
|
|
43
47
|
Requires-Dist: mmh3>=5.0.0; extra == "native"
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Аналіз: JS-редіректи й «втрата» адаптера між запитами
|
|
2
|
+
|
|
3
|
+
> Контекст задачі від користувача: на прикладі сайту `4a-games.com.mt`
|
|
4
|
+
> бібліотека сканувала сторінку вакансії як «головну», бо клієнтський JS на
|
|
5
|
+
> ній через 2–3 секунди перенаправляв браузер на корінь сайту. Цього
|
|
6
|
+
> бібліотека не помічала. Виявилось, що сайт використовує сервіс
|
|
7
|
+
> `geotargetly-api-1.com` — гео-таргетер, який читає `window.location.href`
|
|
8
|
+
> й `document.referrer`, асинхронно звертається до власного API і за
|
|
9
|
+
> правилом редіректить через `location.href = ...`.
|
|
10
|
+
|
|
11
|
+
## 1. Що помітив користувач
|
|
12
|
+
|
|
13
|
+
* Відкриваєш у браузері `https://www.4a-games.com.mt/senior-technical-animator-new-ip`
|
|
14
|
+
напряму — через пару секунд клієнтський JS виконує
|
|
15
|
+
`window.location = "https://www.4a-games.com.mt/"`. Без правильного
|
|
16
|
+
`Referer` / cookies потрапити на сторінку вакансії неможливо.
|
|
17
|
+
* Якщо відкрити `https://www.4a-games.com.mt/careers` та **колесом миші**
|
|
18
|
+
клікнути на посилання вакансії — у новій вкладці сторінка відкривається
|
|
19
|
+
й залишається. Це працює лише в **одному й тому самому браузері**, де
|
|
20
|
+
передались і `Referer`, і власні cookies.
|
|
21
|
+
* Якщо скопіювати ту саму URL в інший браузер / іншу вкладку без переходу
|
|
22
|
+
через `/careers` — знов спрацьовує редірект.
|
|
23
|
+
* Простий `GET`-запит через `requests` / `curl` показує повний HTML
|
|
24
|
+
вакансії — бо JS на сервері не виконується.
|
|
25
|
+
|
|
26
|
+
Висновок: антипарсерний захист реалізований **на клієнті**, через JS,
|
|
27
|
+
який перевіряє `document.referrer` / стан з `/careers` (cookies, history,
|
|
28
|
+
sessionStorage) і у разі «холодного» переходу прибирає користувача на
|
|
29
|
+
корінь сайту.
|
|
30
|
+
|
|
31
|
+
## 2. Що з цим робить наша бібліотека сьогодні
|
|
32
|
+
|
|
33
|
+
Перевіряв `graph_crawler/infrastructure/transport/playwright/driver.py`,
|
|
34
|
+
гілка `main`, файли:
|
|
35
|
+
|
|
36
|
+
* `PlaywrightDriver._fetch_async` — основний метод завантаження.
|
|
37
|
+
* `PlaywrightDriver._create_context` — створює `BrowserContext`.
|
|
38
|
+
* `PlaywrightDriver.fetch_many` — паралельне завантаження.
|
|
39
|
+
|
|
40
|
+
Знайдені слабкі місця:
|
|
41
|
+
|
|
42
|
+
| # | Проблема | Файл / місце | Наслідок |
|
|
43
|
+
|---|----------|--------------|----------|
|
|
44
|
+
| 1 | **На кожен `fetch()` створюється новий `BrowserContext`**: `pw_context = await self.browser.new_context(...)` | `driver.py:320` | Cookies, sessionStorage, історія навігації між URL-ами **повністю губляться**. Скрипт антибота вважає кожен запит «холодним». |
|
|
45
|
+
| 2 | **`page.goto(url, wait_until=..., timeout=...)` НЕ передає `referer`** | `driver.py:477` | `document.referrer` у браузері порожній → JS вирішує, що користувач «прийшов з нізвідки», і редіректить. |
|
|
46
|
+
| 3 | **Після `goto` ми не звіряємо `page.url` з оригінальним `url`**: одразу читаємо `await page.content()` | `driver.py:524` | Якщо JS уже встиг зробити `window.location = "/"` — ми збережемо HTML головної як HTML вакансії. Граф буде зіпсований. |
|
|
47
|
+
| 4 | **`FetchResponse.final_url` і `redirect_chain` у Playwright-драйвері не заповнюються** (на відміну від `AsyncDriver` у `async_http/driver.py:303-304`). | `driver.py:574-580` | Подальші шари не мають можливості зрозуміти, що нас підмінили URL — навіть якщо вони перевіряють `is_redirect`. |
|
|
48
|
+
| 5 | **Між `page.goto` й `page.content()` немає «settle» паузи** для виявлення JS-редіректу (`window.location`, `meta refresh`, `<script>`). | `driver.py:497-524` | Сторінка може ще «їхати» — ми зчитуємо HTML на льоту. |
|
|
49
|
+
| 6 | `AsyncHTTPDriver` тримає одну `aiohttp.ClientSession` на драйвер → cookies зберігаються між URL-ами на одному домені. **АЛЕ** на goto/fetch не пробрасується `Referer`-заголовок із посилаючої сторінки. | `async_http/driver.py:294` | Для HTTP-режиму та сама проблема — `Referer`-захист не пройдемо. |
|
|
50
|
+
|
|
51
|
+
## 3. Чому це важливо для графа
|
|
52
|
+
|
|
53
|
+
`NodeScanner.scan_node`
|
|
54
|
+
(`application/use_cases/crawling/node_scanner.py:147`) викликає
|
|
55
|
+
`await self.driver.fetch(node.url)` без жодного контексту про те, **з якої
|
|
56
|
+
сторінки** ми перейшли. У результаті:
|
|
57
|
+
|
|
58
|
+
1. На `node` зі сторінки `/senior-...-new-ip` ми отримуємо HTML головної.
|
|
59
|
+
2. На цій сторінці є посилання `/careers`, `/contact` тощо — ми ствердно
|
|
60
|
+
додаємо їх як дочірні до вакансії. Граф **жирніє хибними ребрами**.
|
|
61
|
+
3. Якщо потім бібліотека повторно зайде на `/senior-...-new-ip` — знов
|
|
62
|
+
побачить ту саму «головну», тому що дедуплікатор за оригінальним `url`
|
|
63
|
+
дасть кеш.
|
|
64
|
+
|
|
65
|
+
## 4. Як це працює всередині (короткий огляд)
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
NodeScanner.scan_node(node)
|
|
69
|
+
│
|
|
70
|
+
▼
|
|
71
|
+
PlaywrightDriver.fetch(url)
|
|
72
|
+
│
|
|
73
|
+
├── _init_playwright() // browser один раз на драйвер
|
|
74
|
+
├── _create_context() // нов. context, чисті cookies!
|
|
75
|
+
├── pw_context.new_page()
|
|
76
|
+
├── page.goto(url) // referer ВІДСУТНІЙ
|
|
77
|
+
├── await page.content() // HTML «як є»
|
|
78
|
+
└── return FetchResponse(...) // final_url=None завжди
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 5. Що ми пропонуємо змінити (мінімально-інвазивно)
|
|
82
|
+
|
|
83
|
+
Усі правки сумісні зі старим API — нові аргументи опціональні.
|
|
84
|
+
|
|
85
|
+
### 5.1. Опціональний `referer` для `fetch()`
|
|
86
|
+
|
|
87
|
+
* `BaseDriver.fetch(self, url, *, referer: str | None = None)` — розширити
|
|
88
|
+
сигнатуру (без поломки існуючих імплементацій, бо вони можуть
|
|
89
|
+
проігнорувати).
|
|
90
|
+
* У `PlaywrightDriver._fetch_async`: передати `referer` у
|
|
91
|
+
`page.goto(url, referer=referer, ...)`. Playwright офіційно підтримує
|
|
92
|
+
цей аргумент.
|
|
93
|
+
* У `AsyncDriver.fetch`: додати `Referer` у `request_headers`, якщо
|
|
94
|
+
переданий.
|
|
95
|
+
|
|
96
|
+
### 5.2. Persistent context per-domain у `PlaywrightDriver`
|
|
97
|
+
|
|
98
|
+
* Нова опція в конфізі: `persistent_context_per_domain: bool = False`.
|
|
99
|
+
* Якщо `True` — драйвер тримає кеш `dict[domain] -> BrowserContext` і
|
|
100
|
+
переюзає його. Cookies, sessionStorage, історія зберігаються →
|
|
101
|
+
крос-сторінкові переходи на одному домені імітують роботу справжнього
|
|
102
|
+
браузера.
|
|
103
|
+
* Закриваються контексти у `close()`.
|
|
104
|
+
|
|
105
|
+
### 5.3. Виявлення JS-редіректу: settle-затримка + перевірка `page.url`
|
|
106
|
+
|
|
107
|
+
* Нова опція: `post_navigation_settle_ms: int = 0` (за замовч. 0, щоб
|
|
108
|
+
нічого не зламати в існуючих юзерах).
|
|
109
|
+
* Якщо `> 0` — після `page.goto` (і `wait_for_selector`) робимо
|
|
110
|
+
`await asyncio.sleep(post_navigation_settle_ms / 1000)`.
|
|
111
|
+
* Після цього порівнюємо `page.url` з оригінальним `url`. Якщо
|
|
112
|
+
відрізняються — заповнюємо `final_url` у `FetchResponse`, додатково лог
|
|
113
|
+
`WARNING` про JS-редірект.
|
|
114
|
+
|
|
115
|
+
### 5.4. Заповнення `redirect_chain` і `final_url` у Playwright-режимі
|
|
116
|
+
|
|
117
|
+
* Підписка `page.on("framenavigated", ...)` лише для **main frame**:
|
|
118
|
+
складаємо `redirect_chain` (без дублів) для подальшого аналізу.
|
|
119
|
+
* `final_url = page.url` **після** `settle`-затримки (вже після можливих JS
|
|
120
|
+
редіректів) — це фінальна URL у браузерному сенсі.
|
|
121
|
+
* Передаємо ці поля у
|
|
122
|
+
`FetchResponse(url=url, final_url=..., redirect_chain=...)`.
|
|
123
|
+
|
|
124
|
+
### 5.5. Прокидання `referer` у `NodeScanner`
|
|
125
|
+
|
|
126
|
+
* `Node` уже має `parent_id`. У `scan_node` шукати URL батька з графа (за
|
|
127
|
+
`parent_id`) і викликати `driver.fetch(node.url, referer=parent_url)`.
|
|
128
|
+
* Для batch-сценарію (`fetch_many`) — додаємо опціональний паралельний
|
|
129
|
+
список `referrers: list[str | None] | None` й розкладаємо по
|
|
130
|
+
внутрішніх `fetch`. Якщо драйвер не підтримує — ігноруємо (зворотна
|
|
131
|
+
сумісність).
|
|
132
|
+
|
|
133
|
+
### 5.6. Перевірка URL після сканування
|
|
134
|
+
|
|
135
|
+
Як побажав користувач — «перевіряти не тільки на початку, а й у кінці
|
|
136
|
+
сканування сторінки». Це 5.3 + 5.4: якщо `final_url != url`, можна:
|
|
137
|
+
|
|
138
|
+
* пропустити node як «нерелевантний»;
|
|
139
|
+
* або зберегти результат як `node.canonical_url = final_url`;
|
|
140
|
+
* або повторно поставити в чергу новий URL для повноцінного сканування.
|
|
141
|
+
|
|
142
|
+
Наразі обрано **м'який варіант**: фіксуємо `final_url` у `FetchResponse`,
|
|
143
|
+
лог-warning, і дозволяємо вищому шару (graph_walker / node_scanner)
|
|
144
|
+
прийняти рішення на основі прапора `is_redirect`. Це не змінює існуючу
|
|
145
|
+
логіку графа для тих, хто `persistent_context` не вмикає.
|
|
146
|
+
|
|
147
|
+
## 6. План тестів
|
|
148
|
+
|
|
149
|
+
1. Юніт-тест `tests/test_playwright_referer.py`:
|
|
150
|
+
* мокаємо `playwright.async_api.async_playwright`;
|
|
151
|
+
* перевіряємо, що `page.goto` був викликаний саме з `referer=...`;
|
|
152
|
+
* перевіряємо, що `FetchResponse.final_url` заповнюється коли
|
|
153
|
+
mocked-page міняє `page.url` під час settle.
|
|
154
|
+
|
|
155
|
+
2. Юніт-тест `tests/test_playwright_persistent_context.py`:
|
|
156
|
+
* mock browser → `new_context` має викликатися **один раз** для двох
|
|
157
|
+
послідовних `fetch()` на одному домені, коли
|
|
158
|
+
`persistent_context_per_domain=True`.
|
|
159
|
+
|
|
160
|
+
3. Manual-тест `tests/manual_4a_games_test.py` — відтворює 4 сценарії: raw
|
|
161
|
+
HTTP, чистий Playwright, новий контекст-на-URL, контекст через
|
|
162
|
+
`/careers` + referer. Файл self-contained, можна запустити локально.
|
|
163
|
+
|
|
164
|
+
## 7. Залишилось зовні зони (для майбутніх ітерацій)
|
|
165
|
+
|
|
166
|
+
* `botasaurus_driver`, `undetected/*`, `cloudscraper_driver` — на них цей
|
|
167
|
+
самий референс не пробрасується. Якщо проект активно ними користується,
|
|
168
|
+
варто пройти тим же шаблоном.
|
|
169
|
+
* Cookies SQLite-storage між запусками процесу: зараз `SessionManager`
|
|
170
|
+
уміє це для `requests`/`httpx`, але не для Playwright. Кандидат на
|
|
171
|
+
наступний PR — використати `BrowserContext.storage_state()` для
|
|
172
|
+
збереження/завантаження.
|
|
173
|
+
|
|
174
|
+
## 8. Як перевірити вручну
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# в кореневій папці job/
|
|
178
|
+
python tests/manual_4a_games_test.py
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
У середовищі CI (без гео-Мальти) JS-редірект може не спрацювати — це
|
|
182
|
+
нормально. Логіка `final_url != url` має лишитися **функціонально
|
|
183
|
+
коректною** незалежно від того, чи редіректить сайт у цьому конкретному
|
|
184
|
+
оточенні.
|
|
@@ -79,8 +79,9 @@ def crawl(
|
|
|
79
79
|
max_depth: int = 3,
|
|
80
80
|
max_pages: Optional[int] = 100,
|
|
81
81
|
same_domain: bool = True,
|
|
82
|
+
allowed_domains: Optional[list[str]] = None,
|
|
82
83
|
timeout: Optional[int] = None,
|
|
83
|
-
request_delay: float =
|
|
84
|
+
request_delay: Optional[float] = None,
|
|
84
85
|
follow_links: bool = True,
|
|
85
86
|
|
|
86
87
|
# Компоненти
|
|
@@ -94,7 +95,6 @@ def crawl(
|
|
|
94
95
|
node_class: Optional[type[Node]] = None,
|
|
95
96
|
edge_class: Optional[type[Edge]] = None,
|
|
96
97
|
url_rules: Optional[list[URLRule]] = None,
|
|
97
|
-
edge_rules: Optional[list[EdgeRule]] = None,
|
|
98
98
|
edge_strategy: str = "all",
|
|
99
99
|
|
|
100
100
|
# Callbacks
|
|
@@ -103,45 +103,64 @@ def crawl(
|
|
|
103
103
|
on_error: Optional[Callable] = None,
|
|
104
104
|
on_completed: Optional[Callable] = None,
|
|
105
105
|
|
|
106
|
+
# Low-memory режим
|
|
107
|
+
low_memory_mode: bool = False,
|
|
108
|
+
evict_threshold: int = 500,
|
|
109
|
+
eviction_storage_path: Optional[str] = None,
|
|
110
|
+
|
|
106
111
|
# Distributed mode
|
|
107
112
|
wrapper: Optional[dict] = None,
|
|
108
113
|
) -> Graph
|
|
109
114
|
```
|
|
110
115
|
|
|
111
|
-
Синхронний краулінг
|
|
116
|
+
Синхронний краулінг веб-сайту. Працює як `requests` — функція приймає URL і повертає
|
|
117
|
+
результат, не вимагаючи `async/await` або контекстних менеджерів.
|
|
112
118
|
|
|
113
|
-
|
|
119
|
+
**Призначення:** одноразовий запуск краулінгу з простого Python-скрипта, CLI або
|
|
120
|
+
notebook. Внутрішньо викликає `async_crawl()` через `asyncio.run` або через
|
|
121
|
+
ThreadPoolExecutor (якщо вже працює event-loop, наприклад у Jupyter).
|
|
114
122
|
|
|
115
123
|
**Параметри:**
|
|
116
124
|
|
|
117
|
-
| Параметр
|
|
118
|
-
|
|
|
119
|
-
| `url`
|
|
120
|
-
| `seed_urls`
|
|
121
|
-
| `base_graph`
|
|
122
|
-
| `max_depth`
|
|
123
|
-
| `max_pages`
|
|
124
|
-
| `same_domain`
|
|
125
|
-
| `
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `edge_strategy`
|
|
138
|
-
| `on_progress`
|
|
139
|
-
| `on_node_scanned`
|
|
140
|
-
| `on_error`
|
|
141
|
-
| `on_completed`
|
|
142
|
-
| `
|
|
143
|
-
|
|
144
|
-
|
|
125
|
+
| Параметр | Тип | Default | Опис |
|
|
126
|
+
| ----------------------- | ------------------ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
127
|
+
| `url` | str \| None | None | Початковий URL краулінгу. Обов'язковий, якщо не передано `seed_urls` або `base_graph`. |
|
|
128
|
+
| `seed_urls` | list[str] \| None | None | Список початкових URL для багатоточкового старту. Якщо переданий разом з `url`, всі URL потрапляють до черги. |
|
|
129
|
+
| `base_graph` | Graph \| None | None | Готовий граф, з якого продовжується краулінг (інкрементальний режим). Усі його ноди вважаються відомими. |
|
|
130
|
+
| `max_depth` | int | 3 | Максимальна глибина обходу від початкового URL. `0` — лише seed-ноди. |
|
|
131
|
+
| `max_pages` | int \| None | 100 | Жорсткий ліміт кількості завантажених сторінок. `None` — без ліміту. |
|
|
132
|
+
| `same_domain` | bool | True | Якщо `True`, краулер не виходить за межі домену seed-URL. Ігнорується, якщо передано `allowed_domains`. |
|
|
133
|
+
| `allowed_domains` | list[str] \| None | None | Білий список доменів. Приймає прямі домени (`example.com`), вайлдкарди (`*.example.com`) або алiаси: `domain`, `subdomains`, `domain+subdomains`, `*` (будь-який). |
|
|
134
|
+
| `timeout` | int \| None | None | Максимальний час усього краулінгу в секундах. Після таймауту повертається частковий граф. |
|
|
135
|
+
| `request_delay` | float \| None | None | Затримка (сек) між запитами до одного домену. `None` — використовується `DEFAULT_REQUEST_DELAY` з `shared.constants`. |
|
|
136
|
+
| `follow_links` | bool | True | Якщо `False`, краулер обробляє лише seed URL без розкриття знайдених посилань. |
|
|
137
|
+
| `driver` | str \| IDriver | "http" | Транспорт для завантаження сторінок. Допустимі літерали: `http`, `async`, `playwright`, `stealth`. Можна передати власний інстанс `IDriver`. |
|
|
138
|
+
| `driver_config` | dict \| None | None | Параметри драйвера: `user_agent`, `headers`, `headless`, `browser_type`, `viewport_*`, `page_load_timeout` тощо. Див. розділ `DriverSettings`. |
|
|
139
|
+
| `storage` | str \| IStorage | "memory" | Backend для збереження графу: `memory`, `json`, `sqlite`, `postgresql`, `mongodb`. Або власний інстанс `IStorage`. |
|
|
140
|
+
| `storage_config` | dict \| None | None | Параметри storage: `path`, `host`, `port`, `database`, `username`, `password`. |
|
|
141
|
+
| `plugins` | list \| None | default set | Список `BaseNodePlugin` для extraction (metadata, links, text, structured data, NLP тощо). При `None` використовується дефолтний набір. |
|
|
142
|
+
| `node_class` | type[Node] \| None | None | Підкласовий Node для додавання власних полів та методів обробки HTML. |
|
|
143
|
+
| `edge_class` | type[Edge] \| None | None | Підкласовий Edge з власними атрибутами. |
|
|
144
|
+
| `url_rules` | list[Rule] \| None | None | Список правил для priority/should\_scan/should\_follow\_links по regex-патернах URL. |
|
|
145
|
+
| `edge_strategy` | str | "all" | Стратегія створення ребер: `all`, `new_only`, `max_in_degree`, `same_depth_only`, `deeper_only`, `first_encounter_only`. |
|
|
146
|
+
| `on_progress` | Callable \| None | None | Колбек `fn(event_data)` для оновлень прогресу (виклик з пулу подій `PROGRESS_UPDATE`). |
|
|
147
|
+
| `on_node_scanned` | Callable \| None | None | Колбек після завершення сканування кожної ноди. |
|
|
148
|
+
| `on_error` | Callable \| None | None | Колбек з payload помилки (URL, виняток, трасування). |
|
|
149
|
+
| `on_completed` | Callable \| None | None | Колбек, що викликається один раз після завершення всього краулінгу. |
|
|
150
|
+
| `low_memory_mode` | bool | False | Увімкнення режиму обмеженого RAM. Виштовхує (`evict`) старі ноди на диск, тримаючи в пам'яті лише активне вікно. |
|
|
151
|
+
| `evict_threshold` | int | 500 | Поріг кількості нод у пам'яті, після якого починається eviction. Діє лише з `low_memory_mode=True`. |
|
|
152
|
+
| `eviction_storage_path` | str \| None | None | Шлях до директорії або файлу для evicted-нод. `None` — тимчасова директорія. |
|
|
153
|
+
| `wrapper` | dict \| None | None | Конфігурація розподіленого режиму (`broker`, `database`, `workers`). За наявності перемикає виконання на `distributed_crawl`. |
|
|
154
|
+
|
|
155
|
+
**Повертає:** `Graph` — повний граф сайту з нодами (`graph.nodes`) та ребрами
|
|
156
|
+
(`graph.iter_edges()`). У випадку timeout повертається частковий граф.
|
|
157
|
+
|
|
158
|
+
**Винятки:**
|
|
159
|
+
|
|
160
|
+
- `ValueError` — якщо не передано жодного з `url`, `seed_urls`, `base_graph`,
|
|
161
|
+
або у distributed-режимі відсутній `url`.
|
|
162
|
+
- `TimeoutError` — якщо краулінг не завершився у межах `timeout`.
|
|
163
|
+
- `GraphCrawlerError` і підкласи — див. розділ **Exceptions**.
|
|
145
164
|
|
|
146
165
|
**Приклади:**
|
|
147
166
|
|
|
@@ -198,13 +217,63 @@ graph = gc.crawl("https://example.com", wrapper=config)
|
|
|
198
217
|
|
|
199
218
|
```python
|
|
200
219
|
async def async_crawl(
|
|
201
|
-
url: str,
|
|
220
|
+
url: Optional[str] = None,
|
|
202
221
|
*,
|
|
203
|
-
|
|
222
|
+
seed_urls: Optional[list[str]] = None,
|
|
223
|
+
base_graph: Optional[Graph] = None,
|
|
224
|
+
|
|
225
|
+
# Основні параметри
|
|
226
|
+
max_depth: int = 3,
|
|
227
|
+
max_pages: Optional[int] = 100,
|
|
228
|
+
same_domain: bool = True,
|
|
229
|
+
allowed_domains: Optional[list[str]] = None,
|
|
230
|
+
timeout: Optional[int] = None,
|
|
231
|
+
request_delay: Optional[float] = None,
|
|
232
|
+
follow_links: bool = True,
|
|
233
|
+
|
|
234
|
+
# Компоненти
|
|
235
|
+
driver: Optional[str | IDriver] = None,
|
|
236
|
+
driver_config: Optional[dict] = None,
|
|
237
|
+
storage: Optional[str | IStorage] = None,
|
|
238
|
+
storage_config: Optional[dict] = None,
|
|
239
|
+
plugins: Optional[list[BaseNodePlugin]] = None,
|
|
240
|
+
|
|
241
|
+
# Кастомізація
|
|
242
|
+
node_class: Optional[type[Node]] = None,
|
|
243
|
+
edge_class: Optional[type[Edge]] = None,
|
|
244
|
+
url_rules: Optional[list[URLRule]] = None,
|
|
245
|
+
edge_strategy: str = "all",
|
|
246
|
+
|
|
247
|
+
# Callbacks
|
|
248
|
+
on_progress: Optional[Callable] = None,
|
|
249
|
+
on_node_scanned: Optional[Callable] = None,
|
|
250
|
+
on_error: Optional[Callable] = None,
|
|
251
|
+
on_completed: Optional[Callable] = None,
|
|
252
|
+
|
|
253
|
+
# Low-memory режим
|
|
254
|
+
low_memory_mode: bool = False,
|
|
255
|
+
evict_threshold: int = 500,
|
|
256
|
+
eviction_storage_path: Optional[str] = None,
|
|
257
|
+
|
|
258
|
+
# AI Agent integration
|
|
259
|
+
crawl_context: Optional[Any] = None,
|
|
260
|
+
control_channel: Optional[Any] = None,
|
|
204
261
|
) -> Graph
|
|
205
262
|
```
|
|
206
263
|
|
|
207
|
-
|
|
264
|
+
Асинхронна версія `crawl()`. Викликається з вже запущеного event-loop і дозволяє
|
|
265
|
+
паралельний краулінг кількох сайтів через `asyncio.gather`. Підтримує всі параметри
|
|
266
|
+
синхронної функції (крім `wrapper`, який наявний лише у `crawl()`), а також два
|
|
267
|
+
додаткові параметри для інтеграції з AI-агентом.
|
|
268
|
+
|
|
269
|
+
**Додаткові параметри (відсутні у `crawl()`):**
|
|
270
|
+
|
|
271
|
+
| Параметр | Тип | Default | Опис |
|
|
272
|
+
| ----------------- | ---------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
273
|
+
| `crawl_context` | Any \| None | None | Зовнішній контекст, що проброшується в плагіни та спайдер (зазвичай об'єкт `CrawlContext` з `ai/`). Дозволяє агенту читати/писати спільний стан під час краулінгу. |
|
|
274
|
+
| `control_channel` | Any \| None | None | Канал керування (наприклад `asyncio.Queue` або `ControlChannel`), через який зовнішній код може динамічно подавати команди: `pause`, `resume`, `stop`, `expand`. |
|
|
275
|
+
|
|
276
|
+
**Повертає:** `Graph` — побудований граф сайту.
|
|
208
277
|
|
|
209
278
|
**Приклади:**
|
|
210
279
|
|
|
@@ -220,6 +289,25 @@ graphs = await asyncio.gather(
|
|
|
220
289
|
gc.async_crawl("https://site1.com"),
|
|
221
290
|
gc.async_crawl("https://site2.com"),
|
|
222
291
|
)
|
|
292
|
+
|
|
293
|
+
# Контрольований краулінг з AI-агентом
|
|
294
|
+
from graph_crawler.ai import CrawlContext, ControlChannel
|
|
295
|
+
|
|
296
|
+
ctx = CrawlContext()
|
|
297
|
+
channel = ControlChannel()
|
|
298
|
+
|
|
299
|
+
task = asyncio.create_task(
|
|
300
|
+
gc.async_crawl(
|
|
301
|
+
"https://example.com",
|
|
302
|
+
crawl_context=ctx,
|
|
303
|
+
control_channel=channel,
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Зупинити краулінг ззовні
|
|
308
|
+
await channel.send("pause")
|
|
309
|
+
await asyncio.sleep(5)
|
|
310
|
+
await channel.send("resume")
|
|
223
311
|
```
|
|
224
312
|
|
|
225
313
|
---
|
|
@@ -238,13 +326,57 @@ def crawl_sitemap(
|
|
|
238
326
|
storage: Optional[str | IStorage] = None,
|
|
239
327
|
storage_config: Optional[dict] = None,
|
|
240
328
|
wrapper: Optional[dict] = None,
|
|
329
|
+
url_rules: Optional[list[URLRule]] = None,
|
|
330
|
+
max_sitemaps: Optional[int] = None,
|
|
331
|
+
max_depth: Optional[int] = None,
|
|
241
332
|
on_progress: Optional[Callable] = None,
|
|
242
333
|
on_error: Optional[Callable] = None,
|
|
243
334
|
on_completed: Optional[Callable] = None,
|
|
335
|
+
http_client: str = "requests",
|
|
336
|
+
browser_config: Optional[dict] = None,
|
|
244
337
|
) -> Graph
|
|
245
338
|
```
|
|
246
339
|
|
|
247
|
-
Краулінг через sitemap.xml
|
|
340
|
+
Краулінг через `sitemap.xml`. Алгоритм:
|
|
341
|
+
|
|
342
|
+
1. Завантажує `<url>/robots.txt`, шукає директиви `Sitemap:`.
|
|
343
|
+
2. Завантажує знайдені sitemap-файли (включно з sitemap-індексами).
|
|
344
|
+
3. Рекурсивно проходить індекси, формуючи дерево.
|
|
345
|
+
4. (Опційно) розкриває URL зі sitemap у ноди графу.
|
|
346
|
+
|
|
347
|
+
Результат — `Graph`, де root-нода це `robots.txt`, далі sitemap-індекс(и),
|
|
348
|
+
далі окремі sitemap, далі (опційно) кінцеві URL.
|
|
349
|
+
|
|
350
|
+
**Параметри:**
|
|
351
|
+
|
|
352
|
+
| Параметр | Тип | Default | Опис |
|
|
353
|
+
| ---------------- | ------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
354
|
+
| `url` | str | — | Корінь сайту (наприклад `https://example.com`). Sitemap буде шукатися автоматично через `robots.txt` та стандартні шляхи (`/sitemap.xml`). |
|
|
355
|
+
| `max_urls` | int \| None | None | Максимальна кількість URL зі sitemap, які потраплять у граф. `None` = без обмежень. |
|
|
356
|
+
| `include_urls` | bool | True | Якщо `False`, граф містить лише структуру sitemap-файлів без розгортання окремих URL — корисно для аналізу архітектури. |
|
|
357
|
+
| `timeout` | int \| None | None | Таймаут (сек) всього sitemap-краулінгу. При перевищенні повертається частковий граф. |
|
|
358
|
+
| `driver` | str \| IDriver | "http" | Транспорт для завантаження sitemap-файлів. Див. опис у `crawl()`. |
|
|
359
|
+
| `driver_config` | dict \| None | None | Налаштування драйвера. |
|
|
360
|
+
| `storage` | str \| IStorage | "memory" | Backend збереження графу. |
|
|
361
|
+
| `storage_config` | dict \| None | None | Налаштування storage. |
|
|
362
|
+
| `wrapper` | dict \| None | None | Конфіг distributed sitemap-краулінгу. При наявності використовується `distributed_crawl_sitemap`. |
|
|
363
|
+
| `url_rules` | list[Rule] \| None | None | Правила фільтрації/пріоритизації URL зі sitemap (regex + дії). |
|
|
364
|
+
| `max_sitemaps` | int \| None | None | Жорсткий ліміт кількості sitemap-файлів, які буде завантажено (захист від бомб з 10k sitemap-індексами). |
|
|
365
|
+
| `max_depth` | int \| None | None | Максимальна глибина вкладеності sitemap-індексів. `None` — використовується default з `CrawlerConfig` (3). |
|
|
366
|
+
| `on_progress` | Callable \| None | None | Колбек на події `PAGE_CRAWLED` (кожен завантажений sitemap або URL). |
|
|
367
|
+
| `on_error` | Callable \| None | None | Колбек на помилки парсингу/мережі. |
|
|
368
|
+
| `on_completed` | Callable \| None | None | Колбек на `SITEMAP_CRAWL_COMPLETED`. |
|
|
369
|
+
| `http_client` | str | "requests" | Транспорт для завантаження XML: `requests` (синхронний, швидкий) або `browser` (через Playwright — для сайтів з Cloudflare/JS-захистом sitemap'у). |
|
|
370
|
+
| `browser_config` | dict \| None | None | Налаштування браузера, якщо `http_client="browser"` (headless, user_agent, proxy тощо). |
|
|
371
|
+
|
|
372
|
+
**Повертає:** `Graph` — структуроване дерево sitemap'у. Приклад:
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
robots.txt (root)
|
|
376
|
+
└── sitemap_index.xml
|
|
377
|
+
├── sitemap-posts.xml (URLs: 100)
|
|
378
|
+
└── sitemap-pages.xml (URLs: 50)
|
|
379
|
+
```
|
|
248
380
|
|
|
249
381
|
**Приклади:**
|
|
250
382
|
|
|
@@ -255,8 +387,45 @@ graph = gc.crawl_sitemap("https://example.com")
|
|
|
255
387
|
# Тільки структура (без кінцевих URL)
|
|
256
388
|
graph = gc.crawl_sitemap("https://example.com", include_urls=False)
|
|
257
389
|
|
|
258
|
-
# З лімітом
|
|
259
|
-
graph = gc.crawl_sitemap(
|
|
390
|
+
# З лімітом і браузерним клієнтом для захищених сайтів
|
|
391
|
+
graph = gc.crawl_sitemap(
|
|
392
|
+
"https://example.com",
|
|
393
|
+
max_urls=1000,
|
|
394
|
+
max_sitemaps=20,
|
|
395
|
+
http_client="browser",
|
|
396
|
+
browser_config={"headless": True},
|
|
397
|
+
)
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## async_crawl_sitemap()
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
async def async_crawl_sitemap(
|
|
406
|
+
url: str,
|
|
407
|
+
*,
|
|
408
|
+
max_urls: Optional[int] = None,
|
|
409
|
+
include_urls: bool = True,
|
|
410
|
+
timeout: Optional[int] = None,
|
|
411
|
+
driver: Optional[str | IDriver] = None,
|
|
412
|
+
driver_config: Optional[dict] = None,
|
|
413
|
+
storage: Optional[str | IStorage] = None,
|
|
414
|
+
storage_config: Optional[dict] = None,
|
|
415
|
+
url_rules: Optional[list[URLRule]] = None,
|
|
416
|
+
max_sitemaps: Optional[int] = None,
|
|
417
|
+
max_depth: Optional[int] = None,
|
|
418
|
+
on_progress: Optional[Callable] = None,
|
|
419
|
+
on_error: Optional[Callable] = None,
|
|
420
|
+
on_completed: Optional[Callable] = None,
|
|
421
|
+
) -> Graph
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Асинхронний аналог `crawl_sitemap()` для виклику з вже запущеного event-loop.
|
|
425
|
+
Підтримує ті ж параметри (без `wrapper`, `http_client`, `browser_config`).
|
|
426
|
+
|
|
427
|
+
```python
|
|
428
|
+
graph = await gc.async_crawl_sitemap("https://example.com", max_urls=500)
|
|
260
429
|
```
|
|
261
430
|
|
|
262
431
|
---
|
|
@@ -1193,6 +1362,7 @@ from graph_crawler.extensions.plugins.node.structured_data import (
|
|
|
1193
1362
|
|
|
1194
1363
|
## Наступні кроки
|
|
1195
1364
|
|
|
1365
|
+
- [REST API Reference →](REST_API.md)
|
|
1196
1366
|
- [Getting Started →](../getting-started/quickstart.md)
|
|
1197
1367
|
- [Plugin System →](../extraction/plugins.md)
|
|
1198
1368
|
- [Architecture →](../architecture/ARCHITECTURE_OVERVIEW.md)
|