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.
Files changed (474) hide show
  1. {graph_crawler-4.1.2/graph_crawler.egg-info → graph_crawler-4.1.4}/PKG-INFO +5 -1
  2. graph_crawler-4.1.4/docs/JS_REDIRECT_ANALYSIS.md +184 -0
  3. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/api/API.md +208 -38
  4. graph_crawler-4.1.4/docs/api/REST_API.md +372 -0
  5. graph_crawler-4.1.2/docs/INDEX.md → graph_crawler-4.1.4/docs/index.md +1 -0
  6. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/__init__.py +6 -0
  7. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/__version__.py +1 -1
  8. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/_graph_tools.py +11 -6
  9. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/_query_prompts.py +118 -2
  10. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/graph_query.py +132 -58
  11. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/graph_walker.py +108 -12
  12. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/bedrock_model.py +192 -32
  13. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/retry_wrapper.py +85 -74
  14. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/recording_model.py +23 -3
  15. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/async_.py +22 -0
  16. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/sync.py +9 -2
  17. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/crawl_coordinator.py +65 -0
  18. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/link_processor.py +16 -0
  19. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/node_scanner.py +151 -3
  20. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/spider.py +36 -0
  21. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/data/backends/sqlite.py +32 -0
  22. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/edge.py +72 -0
  23. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/graph.py +208 -6
  24. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/graph_operations.py +50 -0
  25. graph_crawler-4.1.4/graph_crawler/domain/interfaces/driver.py +88 -0
  26. graph_crawler-4.1.4/graph_crawler/domain/interfaces/scanner.py +57 -0
  27. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/sqlite_storage.py +123 -103
  28. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/driver.py +91 -8
  29. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/base.py +23 -3
  30. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/driver.py +269 -24
  31. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/__init__.py +6 -0
  32. graph_crawler-4.1.4/graph_crawler/shared/utils/text_utils.py +385 -0
  33. {graph_crawler-4.1.2 → graph_crawler-4.1.4/graph_crawler.egg-info}/PKG-INFO +5 -1
  34. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler.egg-info/SOURCES.txt +3 -0
  35. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler.egg-info/requires.txt +4 -0
  36. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/pyproject.toml +9 -1
  37. graph_crawler-4.1.2/graph_crawler/domain/interfaces/driver.py +0 -52
  38. graph_crawler-4.1.2/graph_crawler/domain/interfaces/scanner.py +0 -39
  39. graph_crawler-4.1.2/graph_crawler/shared/utils/text_utils.py +0 -243
  40. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/LICENSE +0 -0
  41. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/MANIFEST.in +0 -0
  42. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/README.md +0 -0
  43. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/AI_AGENT_AUDIT.md +0 -0
  44. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/AI_AGENT_REVIEW.md +0 -0
  45. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/AI_LAYER_QUALITY_REPORT.md +0 -0
  46. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/advanced/distributed-crawling.md +0 -0
  47. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/advanced/hooks-auth.md +0 -0
  48. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/advanced/proxy-security.md +0 -0
  49. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/advanced/session-management.md +0 -0
  50. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/architecture/ARCHITECTURE_OVERVIEW.md +0 -0
  51. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/architecture/COMMUNICATION_CHANNELS.md +0 -0
  52. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/architecture/COMPONENT_CATALOG.md +0 -0
  53. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/architecture/EXTENSION_POINTS.md +0 -0
  54. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/architecture/FACTORY_LIFECYCLE.md +0 -0
  55. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/architecture/LAYER_SPECIFICATION.md +0 -0
  56. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/architecture/PLUGIN_SYSTEM.md +0 -0
  57. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/core/cache-modes.md +0 -0
  58. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/core/deep-crawling.md +0 -0
  59. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/core/graph-operations.md +0 -0
  60. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/core/simple-crawling.md +0 -0
  61. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/core/url-rules.md +0 -0
  62. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/extraction/custom-extractors.md +0 -0
  63. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/extraction/plugins.md +0 -0
  64. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/extraction/structured-data.md +0 -0
  65. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/getting-started/examples.md +0 -0
  66. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/getting-started/installation.md +0 -0
  67. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/docs/getting-started/quickstart.md +0 -0
  68. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/__init__.py +0 -0
  69. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/_run_recorder.py +0 -0
  70. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/agent.py +0 -0
  71. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/extraction_plugin.py +0 -0
  72. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/__init__.py +0 -0
  73. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/_response_normalizer.py +0 -0
  74. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/_schema_utils.py +0 -0
  75. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/anthropic_model.py +0 -0
  76. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/emergent_model.py +0 -0
  77. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/models/openai_model.py +0 -0
  78. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/query_debug.py +0 -0
  79. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/ai/views.py +0 -0
  80. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/__init__.py +0 -0
  81. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/_core.py +0 -0
  82. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/_distributed.py +0 -0
  83. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/_shared.py +0 -0
  84. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/_sitemap_distributed.py +0 -0
  85. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/cli.py +0 -0
  86. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/client/__init__.py +0 -0
  87. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/client/client.py +0 -0
  88. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/crawl_monitor.py +0 -0
  89. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/dashboard.py +0 -0
  90. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/history_manager.py +0 -0
  91. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/project_init.py +0 -0
  92. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/rest_api.py +0 -0
  93. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/stats_collector.py +0 -0
  94. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/webhooks.py +0 -0
  95. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/api/websocket_manager.py +0 -0
  96. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/__init__.py +0 -0
  97. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/bootstrap.py +0 -0
  98. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/context/__init__.py +0 -0
  99. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/context/dependency_registry.py +0 -0
  100. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/context/graph_context.py +0 -0
  101. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/context/merge_context.py +0 -0
  102. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/__init__.py +0 -0
  103. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/edge_dto.py +0 -0
  104. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/graph_dto.py +0 -0
  105. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/mappers/__init__.py +0 -0
  106. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/mappers/edge_mapper.py +0 -0
  107. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/mappers/graph_mapper.py +0 -0
  108. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/mappers/node_mapper.py +0 -0
  109. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/node_dto.py +0 -0
  110. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/dto/utils.py +0 -0
  111. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/__init__.py +0 -0
  112. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/application_container.py +0 -0
  113. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/driver_factory.py +0 -0
  114. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/__init__.py +0 -0
  115. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/base_exporter.py +0 -0
  116. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/csv_exporter.py +0 -0
  117. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/edge_exporter.py +0 -0
  118. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/excel_exporter.py +0 -0
  119. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/jsonl_exporter.py +0 -0
  120. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/node_exporter.py +0 -0
  121. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/parquet_exporter.py +0 -0
  122. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/exporters/sql_exporter.py +0 -0
  123. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/low_memory_graph_saver.py +0 -0
  124. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/services/storage_factory.py +0 -0
  125. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/__init__.py +0 -0
  126. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/__init__.py +0 -0
  127. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/adaptive_throttler.py +0 -0
  128. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/base_spider.py +0 -0
  129. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/celery_batch_spider.py +0 -0
  130. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/celery_spider.py +0 -0
  131. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/checkpoint.py +0 -0
  132. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/dead_letter_queue.py +0 -0
  133. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/domain_rate_limiter.py +0 -0
  134. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/filters/__init__.py +0 -0
  135. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/filters/base.py +0 -0
  136. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/filters/domain_filter.py +0 -0
  137. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/filters/domain_patterns.py +0 -0
  138. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/filters/path_filter.py +0 -0
  139. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/incremental_strategy.py +0 -0
  140. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/multiprocess_spider.py +0 -0
  141. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/parsers/__init__.py +0 -0
  142. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/parsers/base.py +0 -0
  143. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/parsers/html_parser.py +0 -0
  144. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/progress_tracker.py +0 -0
  145. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/scheduler.py +0 -0
  146. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/serialization_mixin.py +0 -0
  147. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/sitemap_parser.py +0 -0
  148. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/sitemap_processor.py +0 -0
  149. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/sitemap_spider.py +0 -0
  150. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/spider_lifecycle.py +0 -0
  151. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/crawling/spider_refactored.py +0 -0
  152. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/application/use_cases/graph_export.py +0 -0
  153. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/data/__init__.py +0 -0
  154. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/data/backends/__init__.py +0 -0
  155. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/data/backends/memory.py +0 -0
  156. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/data/interfaces.py +0 -0
  157. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/__init__.py +0 -0
  158. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/context/__init__.py +0 -0
  159. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/context/crawl_context.py +0 -0
  160. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/__init__.py +0 -0
  161. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/edge_analysis.py +0 -0
  162. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/graph_statistics.py +0 -0
  163. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/merge_strategies.py +0 -0
  164. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/node.py +0 -0
  165. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/registries.py +0 -0
  166. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/sitemap_node.py +0 -0
  167. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/entities/strategies.py +0 -0
  168. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/events/__init__.py +0 -0
  169. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/events/event_bus.py +0 -0
  170. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/events/events.py +0 -0
  171. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/__init__.py +0 -0
  172. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/adapter.py +0 -0
  173. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/control_channel.py +0 -0
  174. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/distributed_spider.py +0 -0
  175. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/event_bus.py +0 -0
  176. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/eviction_storage.py +0 -0
  177. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/exporters.py +0 -0
  178. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/filter.py +0 -0
  179. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/language_model.py +0 -0
  180. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/merge_context.py +0 -0
  181. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/node_interfaces.py +0 -0
  182. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/parser.py +0 -0
  183. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/plugin_manager.py +0 -0
  184. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/processor.py +0 -0
  185. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/scheduler.py +0 -0
  186. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/spider.py +0 -0
  187. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/stop_condition.py +0 -0
  188. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/storage.py +0 -0
  189. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/interfaces/unified_storage.py +0 -0
  190. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/value_objects/__init__.py +0 -0
  191. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/value_objects/configs.py +0 -0
  192. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/value_objects/domain_patterns.py +0 -0
  193. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/value_objects/lifecycle.py +0 -0
  194. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/value_objects/models.py +0 -0
  195. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/value_objects/settings.py +0 -0
  196. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/domain/value_objects/url_normalization.py +0 -0
  197. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/__init__.py +0 -0
  198. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/__init__.py +0 -0
  199. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/base.py +0 -0
  200. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/cache_middleware.py +0 -0
  201. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/chain.py +0 -0
  202. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/error_recovery_middleware.py +0 -0
  203. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/logging_middleware.py +0 -0
  204. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/proxy_health.py +0 -0
  205. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/proxy_middleware.py +0 -0
  206. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/proxy_models.py +0 -0
  207. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/proxy_selection.py +0 -0
  208. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/rate_limit_middleware.py +0 -0
  209. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/request_response_middleware.py +0 -0
  210. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/retry_middleware.py +0 -0
  211. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/robots_cache.py +0 -0
  212. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/robots_middleware.py +0 -0
  213. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/robots_validator.py +0 -0
  214. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/middleware/user_agent_middleware.py +0 -0
  215. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/__init__.py +0 -0
  216. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/base.py +0 -0
  217. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/builtin/__init__.py +0 -0
  218. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/builtin/stats_export_plugin.py +0 -0
  219. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/crawl_engine/__init__.py +0 -0
  220. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/crawl_engine/base.py +0 -0
  221. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/crawl_engine/priority_provider.py +0 -0
  222. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/crawl_engine/smart_crawl.py +0 -0
  223. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/crawl_engine/vector_crawl.py +0 -0
  224. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/__init__.py +0 -0
  225. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/anti_bot_detection.py +0 -0
  226. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/anti_bot_playwright.py +0 -0
  227. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/anti_bot_scripts.py +0 -0
  228. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/anti_bot_stealth.py +0 -0
  229. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/captcha/__init__.py +0 -0
  230. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/captcha/detector.py +0 -0
  231. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/captcha/models.py +0 -0
  232. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/captcha/plugin.py +0 -0
  233. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/engine/captcha/services.py +0 -0
  234. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/__init__.py +0 -0
  235. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/base.py +0 -0
  236. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/content_extractors/__init__.py +0 -0
  237. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/content_extractors/base.py +0 -0
  238. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/content_extractors/goose3_extractor.py +0 -0
  239. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/content_extractors/newspaper_extractor.py +0 -0
  240. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/content_extractors/plugin.py +0 -0
  241. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/content_extractors/readability_extractor.py +0 -0
  242. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/defaults.py +0 -0
  243. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/extractors/__init__.py +0 -0
  244. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/extractors/email_extractor.py +0 -0
  245. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/extractors/phone_extractor.py +0 -0
  246. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/extractors/price_extractor.py +0 -0
  247. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/links.py +0 -0
  248. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/markdown_plugin.py +0 -0
  249. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/metadata.py +0 -0
  250. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/smart_page_finder.py +0 -0
  251. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/__init__.py +0 -0
  252. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/constants.py +0 -0
  253. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/exceptions.py +0 -0
  254. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/extractor.py +0 -0
  255. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/options.py +0 -0
  256. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/__init__.py +0 -0
  257. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/base.py +0 -0
  258. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/jsonld.py +0 -0
  259. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/microdata.py +0 -0
  260. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/opengraph.py +0 -0
  261. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/rdfa.py +0 -0
  262. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/parsers/twitter.py +0 -0
  263. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/plugin.py +0 -0
  264. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/structured_data/result.py +0 -0
  265. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/text.py +0 -0
  266. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/url_normalizer.py +0 -0
  267. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/vectorization/__init__.py +0 -0
  268. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/vectorization/batch_vectorizer.py +0 -0
  269. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/vectorization/realtime_vectorizer.py +0 -0
  270. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/extensions/plugins/node/vectorization/utils.py +0 -0
  271. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/__init__.py +0 -0
  272. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/adapters/__init__.py +0 -0
  273. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/adapters/base.py +0 -0
  274. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/adapters/beautifulsoup_adapter.py +0 -0
  275. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/adapters/lxml_adapter.py +0 -0
  276. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/adapters/scrapy_adapter.py +0 -0
  277. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/adapters/selectolax_adapter.py +0 -0
  278. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/__init__.py +0 -0
  279. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/celery_app.py +0 -0
  280. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/celery_batch.py +0 -0
  281. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/celery_job_task.py +0 -0
  282. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/celery_unified.py +0 -0
  283. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/config.py +0 -0
  284. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/easy_crawler.py +0 -0
  285. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/messaging/worker_api.py +0 -0
  286. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/__init__.py +0 -0
  287. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/auto_storage.py +0 -0
  288. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/base.py +0 -0
  289. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/eviction.py +0 -0
  290. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/graph_repository.py +0 -0
  291. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/json/__init__.py +0 -0
  292. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/json_storage.py +0 -0
  293. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/lmdb_eviction_storage.py +0 -0
  294. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/memory/__init__.py +0 -0
  295. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/memory_storage.py +0 -0
  296. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/mongodb_storage.py +0 -0
  297. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/naming_strategy.py +0 -0
  298. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/postgresql_storage.py +0 -0
  299. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/protocols/__init__.py +0 -0
  300. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/repository.py +0 -0
  301. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/sqlite/__init__.py +0 -0
  302. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/sqlite_eviction_storage.py +0 -0
  303. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/__init__.py +0 -0
  304. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/file_job_storage.py +0 -0
  305. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/file_queue_storage.py +0 -0
  306. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/memory_job_storage.py +0 -0
  307. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/memory_queue_storage.py +0 -0
  308. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/postgresql_job_storage.py +0 -0
  309. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/postgresql_queue_storage.py +0 -0
  310. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/persistence/unified/unified_storage.py +0 -0
  311. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/__init__.py +0 -0
  312. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/__init__.py +0 -0
  313. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/config.py +0 -0
  314. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/context.py +0 -0
  315. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/driver_cloudscraper.py +0 -0
  316. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/driver_curl_cffi.py +0 -0
  317. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/driver_httpx.py +0 -0
  318. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/driver_v4.py +0 -0
  319. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/__init__.py +0 -0
  320. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle.py +0 -0
  321. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle_v2.py +0 -0
  322. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/headers.py +0 -0
  323. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/http_cache.py +0 -0
  324. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/rate_limiter.py +0 -0
  325. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/retry.py +0 -0
  326. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/plugins/stealth_driver.py +0 -0
  327. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/async_http/stages.py +0 -0
  328. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/base_plugin.py +0 -0
  329. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/botasaurus_driver.py +0 -0
  330. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/browser/__init__.py +0 -0
  331. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/compression/__init__.py +0 -0
  332. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/compression/strategy.py +0 -0
  333. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/connection_pool.py +0 -0
  334. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/context.py +0 -0
  335. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/core/__init__.py +0 -0
  336. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/core/base_async.py +0 -0
  337. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/core/base_sync.py +0 -0
  338. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/core/mixins.py +0 -0
  339. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/factory.py +0 -0
  340. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/http/__init__.py +0 -0
  341. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/__init__.py +0 -0
  342. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/config.py +0 -0
  343. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/context.py +0 -0
  344. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/__init__.py +0 -0
  345. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_detector.py +0 -0
  346. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_solver.py +0 -0
  347. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/cloudflare.py +0 -0
  348. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/compatibility.py +0 -0
  349. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/enhanced_cloudflare.py +0 -0
  350. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/enhanced_stealth.py +0 -0
  351. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/form_filler.py +0 -0
  352. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/human_behavior.py +0 -0
  353. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/ram_adaptive.py +0 -0
  354. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/screenshot.py +0 -0
  355. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/stealth.py +0 -0
  356. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/plugins/user_profile.py +0 -0
  357. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/pooled_driver.py +0 -0
  358. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/playwright/stages.py +0 -0
  359. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/plugin_manager.py +0 -0
  360. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/protocols.py +0 -0
  361. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/session_adapters.py +0 -0
  362. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/session_manager.py +0 -0
  363. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/session_protocol.py +0 -0
  364. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/sync/__init__.py +0 -0
  365. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/sync/cloudscraper_driver.py +0 -0
  366. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/sync/requests_driver.py +0 -0
  367. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/tls_client.py +0 -0
  368. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/undetected/__init__.py +0 -0
  369. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/undetected/async_driver.py +0 -0
  370. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/infrastructure/transport/undetected/driver.py +0 -0
  371. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/native/README.md +0 -0
  372. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/native/__init__.py +0 -0
  373. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/native/bloom_filter.pyx +0 -0
  374. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/native/html_parser.pyx +0 -0
  375. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/native/setup.py +0 -0
  376. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/native/url_utils.pyx +0 -0
  377. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/__init__.py +0 -0
  378. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/decorators/__init__.py +0 -0
  379. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/decorators/cache.py +0 -0
  380. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/decorators/log.py +0 -0
  381. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/decorators/retry.py +0 -0
  382. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/decorators/timing.py +0 -0
  383. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/error_tracing.py +0 -0
  384. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/extensions/__init__.py +0 -0
  385. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/extensions/memory_guard.py +0 -0
  386. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/extensions/state_manager.py +0 -0
  387. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/__init__.py +0 -0
  388. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/base.py +0 -0
  389. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/base_metrics_listener.py +0 -0
  390. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/crawl_listener.py +0 -0
  391. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/dlq_listener.py +0 -0
  392. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/error_listener.py +0 -0
  393. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/logging_listener.py +0 -0
  394. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/metrics_listener.py +0 -0
  395. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/node_listener.py +0 -0
  396. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/plugin_listener.py +0 -0
  397. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/storage_listener.py +0 -0
  398. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/listeners/url_listener.py +0 -0
  399. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/metrics/__init__.py +0 -0
  400. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/metrics/memory_profiler.py +0 -0
  401. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/metrics/metrics_collector.py +0 -0
  402. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/metrics/prometheus_metrics.py +0 -0
  403. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/metrics_core.py +0 -0
  404. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/structured_logging.py +0 -0
  405. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/observability/tracing.py +0 -0
  406. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/optimizations/__init__.py +0 -0
  407. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/optimizations/generators.py +0 -0
  408. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/optimizations/memory_slots.py +0 -0
  409. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/optimizations/simhash_numba.py +0 -0
  410. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/py.typed +0 -0
  411. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/__init__.py +0 -0
  412. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/constants.py +0 -0
  413. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/dto/__init__.py +0 -0
  414. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/dto/edge_dto.py +0 -0
  415. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/dto/graph_dto.py +0 -0
  416. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/dto/node_dto.py +0 -0
  417. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/error_handling/__init__.py +0 -0
  418. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/error_handling/error_handler.py +0 -0
  419. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/exceptions.py +0 -0
  420. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/platform/__init__.py +0 -0
  421. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/platform/adaptive_resolver.py +0 -0
  422. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/platform/config.py +0 -0
  423. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/platform/detector.py +0 -0
  424. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/platform/diagnostics.py +0 -0
  425. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/platform/metrics.py +0 -0
  426. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/security/__init__.py +0 -0
  427. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/security/url_sanitizer.py +0 -0
  428. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/security/url_validator.py +0 -0
  429. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/async_compat.py +0 -0
  430. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/bloom_filter.py +0 -0
  431. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/captcha/__init__.py +0 -0
  432. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/captcha/base.py +0 -0
  433. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/captcha/manager.py +0 -0
  434. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/captcha_bypass.py +0 -0
  435. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/celery_config.py +0 -0
  436. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/celery_helpers.py +0 -0
  437. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/distributed_rate_limiter.py +0 -0
  438. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/distributed_rate_limiter_backends.py +0 -0
  439. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/dns_cache.py +0 -0
  440. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/enterprise_ua_generator.py +0 -0
  441. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/event_publisher_mixin.py +0 -0
  442. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/fast_json.py +0 -0
  443. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/fingerprint.py +0 -0
  444. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/fingerprint_data.py +0 -0
  445. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/fingerprint_generators.py +0 -0
  446. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/fingerprint_profile.py +0 -0
  447. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/fingerprint_stealth.py +0 -0
  448. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/html_utils.py +0 -0
  449. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/id_generator.py +0 -0
  450. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/__init__.py +0 -0
  451. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/adapters/__init__.py +0 -0
  452. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/adapters/base.py +0 -0
  453. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/adapters/bs4_adapter.py +0 -0
  454. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/adapters/selectolax_adapter.py +0 -0
  455. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/ast.py +0 -0
  456. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/converter.py +0 -0
  457. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/generator.py +0 -0
  458. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/options.py +0 -0
  459. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/renderer.py +0 -0
  460. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/markdown/result.py +0 -0
  461. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/memory_optimizer.py +0 -0
  462. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/proxy_manager.py +0 -0
  463. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/rate_limiter.py +0 -0
  464. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/regex_validator.py +0 -0
  465. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/url_patterns.py +0 -0
  466. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/url_utils.py +0 -0
  467. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/user_agent_rotator.py +0 -0
  468. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/validation_helpers.py +0 -0
  469. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/visualization.py +0 -0
  470. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler/shared/utils/visualization_core.py +0 -0
  471. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler.egg-info/dependency_links.txt +0 -0
  472. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler.egg-info/entry_points.txt +0 -0
  473. {graph_crawler-4.1.2 → graph_crawler-4.1.4}/graph_crawler.egg-info/top_level.txt +0 -0
  474. {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.2
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 = 0.5,
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
- Синхронний краулінг веб-сайту - **простий як requests**.
116
+ Синхронний краулінг веб-сайту. Працює як `requests` — функція приймає URL і повертає
117
+ результат, не вимагаючи `async/await` або контекстних менеджерів.
112
118
 
113
- 🆕 **NEW in v3.2.0:** Підтримка множинних точок входу (`seed_urls`) та продовження існуючого краулінгу (`base_graph`)!
119
+ **Призначення:** одноразовий запуск краулінгу з простого Python-скрипта, CLI або
120
+ notebook. Внутрішньо викликає `async_crawl()` через `asyncio.run` або через
121
+ ThreadPoolExecutor (якщо вже працює event-loop, наприклад у Jupyter).
114
122
 
115
123
  **Параметри:**
116
124
 
117
- | Параметр | Тип | Default | Опис |
118
- | ----------------- | ------------ | -------- | ------------------------------------------------------- |
119
- | `url` | str | None | URL для початку краулінгу (якщо seed_urls не передано) |
120
- | `seed_urls` 🆕 | list[str] | None | Список URL для початку краулінгу (множинні точки входу) |
121
- | `base_graph` 🆕 | Graph | None | Існуючий граф для продовження краулінгу (incremental) |
122
- | `max_depth` | int | 3 | Максимальна глибина обходу |
123
- | `max_pages` | int | 100 | Максимальна кількість сторінок |
124
- | `same_domain` | bool | True | Сканувати тільки поточний домен |
125
- | `timeout` | int | None | Максимальний час в секундах |
126
- | `request_delay` | float | 0.5 | Затримка між запитами |
127
- | `follow_links` 🆕 | bool | True | Переходити за посиланнями (False = тільки seed URLs) |
128
- | `driver` | str/IDriver | "http" | "http", "async", "playwright", "stealth" |
129
- | `driver_config` | dict | None | Конфігурація драйвера |
130
- | `storage` | str/IStorage | "memory" | "memory", "json", "sqlite", "postgresql" |
131
- | `storage_config` | dict | None | Конфігурація storage |
132
- | `plugins` | list | default | Список плагінів |
133
- | `node_class` | type | Node | Кастомний клас Node |
134
- | `edge_class` | type | Edge | Кастомний клас Edge |
135
- | `url_rules` | list | None | Список URLRule |
136
- | `edge_rules` 🆕 | list | None | Список EdgeRule для контролю edges |
137
- | `edge_strategy` | str | "all" | "all", "new_only", "max_in_degree", "deeper_only" |
138
- | `on_progress` | Callable | None | Callback для прогресу |
139
- | `on_node_scanned` | Callable | None | Callback після сканування ноди |
140
- | `on_error` | Callable | None | Callback для помилок |
141
- | `on_completed` | Callable | None | Callback після завершення |
142
- | `wrapper` | dict | None | Конфіг distributed crawling |
143
-
144
- **Повертає:** `Graph`
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
- # ... ті самі параметри що і crawl() (без wrapper)
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
- Async версія crawl() для максимальної продуктивності.
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 - парсить robots.txt → знаходить sitemap → обробляє всі URL.
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("https://example.com", max_urls=1000)
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)