graph-crawler 5.3.3__tar.gz → 5.3.5__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 (481) hide show
  1. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/CHANGELOG.md +113 -0
  2. {graph_crawler-5.3.3/graph_crawler.egg-info → graph_crawler-5.3.5}/PKG-INFO +56 -65
  3. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/README.md +55 -64
  4. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/_core.py +2 -0
  5. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/async_.py +2 -0
  6. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/client/client.py +3 -0
  7. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/sync.py +4 -0
  8. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/dto/mappers/node_mapper.py +1 -0
  9. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/crawl_coordinator.py +22 -3
  10. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/filters/domain_filter.py +31 -0
  11. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/node_scanner.py +5 -5
  12. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/sitemap_parser.py +84 -12
  13. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/sitemap_processor.py +3 -0
  14. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/sitemap_spider.py +1 -0
  15. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/spider.py +75 -36
  16. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/entities/graph.py +129 -7
  17. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/entities/node.py +163 -11
  18. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/entities/sitemap_node.py +3 -0
  19. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/__init__.py +2 -0
  20. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/control_channel.py +27 -17
  21. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/value_objects/configs.py +3 -0
  22. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/sqlite_eviction_storage.py +1 -0
  23. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/escalation.py +128 -19
  24. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/isolated_executor.py +7 -0
  25. {graph_crawler-5.3.3 → graph_crawler-5.3.5/graph_crawler.egg-info}/PKG-INFO +56 -65
  26. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/pyproject.toml +1 -1
  27. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/LICENSE +0 -0
  28. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/MANIFEST.in +0 -0
  29. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/__init__.py +0 -0
  30. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/__version__.py +0 -0
  31. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/ai/__init__.py +0 -0
  32. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/__init__.py +0 -0
  33. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/_distributed.py +0 -0
  34. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/_shared.py +0 -0
  35. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/_sitemap_distributed.py +0 -0
  36. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/cli.py +0 -0
  37. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/client/__init__.py +0 -0
  38. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/history_manager.py +0 -0
  39. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/project_init.py +0 -0
  40. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/server/__init__.py +0 -0
  41. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/server/crawl_monitor.py +0 -0
  42. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/server/dashboard.py +0 -0
  43. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/server/rest_api.py +0 -0
  44. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/server/webhooks.py +0 -0
  45. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/server/websocket_manager.py +0 -0
  46. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/api/stats_collector.py +0 -0
  47. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/__init__.py +0 -0
  48. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/bootstrap.py +0 -0
  49. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/context/__init__.py +0 -0
  50. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/context/dependency_registry.py +0 -0
  51. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/context/graph_context.py +0 -0
  52. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/context/merge_context.py +0 -0
  53. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/dto/__init__.py +0 -0
  54. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/dto/edge_dto.py +0 -0
  55. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/dto/graph_dto.py +0 -0
  56. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/dto/mappers/__init__.py +0 -0
  57. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/dto/mappers/edge_mapper.py +0 -0
  58. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/dto/mappers/graph_mapper.py +0 -0
  59. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/dto/mappers/node_data_codec.py +0 -0
  60. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/dto/node_dto.py +0 -0
  61. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/dto/utils.py +0 -0
  62. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/__init__.py +0 -0
  63. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/application_container.py +0 -0
  64. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/driver_factory.py +0 -0
  65. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/exporters/__init__.py +0 -0
  66. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/exporters/base_exporter.py +0 -0
  67. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/exporters/csv_exporter.py +0 -0
  68. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/exporters/edge_exporter.py +0 -0
  69. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/exporters/excel_exporter.py +0 -0
  70. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/exporters/jsonl_exporter.py +0 -0
  71. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/exporters/node_exporter.py +0 -0
  72. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/exporters/parquet_exporter.py +0 -0
  73. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/exporters/sql_exporter.py +0 -0
  74. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/finalize.py +0 -0
  75. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/low_memory_graph_saver.py +0 -0
  76. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/resource_budget.py +0 -0
  77. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/scheduling/__init__.py +0 -0
  78. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/scheduling/rule_based_scheduler.py +0 -0
  79. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/services/storage_factory.py +0 -0
  80. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/__init__.py +0 -0
  81. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/__init__.py +0 -0
  82. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/adaptive_throttler.py +0 -0
  83. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/async_bridge.py +0 -0
  84. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/base_spider.py +0 -0
  85. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/celery_batch_spider.py +0 -0
  86. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/celery_spider.py +0 -0
  87. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/checkpoint.py +0 -0
  88. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/dead_letter_queue.py +0 -0
  89. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/domain_rate_limiter.py +0 -0
  90. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/filters/__init__.py +0 -0
  91. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/filters/base.py +0 -0
  92. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/filters/domain_patterns.py +0 -0
  93. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/filters/path_filter.py +0 -0
  94. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/incremental_strategy.py +0 -0
  95. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/link_processor.py +0 -0
  96. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/multiprocess_spider.py +0 -0
  97. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/parsers/__init__.py +0 -0
  98. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/parsers/base.py +0 -0
  99. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/parsers/html_parser.py +0 -0
  100. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/progress_tracker.py +0 -0
  101. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/scheduler.py +0 -0
  102. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/serialization_mixin.py +0 -0
  103. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/spider_lifecycle.py +0 -0
  104. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/crawling/worker_pool.py +0 -0
  105. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/graph_export.py +0 -0
  106. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/graph_merge/__init__.py +0 -0
  107. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/graph_merge/graph_operations.py +0 -0
  108. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/application/use_cases/graph_merge/merge_strategies.py +0 -0
  109. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/data/__init__.py +0 -0
  110. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/data/backends/__init__.py +0 -0
  111. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/data/backends/memory.py +0 -0
  112. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/data/backends/sqlite.py +0 -0
  113. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/data/interfaces.py +0 -0
  114. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/__init__.py +0 -0
  115. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/context/__init__.py +0 -0
  116. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/context/crawl_context.py +0 -0
  117. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/entities/__init__.py +0 -0
  118. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/entities/edge.py +0 -0
  119. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/entities/edge_analysis.py +0 -0
  120. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/entities/graph_operations.py +0 -0
  121. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/entities/graph_statistics.py +0 -0
  122. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/entities/merge_strategies.py +0 -0
  123. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/entities/redirect_mode.py +0 -0
  124. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/entities/registries.py +0 -0
  125. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/entities/strategies.py +0 -0
  126. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/events/__init__.py +0 -0
  127. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/events/base.py +0 -0
  128. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/events/crawl.py +0 -0
  129. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/events/edge.py +0 -0
  130. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/events/event_bus.py +0 -0
  131. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/events/events.py +0 -0
  132. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/events/fetch.py +0 -0
  133. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/events/idempotent_subscriber.py +0 -0
  134. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/events/legacy_type_map.py +0 -0
  135. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/events/llm.py +0 -0
  136. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/events/node.py +0 -0
  137. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/events/plugin.py +0 -0
  138. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/events/scheduler.py +0 -0
  139. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/events/storage.py +0 -0
  140. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/adapter.py +0 -0
  141. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/crawl_context.py +0 -0
  142. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/distributed_spider.py +0 -0
  143. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/driver.py +0 -0
  144. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/event_bus.py +0 -0
  145. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/exporters.py +0 -0
  146. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/filter.py +0 -0
  147. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/merge_context.py +0 -0
  148. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/node_interfaces.py +0 -0
  149. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/parser.py +0 -0
  150. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/plugin_manager.py +0 -0
  151. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/processor.py +0 -0
  152. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/scanner.py +0 -0
  153. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/scheduler.py +0 -0
  154. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/scheduling/__init__.py +0 -0
  155. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/scheduling/frontier.py +0 -0
  156. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/scheduling/scheduler.py +0 -0
  157. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/spider.py +0 -0
  158. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/stop_condition.py +0 -0
  159. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/storage/__init__.py +0 -0
  160. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/storage/base.py +0 -0
  161. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/storage/eviction.py +0 -0
  162. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/interfaces/storage/queue.py +0 -0
  163. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/__init__.py +0 -0
  164. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/adjacency_maintenance.py +0 -0
  165. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/cycle_detection.py +0 -0
  166. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/degree_ranking.py +0 -0
  167. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/depth_context.py +0 -0
  168. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/display_title.py +0 -0
  169. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/duplicate_detection.py +0 -0
  170. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/duplicate_selector.py +0 -0
  171. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/duplicate_stats.py +0 -0
  172. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/edge_filter.py +0 -0
  173. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/edge_statistics.py +0 -0
  174. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/eviction_policy.py +0 -0
  175. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/graph_query_cascade.py +0 -0
  176. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/graph_statistics.py +0 -0
  177. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/graph_traversal.py +0 -0
  178. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/language_versions.py +0 -0
  179. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/lsh_index.py +0 -0
  180. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/minimap_builder.py +0 -0
  181. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/node_content_resolver.py +0 -0
  182. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/node_view_builder.py +0 -0
  183. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/pagination_guards.py +0 -0
  184. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/predicate_ops.py +0 -0
  185. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/schema_filter.py +0 -0
  186. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/search_haystack.py +0 -0
  187. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/simhash_clustering.py +0 -0
  188. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/simhash_fallback.py +0 -0
  189. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/similarity_ranking.py +0 -0
  190. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/text_search.py +0 -0
  191. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/url_clustering.py +0 -0
  192. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/url_deduplication.py +0 -0
  193. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/url_normalizer.py +0 -0
  194. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/url_rule_matcher.py +0 -0
  195. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/services/url_tree_builder.py +0 -0
  196. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/value_objects/__init__.py +0 -0
  197. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/value_objects/domain_patterns.py +0 -0
  198. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/value_objects/fetch_options.py +0 -0
  199. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/value_objects/html_document.py +0 -0
  200. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/value_objects/lifecycle.py +0 -0
  201. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/value_objects/models.py +0 -0
  202. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/value_objects/page_actions.py +0 -0
  203. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/value_objects/redirect_hop.py +0 -0
  204. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/value_objects/scheduling/__init__.py +0 -0
  205. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/value_objects/scheduling/crawl_request.py +0 -0
  206. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/value_objects/scheduling/retry_policy.py +0 -0
  207. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/value_objects/settings.py +0 -0
  208. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/value_objects/url_normalization.py +0 -0
  209. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/domain/value_objects/views.py +0 -0
  210. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/__init__.py +0 -0
  211. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/__init__.py +0 -0
  212. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/base.py +0 -0
  213. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/cache_middleware.py +0 -0
  214. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/chain.py +0 -0
  215. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/error_recovery_middleware.py +0 -0
  216. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/logging_middleware.py +0 -0
  217. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/proxy_health.py +0 -0
  218. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/proxy_middleware.py +0 -0
  219. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/proxy_models.py +0 -0
  220. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/proxy_selection.py +0 -0
  221. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/rate_limit_middleware.py +0 -0
  222. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/request_response_middleware.py +0 -0
  223. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/retry_middleware.py +0 -0
  224. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/robots_cache.py +0 -0
  225. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/robots_middleware.py +0 -0
  226. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/robots_validator.py +0 -0
  227. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/middleware/user_agent_middleware.py +0 -0
  228. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/__init__.py +0 -0
  229. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/builtin/__init__.py +0 -0
  230. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/builtin/stats_export_plugin.py +0 -0
  231. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/crawl_engine/__init__.py +0 -0
  232. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/crawl_engine/base.py +0 -0
  233. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/crawl_engine/priority_provider.py +0 -0
  234. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/crawl_engine/smart_crawl.py +0 -0
  235. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/engine/__init__.py +0 -0
  236. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/engine/anti_bot_detection.py +0 -0
  237. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/engine/anti_bot_playwright.py +0 -0
  238. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/engine/anti_bot_scripts.py +0 -0
  239. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/engine/anti_bot_stealth.py +0 -0
  240. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/engine/base.py +0 -0
  241. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/engine/captcha/__init__.py +0 -0
  242. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/engine/captcha/detector.py +0 -0
  243. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/engine/captcha/models.py +0 -0
  244. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/engine/captcha/plugin.py +0 -0
  245. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/engine/captcha/services.py +0 -0
  246. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/__init__.py +0 -0
  247. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/base.py +0 -0
  248. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/content_extractors/__init__.py +0 -0
  249. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/content_extractors/base.py +0 -0
  250. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/content_extractors/goose3_extractor.py +0 -0
  251. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/content_extractors/newspaper_extractor.py +0 -0
  252. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/content_extractors/plugin.py +0 -0
  253. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/content_extractors/readability_extractor.py +0 -0
  254. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/defaults.py +0 -0
  255. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/extractors/__init__.py +0 -0
  256. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/extractors/email_extractor.py +0 -0
  257. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/extractors/phone_extractor.py +0 -0
  258. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/extractors/price_extractor.py +0 -0
  259. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/links.py +0 -0
  260. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/markdown_plugin.py +0 -0
  261. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/metadata.py +0 -0
  262. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/smart_page_finder.py +0 -0
  263. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/structured_data/__init__.py +0 -0
  264. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/structured_data/constants.py +0 -0
  265. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/structured_data/exceptions.py +0 -0
  266. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/structured_data/extractor.py +0 -0
  267. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/structured_data/options.py +0 -0
  268. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/structured_data/parsers/__init__.py +0 -0
  269. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/structured_data/parsers/base.py +0 -0
  270. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/structured_data/parsers/jsonld.py +0 -0
  271. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/structured_data/parsers/microdata.py +0 -0
  272. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/structured_data/parsers/opengraph.py +0 -0
  273. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/structured_data/parsers/rdfa.py +0 -0
  274. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/structured_data/parsers/twitter.py +0 -0
  275. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/structured_data/plugin.py +0 -0
  276. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/structured_data/result.py +0 -0
  277. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/text.py +0 -0
  278. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/node/url_normalizer.py +0 -0
  279. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/extensions/plugins/registry.py +0 -0
  280. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/__init__.py +0 -0
  281. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/adapters/__init__.py +0 -0
  282. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/adapters/base.py +0 -0
  283. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/adapters/selectolax_adapter.py +0 -0
  284. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/messaging/__init__.py +0 -0
  285. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/messaging/celery_app.py +0 -0
  286. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/messaging/celery_batch.py +0 -0
  287. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/messaging/celery_job_task.py +0 -0
  288. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/messaging/celery_unified.py +0 -0
  289. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/messaging/config.py +0 -0
  290. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/messaging/easy_crawler.py +0 -0
  291. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/messaging/worker_api.py +0 -0
  292. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/__init__.py +0 -0
  293. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/auto_storage.py +0 -0
  294. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/base.py +0 -0
  295. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/eviction.py +0 -0
  296. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/graph_repository.py +0 -0
  297. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/json/__init__.py +0 -0
  298. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/json_storage.py +0 -0
  299. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/lmdb_eviction_storage.py +0 -0
  300. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/memory/__init__.py +0 -0
  301. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/memory_dedup_store.py +0 -0
  302. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/memory_frontier.py +0 -0
  303. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/memory_politeness_store.py +0 -0
  304. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/memory_storage.py +0 -0
  305. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/mongodb_storage.py +0 -0
  306. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/naming_strategy.py +0 -0
  307. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/postgresql_storage.py +0 -0
  308. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/protocols/__init__.py +0 -0
  309. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/redis_dedup_store.py +0 -0
  310. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/redis_politeness_store.py +0 -0
  311. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/repository.py +0 -0
  312. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/sqlite/__init__.py +0 -0
  313. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/sqlite_storage.py +0 -0
  314. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/unified/__init__.py +0 -0
  315. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/unified/file_job_storage.py +0 -0
  316. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/unified/file_queue_storage.py +0 -0
  317. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/unified/memory_job_storage.py +0 -0
  318. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/unified/memory_queue_storage.py +0 -0
  319. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/unified/postgresql_job_storage.py +0 -0
  320. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/unified/postgresql_queue_storage.py +0 -0
  321. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/persistence/unified/unified_storage.py +0 -0
  322. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/__init__.py +0 -0
  323. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/async_http/__init__.py +0 -0
  324. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/async_http/config.py +0 -0
  325. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/async_http/context.py +0 -0
  326. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/async_http/driver.py +0 -0
  327. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/async_http/driver_curl_cffi.py +0 -0
  328. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/async_http/plugins/__init__.py +0 -0
  329. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle.py +0 -0
  330. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle_adaptive.py +0 -0
  331. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/async_http/plugins/headers.py +0 -0
  332. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/async_http/plugins/http_cache.py +0 -0
  333. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/async_http/plugins/rate_limiter.py +0 -0
  334. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/async_http/plugins/retry.py +0 -0
  335. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/async_http/stages.py +0 -0
  336. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/base.py +0 -0
  337. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/base_plugin.py +0 -0
  338. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/compression/__init__.py +0 -0
  339. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/compression/strategy.py +0 -0
  340. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/connection_pool.py +0 -0
  341. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/context.py +0 -0
  342. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/core/__init__.py +0 -0
  343. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/core/base_async.py +0 -0
  344. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/core/body_decoder.py +0 -0
  345. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/core/mixins.py +0 -0
  346. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/core/redirect_utils.py +0 -0
  347. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/factory.py +0 -0
  348. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/__init__.py +0 -0
  349. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/adaptive_semaphore.py +0 -0
  350. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/batch_scheduler.py +0 -0
  351. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/browser_factory.py +0 -0
  352. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/browser_profile.py +0 -0
  353. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/config.py +0 -0
  354. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/context.py +0 -0
  355. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/driver.py +0 -0
  356. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/engine.py +0 -0
  357. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/fetch_metrics.py +0 -0
  358. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/net/__init__.py +0 -0
  359. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/net/geoip.py +0 -0
  360. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/net/proxy_pool.py +0 -0
  361. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/net/rate_limiter.py +0 -0
  362. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/page_actions.py +0 -0
  363. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/page_fetch.py +0 -0
  364. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/plugins/__init__.py +0 -0
  365. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/plugins/_network_record.py +0 -0
  366. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_detector.py +0 -0
  367. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_solver.py +0 -0
  368. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/plugins/cookie_consent.py +0 -0
  369. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/plugins/form_filler.py +0 -0
  370. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/plugins/human_bezier.py +0 -0
  371. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/plugins/network_log.py +0 -0
  372. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/plugins/ram_adaptive.py +0 -0
  373. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/plugins/screenshot.py +0 -0
  374. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/plugins/user_profile.py +0 -0
  375. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/pool_lifecycle.py +0 -0
  376. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/pooled_driver.py +0 -0
  377. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/stages.py +0 -0
  378. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/playwright/stealth_hints.py +0 -0
  379. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/plugin_manager.py +0 -0
  380. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/protocols.py +0 -0
  381. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/undetected/__init__.py +0 -0
  382. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/infrastructure/transport/undetected/async_driver.py +0 -0
  383. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/native/__init__.py +0 -0
  384. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/native/bloom_filter.pyx +0 -0
  385. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/native/html_parser.pyx +0 -0
  386. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/native/setup.py +0 -0
  387. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/native/url_utils.pyx +0 -0
  388. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/__init__.py +0 -0
  389. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/decorators/__init__.py +0 -0
  390. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/decorators/cache.py +0 -0
  391. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/decorators/log.py +0 -0
  392. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/decorators/retry.py +0 -0
  393. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/decorators/timing.py +0 -0
  394. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/error_tracing.py +0 -0
  395. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/extensions/__init__.py +0 -0
  396. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/extensions/memory_guard.py +0 -0
  397. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/extensions/state_manager.py +0 -0
  398. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/listeners/__init__.py +0 -0
  399. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/listeners/base.py +0 -0
  400. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/listeners/base_metrics_listener.py +0 -0
  401. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/listeners/crawl_listener.py +0 -0
  402. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/listeners/dlq_listener.py +0 -0
  403. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/listeners/error_listener.py +0 -0
  404. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/listeners/logging_listener.py +0 -0
  405. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/listeners/metrics_listener.py +0 -0
  406. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/listeners/node_listener.py +0 -0
  407. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/listeners/plugin_listener.py +0 -0
  408. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/listeners/storage_listener.py +0 -0
  409. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/listeners/url_listener.py +0 -0
  410. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/metrics/__init__.py +0 -0
  411. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/metrics/memory_profiler.py +0 -0
  412. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/metrics/metrics_collector.py +0 -0
  413. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/metrics/prometheus_metrics.py +0 -0
  414. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/metrics_core.py +0 -0
  415. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/structured_logging.py +0 -0
  416. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/observability/tracing.py +0 -0
  417. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/optimizations/__init__.py +0 -0
  418. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/optimizations/generators.py +0 -0
  419. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/optimizations/memory_slots.py +0 -0
  420. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/optimizations/simhash_numba.py +0 -0
  421. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/py.typed +0 -0
  422. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/__init__.py +0 -0
  423. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/constants.py +0 -0
  424. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/dto/__init__.py +0 -0
  425. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/dto/edge_dto.py +0 -0
  426. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/dto/graph_dto.py +0 -0
  427. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/dto/node_dto.py +0 -0
  428. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/error_handling/__init__.py +0 -0
  429. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/error_handling/error_handler.py +0 -0
  430. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/exceptions.py +0 -0
  431. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/platform/__init__.py +0 -0
  432. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/platform/adaptive_resolver.py +0 -0
  433. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/platform/config.py +0 -0
  434. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/platform/detector.py +0 -0
  435. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/platform/diagnostics.py +0 -0
  436. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/platform/metrics.py +0 -0
  437. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/security/__init__.py +0 -0
  438. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/security/url_sanitizer.py +0 -0
  439. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/security/url_validator.py +0 -0
  440. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/__init__.py +0 -0
  441. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/async_compat.py +0 -0
  442. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/bloom_filter.py +0 -0
  443. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/captcha/__init__.py +0 -0
  444. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/captcha/base.py +0 -0
  445. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/captcha/manager.py +0 -0
  446. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/celery_config.py +0 -0
  447. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/celery_helpers.py +0 -0
  448. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/cgroup_limits.py +0 -0
  449. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/content_decoder.py +0 -0
  450. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/distributed_rate_limiter.py +0 -0
  451. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/distributed_rate_limiter_backends.py +0 -0
  452. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/dns_cache.py +0 -0
  453. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/enterprise_ua_generator.py +0 -0
  454. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/event_publisher_mixin.py +0 -0
  455. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/fast_json.py +0 -0
  456. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/fingerprint.py +0 -0
  457. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/fingerprint_data.py +0 -0
  458. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/fingerprint_generators.py +0 -0
  459. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/fingerprint_profile.py +0 -0
  460. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/fingerprint_stealth.py +0 -0
  461. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/html_utils.py +0 -0
  462. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/id_generator.py +0 -0
  463. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/memory_optimizer.py +0 -0
  464. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/rate_limiter.py +0 -0
  465. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/regex_validator.py +0 -0
  466. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/shadow_dom.py +0 -0
  467. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/text_utils.py +0 -0
  468. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/url_patterns.py +0 -0
  469. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/url_utils.py +0 -0
  470. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/user_agent_rotator.py +0 -0
  471. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/validation_helpers.py +0 -0
  472. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/visualization.py +0 -0
  473. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/shared/utils/visualization_core.py +0 -0
  474. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/tools/__init__.py +0 -0
  475. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler/tools/db_cleanup.py +0 -0
  476. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler.egg-info/SOURCES.txt +0 -0
  477. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler.egg-info/dependency_links.txt +0 -0
  478. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler.egg-info/entry_points.txt +0 -0
  479. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler.egg-info/requires.txt +0 -0
  480. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/graph_crawler.egg-info/top_level.txt +0 -0
  481. {graph_crawler-5.3.3 → graph_crawler-5.3.5}/setup.cfg +0 -0
@@ -1,5 +1,118 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 5.3.5 — 2026-06
6
+
7
+ ### Fixed
8
+
9
+ - **Dirty `Sitemap:` value in robots.txt no longer loses the sitemap (P19).** `SitemapParser._parse_robots_txt`
10
+ took the whole directive value verbatim (`line[8:].strip()`), so `Sitemap: sitemap URL https://site.com/sitemap_index.xml`
11
+ was glued into a broken address and the sitemap was lost. It now extracts the first `https?://\S+` from the value;
12
+ `urljoin` is applied only when the value is a relative path starting with `/`. New `SitemapParser._clean_sitemap_value`.
13
+ - **A 404/410 is never a bot block (P19 side-finding).** `escalation._classify` returns `BotBlockStatus.NONE` for
14
+ status 404/410 right after the authoritative challenge headers. A dead sitemap candidate (or any not-found page) is
15
+ no longer classified as `captcha`/`challenge` and dragged through every tier (http→tls→browser). A real challenge
16
+ uses 403/503, not 404, so the authoritative header check still wins. Invariant: 404 is a definitive "not found"
17
+ from the server, not a page served instead of content. (After N3, verify & lock with a test — see Parser side.)
18
+ - **A redirect inside the same ATS no longer blocks `domain_filter` (P21).** `CrawlCoordinator._handle_redirect_after_scan`
19
+ always allows a redirect to the **same host** (short ATS forms `apply.workable.com/j/X` → `apply.workable.com/<slug>/j/X`).
20
+ For a different host it still asks `domain_filter.is_allowed`, and when that refuses it consults a new host-alias
21
+ extension point `DomainFilter.allow_redirect(source, target)` (driven by a settable `redirect_host_alias` hook) — so
22
+ the Parser can pass its `DOMAIN_POLICIES` and allow `boards.greenhouse.io` → `job-boards.greenhouse.io` without
23
+ opening the whole internet.
24
+ - **Sitemap under a Cloudflare 52x origin error stops without retries (1.6).** On the first `520`–`527` response to
25
+ robots.txt / sitemap, `SitemapParser` stops the sitemap phase and returns `result["origin_down"] = True` (logged as
26
+ `ORIGIN_DOWN`) instead of burning 100–180 s on retries/escalation against a down origin. New
27
+ `SitemapParser._is_origin_down`; `origin_down` is propagated through robots / default / nested sitemap paths.
28
+ - **Nodes evicted by low-memory eviction are no longer lost in a browser full rescan (P23).**
29
+ `Graph.iter_nodes_async(include_evicted=True)` now also yields nodes that were evicted to disk (read-only, without
30
+ pulling them back into RAM), so a merge that iterates the source graph sees them. `Node.pinned` + `Graph.pin_node()`
31
+ / `Graph.pin_seed_nodes()` keep seed (`depth == 0`) vacancies in RAM — they were the oldest and got evicted first.
32
+ `Graph.eviction_stats` exposes the graph's own eviction counters (a backend-less graph evicts to LMDB/SQLite, which
33
+ `backend.get_stats()` reported as 0).
34
+ - **A 403 storm without vendor markers is treated as a block (P24).** `EscalatingDriver` tracks a per-host window of
35
+ recent HTTP statuses; if a host that recently returned 2xx then returns 403 with no anti-bot markers and either ≥3
36
+ consecutive 403 or >30 % 403 in the window, the 403 is reclassified as `SOFT_BLOCK` (`escalation_reason=http_403_storm`)
37
+ and the tier escalates. A single 403 on a 2xx background is still treated as auth/method (no escalation). New
38
+ `_HostStatusWindow`.
39
+ - **The HTML enrichment pool is priority-aware (2.1 / §15.4).** `run_in_html_pool(fn, *args, priority=...)` admits the
40
+ highest-priority waiter first when the pool is saturated (job-URLs and career subdomains enrich before catalog/blog
41
+ pages); with a free slot and empty queue it behaves exactly like the old FIFO semaphore. `Node` passes its URL-rule
42
+ priority. New `_PrioritySemaphore`.
43
+ - **Cloudflare 520–527 is never a bot block.** `escalation._classify` returns `NONE` for 520–527 (as for 404/410):
44
+ the CF error page embeds `challenge-platform`, so a down origin was classified `challenge` and escalated
45
+ http→tls→browser (~20 s per tier) before `SitemapParser` could stop on `ORIGIN_DOWN`.
46
+ - **robots.txt after a domain move keeps the new host's sitemap.** The same-origin gate and relative `Sitemap:`
47
+ paths now use the host that actually served robots.txt (`final_url`): `kilo.health` → `kilo.co/robots.txt` with
48
+ `Sitemap: … https://kilo.co/sitemap_index.xml` was dropped as cross-origin even after P19 cleaning.
49
+ - **Cancelling a call waiting in the HTML-pool queue resumes the enrich budget.** With the priority semaphore the
50
+ acquire moved out of the `try`, so a cancellation in the queue left the node deadline suspended.
51
+ - **`Node.pinned` does not leak into `user_data['_custom_fields']`** (`NodeMapper`, SQLite eviction storage base
52
+ fields) — it broke DTO/user-data invariants (`test_reserved_user_data_keys`, `test_node_mapper_content_depth`).
53
+ - Tests: `tests/test_v535_fixes.py` (28, offline: P19, 404/52x, 1.6, P21 incl. end-to-end `async_crawl`, P23, P24, 2.1).
54
+
55
+ ### Added
56
+
57
+ - `crawl` / `async_crawl` / `GraphCrawlerClient.crawl` / `CrawlerConfig`: `redirect_host_alias=` —
58
+ `Callable[[source_url, target_url], bool]` installed on the spider's `DomainFilter.redirect_host_alias`
59
+ (the extension point was otherwise unreachable through the public API).
60
+ - `SitemapParser._clean_sitemap_value`, `SitemapParser._is_origin_down`; `result["origin_down"]` key.
61
+ - `DomainFilter.allow_redirect(source_url, target_url)` and settable `DomainFilter.redirect_host_alias` hook.
62
+ - `Node.pinned`; `Graph.pin_node`, `Graph.pin_seed_nodes`, `Graph.eviction_stats`; `iter_nodes_async(include_evicted=)`.
63
+ - `run_in_html_pool(..., priority=)`.
64
+
65
+ ### Fixed (carried from Unreleased)
66
+
67
+ - **Sitemap `<lastmod>` is no longer dropped.** `SitemapParser` reads `<lastmod>` of every `<url>` (with or without
68
+ namespace) into `result["lastmod"]` (`{url: value}`, merged across robots/default/nested sitemaps), and
69
+ `SitemapProcessor.create_url_nodes(lastmod=...)` stores it on the new `SitemapNode.lastmod` field (W3C datetime as
70
+ in the file). Consumers can now tell a 2021 post from a fresh page; `_parse_urlset` keeps its contract.
71
+
72
+ - **An embedded captcha widget on a page with content is not a bot block.** `classify_block` follows the same
73
+ principle as the browser `CaptchaDetectorPlugin`: a block is a page served *instead of* content. After the
74
+ authoritative challenge headers (`cf-mitigated`, `x-amzn-waf-action`, `x-vercel-mitigated`), a non-blocking
75
+ response whose `<body>` has ≥1000 visible characters or ≥15 links is `NONE`, whatever widget or protection
76
+ script it embeds. Previously visible text was measured over the first 64 KB of the document (in WordPress that is
77
+ almost all `<head>`), and `cf-turnstile` / `challenge-platform` (Cloudflare loads its JSD script on regular pages)
78
+ were unconditional markers — so every page with reCAPTCHA/hCaptcha/Turnstile in a contact or apply form was
79
+ `captcha`, escalated HTTP→TLS and pinned the whole host to the TLS tier. `cf-turnstile`, `captcha-container`,
80
+ `challenge-platform`, `_incapsula_resource`, `awswafintegration` now count only on pages without content or with a
81
+ blocking status; markers and text are read from `<body>` up to 1 MB.
82
+
83
+ ## 5.3.4 — 2026-10
84
+
85
+ ### Fixed
86
+
87
+ - **The per-node enrichment limit measures active work, not the HTML-pool queue.** `NodeScanner` wraps
88
+ `enrich_node` in `enrich_budget(seconds)` (`graph_crawler.domain.entities.node`): while a call waits for a thread
89
+ of the HTML executor in `run_in_html_pool` (parse, `_update_from_context`, hashes), the deadline is suspended and
90
+ resumed with the same remainder the moment the work starts. With 5.3.3 the hook and the hashes moved into the
91
+ shared pool, so under load a node's wall-clock depended on how many neighbours stood in front of it: a burst of
92
+ large pages produced a cascade of `Per-node timeout … marking as ERROR` for pages whose own processing took a
93
+ fraction of the limit (and every stage re-queued behind the others). A page whose **own** processing exceeds
94
+ `node_enrich_timeout` still times out and still returns its discovered links. The crawl-level timeout remains the
95
+ upper bound for a node that waits in the queue.
96
+ - **`IsolatedExecutor.run` admits at most `max_workers` tasks at a time** (`threading.BoundedSemaphore`): the task
97
+ `timeout` now measures execution in the child process, not the wait behind other tasks. Previously a burst of
98
+ submissions could expire the timeout of a queued task, which killed the whole pool and failed every task in flight.
99
+
100
+ - **Control commands are no longer dropped silently.** `GraphSpider.process_control_messages` executes only
101
+ `SPIDER_COMMANDS` (`STOP`, `PAUSE`, `RESUME`, `REPRIORITIZE`, `FORCE_VISIT`); any other `CrawlCommand` logs
102
+ `WARNING Unsupported control command '<value>' from <sender> is ignored (supported: ...)` once per command type
103
+ per crawl (repeats at `DEBUG`) and the crawl goes on. `REPRIORITIZE` without `url`/`priority` and `FORCE_VISIT`
104
+ without `url` log a `WARNING` with the received `data` instead of doing nothing.
105
+
106
+ ### Added
107
+
108
+ - `EnrichBudget` (`pause()` / `resume()`) and `enrich_budget()` in `graph_crawler.domain.entities.node`.
109
+ - `SPIDER_COMMANDS` (`graph_crawler.domain.interfaces`): the commands `GraphSpider` executes.
110
+
111
+ ### Deprecated
112
+
113
+ - `CrawlCommand.SKIP_URL`, `CHANGE_STRATEGY`, `SET_DEPTH_LIMIT`, `REQUEST_STATE`, `UPDATE_GOAL` were never executed
114
+ by the spider. They will be removed from the enum in 5.4.0.
115
+
3
116
  ## 5.3.3 — 2026-10
4
117
 
5
118
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graph-crawler
3
- Version: 5.3.3
3
+ Version: 5.3.5
4
4
  Summary: Web crawler that builds a graph of a site. HTTP → TLS → browser escalation with pluggable browser engines.
5
5
  Author: 0-EternalJunior-0
6
6
  Maintainer: 0-EternalJunior-0
@@ -139,34 +139,20 @@ pip install "graph-crawler[browser]" # + browser (stock Playwright)
139
139
  pip install "graph-crawler[antidetect]" # + TLS, patchright, CloakBrowser, nodriver
140
140
  ```
141
141
 
142
- <<<<<<< HEAD
143
- | extra | what it adds |
144
- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
145
- | `browser` | `driver="playwright"` with bundled Chromium (`playwright` = alias) |
146
- | `tls` | `curl_cffi` — TLS fingerprint tier (`driver="tls"`) |
147
- | `stealth` | `patchright` + `playwright-stealth` — engine `"patchright"` |
148
- | `cloak` | `cloakbrowser` — engine `"cloakbrowser"` (stealth in the binary) |
149
- | `undetected` | `nodriver` — last tier (`driver="nodriver"`) |
150
- | `antidetect` | all four above |
151
- | `server` | FastAPI dashboard / REST / WebSocket (`graph_crawler.api.server`) |
152
- | `text` | `tiktoken` + `stop-words` — `count_tokens`, `extract_keywords` |
153
- | `markdown` | `html2md-clean` + `markdown-it-py` — `MarkdownPlugin` |
154
- | `mongodb`, `postgresql`, `celery`, `viz`, `articles`, `fuzzy`, `performance`, `system`, `all` | storage backends, distributed mode, extras |
155
- =======
156
- | extra | what it adds |
157
- |--------------|---------------------------------------------------------------------|
158
- | `browser` | `driver="playwright"` (`playwright` is an alias of this extra) |
159
- | `tls` | `curl_cffi` — TLS fingerprint tier, `driver="tls"` |
160
- | `stealth` | `patchright` + `playwright-stealth` — engine `"patchright"` |
161
- | `cloak` | `cloakbrowser` — engine `"cloakbrowser"` (stealth in the binary) |
162
- | `undetected` | `nodriver` — last tier, `driver="nodriver"` |
163
- | `antidetect` | the four extras above |
164
- | `server` | FastAPI dashboard / REST / WebSocket |
165
- | `text` | `tiktoken` + `stop-words` |
166
- | `markdown` | `html2md-clean` + `markdown-it-py` — `MarkdownPlugin` |
167
- | `fuzzy` | `rapidfuzz` — `graph.search(..., mode="fuzzy")` |
168
- | `mongodb`, `postgresql`, `celery`, `viz`, `articles`, `sitemap-fast`, `performance`, `system`, `all` | storage backends, distributed mode and other extras |
169
- >>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
142
+ | extra | what it adds |
143
+ | -------------- | ------------------------------------------------------------------------------- |
144
+ | `browser` | `driver="playwright"`, stock Playwright (`playwright` is an alias of this extra) |
145
+ | `tls` | `curl_cffi` — TLS fingerprint tier, `driver="tls"` |
146
+ | `stealth` | `patchright` + `playwright-stealth` — engine `"patchright"` |
147
+ | `cloak` | `cloakbrowser` — engine `"cloakbrowser"` (stealth in the binary) |
148
+ | `undetected` | `nodriver` — last tier, `driver="nodriver"` |
149
+ | `antidetect` | the five extras above |
150
+ | `server` | FastAPI dashboard / REST / WebSocket (`graph_crawler.api.server`) |
151
+ | `text` | `tiktoken` + `stop-words` — `count_tokens`, `extract_keywords` |
152
+ | `markdown` | `html2md-clean` + `markdown-it-py` — `MarkdownPlugin` |
153
+ | `fuzzy` | `rapidfuzz` — `graph.search(..., mode="fuzzy")` |
154
+ | `sitemap-fast` | `lxml` sitemap parser |
155
+ | `mongodb`, `postgresql`, `celery`, `viz`, `articles`, `performance`, `system`, `all` | storage backends, distributed mode and other extras |
170
156
 
171
157
  After installing a browser extra, download the browser once:
172
158
 
@@ -267,23 +253,13 @@ so `node.ok` is `False` for error pages.
267
253
 
268
254
  `driver=` accepts a name or a driver instance.
269
255
 
270
- <<<<<<< HEAD
271
- | name | tier | needs | notes |
272
- | ------------------- | ----------- | --------------------- | ----------------------------------------------- |
273
- | `http` / `async` | http | — | aiohttp, default |
274
- | `tls` / `curl_cffi` | tls | `[tls]` | Chrome TLS fingerprint, no JS |
275
- | `playwright` | browser | `[playwright]` | real browser; engine chosen by `browser_engine` |
276
- | `nodriver` | undetected | `[undetected]` | CDP without webdriver, async |
277
- | `escalating` | all of them | whatever is installed | http → tls → browser → undetected |
278
- =======
279
- | name | tier | needs | notes |
280
- |--------------|-------------|-----------------------|-------------------------------------------------|
281
- | `aiohttp` / `http` | http | — | default |
282
- | `tls` | tls | `[tls]` | Chrome TLS fingerprint, no JavaScript |
283
- | `playwright` | browser | `[browser]` | real browser; engine chosen by `browser_engine` |
284
- | `nodriver` | undetected | `[undetected]` | CDP without webdriver |
285
- | `escalating` | all of them | whatever is installed | http → tls → browser → undetected |
286
- >>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
256
+ | name | tier | needs | notes |
257
+ | ----------------------------- | ----------- | --------------------- | ----------------------------------------------- |
258
+ | `aiohttp` / `http` / `async` | http | — | default |
259
+ | `tls` / `curl_cffi` | tls | `[tls]` | Chrome TLS fingerprint, no JavaScript |
260
+ | `playwright` | browser | `[browser]` | real browser; engine chosen by `browser_engine` |
261
+ | `nodriver` | undetected | `[undetected]` | CDP without webdriver |
262
+ | `escalating` | all of them | whatever is installed | http → tls → browser → undetected |
287
263
 
288
264
  `escalating` creates tiers lazily: a browser starts only when a cheaper tier is
289
265
  blocked. Tiers whose dependency is not installed are skipped.
@@ -299,13 +275,8 @@ Every response goes through `classify_block()`:
299
275
  | `none` | normal page |
300
276
  | `soft_block` | 429, plain 503, or 200 with an empty body |
301
277
  | `challenge` | Cloudflare "Just a moment", `cf-mitigated: challenge`, Incapsula, DDoS-Guard |
302
- <<<<<<< HEAD
303
- | `hard_block` | 403 "Access Denied", Akamai `_abck`, DataDome headers |
304
- | `captcha` | PerimeterX, DataDome captcha, reCAPTCHA / hCaptcha / Turnstile markup |
305
- =======
306
278
  | `hard_block` | 403/401/405 **with** a vendor or "Access Denied" marker (Akamai `_abck`, DataDome/Incapsula headers, `x-iinfo`, Cloudflare `server`) |
307
- | `captcha` | PerimeterX, DataDome captcha, reCAPTCHA / hCaptcha / Turnstile markup |
308
- >>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
279
+ | `captcha` | PerimeterX, DataDome captcha, reCAPTCHA / hCaptcha / Turnstile markup |
309
280
 
310
281
  Anything other than `none`, and network errors, move the request to the next tier.
311
282
 
@@ -375,25 +346,14 @@ policy = EscalationPolicy(
375
346
 
376
347
  ### Browser engines
377
348
 
378
- <<<<<<< HEAD
379
- The browser tier is stock `PlaywrightDriver`; the _engine_ decides which
380
- Playwright module and which Chromium binary it launches.
381
-
382
- | engine | what it is | falls back to |
383
- | -------------- | ------------------------------------------------------------- | ------------- |
384
- | `cloakbrowser` | Chromium with fingerprint patches compiled into the binary | `patchright` |
385
- | `patchright` | Playwright fork without automation leaks (`Runtime.enable` …) | `playwright` |
386
- | `playwright` | stock | — |
387
- =======
388
- The browser tier is `PlaywrightDriver`; the *engine* decides which Playwright
349
+ The browser tier is `PlaywrightDriver`; the _engine_ decides which Playwright
389
350
  module and which Chromium binary it launches.
390
351
 
391
352
  | engine | what it is | falls back to |
392
- |----------------|---------------------------------------------------------------|---------------|
353
+ | -------------- | ------------------------------------------------------------- | ------------- |
393
354
  | `cloakbrowser` | Chromium with fingerprint patches compiled into the binary | `patchright` |
394
355
  | `patchright` | Playwright fork without automation leaks (`Runtime.enable` …) | `playwright` |
395
356
  | `playwright` | stock Playwright | — |
396
- >>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
397
357
 
398
358
  A fallback is logged once at `INFO` with the reason. To get an `ImportError`
399
359
  instead of a silent downgrade, set `GC_ENGINE_STRICT=1`, or pass
@@ -456,6 +416,37 @@ A target is found by `selector`, `text`, `placeholder` or `label` (`nth` picks
456
416
  among visible matches). The first failing action stops the chain and is written
457
417
  to `node.metadata["actions_error"]`.
458
418
 
419
+ ## Controlling a running crawl
420
+
421
+ `async_crawl(..., control_channel=channel)` (and `crawl`, `GraphCrawlerClient.crawl`)
422
+ reads commands from the channel between pages:
423
+
424
+ ```python
425
+ import graph_crawler as gc
426
+ from graph_crawler.domain.interfaces import AsyncQueueControlChannel, force_visit_command, stop_command
427
+
428
+ channel = AsyncQueueControlChannel()
429
+ channel.send(force_visit_command("https://example.com/hidden-page"))
430
+ graph = await gc.async_crawl("https://example.com", control_channel=channel)
431
+
432
+ # from a plugin, a callback or another task while the crawl runs:
433
+ channel.send(stop_command("enough data")) # graph.metadata["stop_reason"] == "enough data"
434
+ ```
435
+
436
+ | command | `data` | effect |
437
+ | -------------- | ----------------------- | ---------------------------------------------- |
438
+ | `STOP` | `reason` (optional) | stops the crawl, `stop_reason` = `reason` |
439
+ | `PAUSE` | — | pauses until `RESUME` or `STOP` |
440
+ | `RESUME` | — | continues a paused crawl |
441
+ | `REPRIORITIZE` | `url`, `priority` | moves a queued URL in the queue |
442
+ | `FORCE_VISIT` | `url` | adds the URL to the queue with top priority |
443
+
444
+ `SPIDER_COMMANDS` is this set. `SKIP_URL`, `CHANGE_STRATEGY`, `SET_DEPTH_LIMIT`,
445
+ `REQUEST_STATE` and `UPDATE_GOAL` are declared in `CrawlCommand` but not executed:
446
+ the spider logs `WARNING Unsupported control command ...` (once per command type
447
+ per crawl) and goes on; they will be removed in the next minor release. A supported
448
+ command without its required `data` fields is also skipped with a `WARNING`.
449
+
459
450
  ## Plugins and custom nodes
460
451
 
461
452
  A node plugin runs on every fetched page and writes to `user_data`, `metadata`
@@ -18,34 +18,20 @@ pip install "graph-crawler[browser]" # + browser (stock Playwright)
18
18
  pip install "graph-crawler[antidetect]" # + TLS, patchright, CloakBrowser, nodriver
19
19
  ```
20
20
 
21
- <<<<<<< HEAD
22
- | extra | what it adds |
23
- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
24
- | `browser` | `driver="playwright"` with bundled Chromium (`playwright` = alias) |
25
- | `tls` | `curl_cffi` — TLS fingerprint tier (`driver="tls"`) |
26
- | `stealth` | `patchright` + `playwright-stealth` — engine `"patchright"` |
27
- | `cloak` | `cloakbrowser` — engine `"cloakbrowser"` (stealth in the binary) |
28
- | `undetected` | `nodriver` — last tier (`driver="nodriver"`) |
29
- | `antidetect` | all four above |
30
- | `server` | FastAPI dashboard / REST / WebSocket (`graph_crawler.api.server`) |
31
- | `text` | `tiktoken` + `stop-words` — `count_tokens`, `extract_keywords` |
32
- | `markdown` | `html2md-clean` + `markdown-it-py` — `MarkdownPlugin` |
33
- | `mongodb`, `postgresql`, `celery`, `viz`, `articles`, `fuzzy`, `performance`, `system`, `all` | storage backends, distributed mode, extras |
34
- =======
35
- | extra | what it adds |
36
- |--------------|---------------------------------------------------------------------|
37
- | `browser` | `driver="playwright"` (`playwright` is an alias of this extra) |
38
- | `tls` | `curl_cffi` — TLS fingerprint tier, `driver="tls"` |
39
- | `stealth` | `patchright` + `playwright-stealth` — engine `"patchright"` |
40
- | `cloak` | `cloakbrowser` — engine `"cloakbrowser"` (stealth in the binary) |
41
- | `undetected` | `nodriver` — last tier, `driver="nodriver"` |
42
- | `antidetect` | the four extras above |
43
- | `server` | FastAPI dashboard / REST / WebSocket |
44
- | `text` | `tiktoken` + `stop-words` |
45
- | `markdown` | `html2md-clean` + `markdown-it-py` — `MarkdownPlugin` |
46
- | `fuzzy` | `rapidfuzz` — `graph.search(..., mode="fuzzy")` |
47
- | `mongodb`, `postgresql`, `celery`, `viz`, `articles`, `sitemap-fast`, `performance`, `system`, `all` | storage backends, distributed mode and other extras |
48
- >>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
21
+ | extra | what it adds |
22
+ | -------------- | ------------------------------------------------------------------------------- |
23
+ | `browser` | `driver="playwright"`, stock Playwright (`playwright` is an alias of this extra) |
24
+ | `tls` | `curl_cffi` — TLS fingerprint tier, `driver="tls"` |
25
+ | `stealth` | `patchright` + `playwright-stealth` — engine `"patchright"` |
26
+ | `cloak` | `cloakbrowser` — engine `"cloakbrowser"` (stealth in the binary) |
27
+ | `undetected` | `nodriver` — last tier, `driver="nodriver"` |
28
+ | `antidetect` | the five extras above |
29
+ | `server` | FastAPI dashboard / REST / WebSocket (`graph_crawler.api.server`) |
30
+ | `text` | `tiktoken` + `stop-words` — `count_tokens`, `extract_keywords` |
31
+ | `markdown` | `html2md-clean` + `markdown-it-py` — `MarkdownPlugin` |
32
+ | `fuzzy` | `rapidfuzz` — `graph.search(..., mode="fuzzy")` |
33
+ | `sitemap-fast` | `lxml` sitemap parser |
34
+ | `mongodb`, `postgresql`, `celery`, `viz`, `articles`, `performance`, `system`, `all` | storage backends, distributed mode and other extras |
49
35
 
50
36
  After installing a browser extra, download the browser once:
51
37
 
@@ -146,23 +132,13 @@ so `node.ok` is `False` for error pages.
146
132
 
147
133
  `driver=` accepts a name or a driver instance.
148
134
 
149
- <<<<<<< HEAD
150
- | name | tier | needs | notes |
151
- | ------------------- | ----------- | --------------------- | ----------------------------------------------- |
152
- | `http` / `async` | http | — | aiohttp, default |
153
- | `tls` / `curl_cffi` | tls | `[tls]` | Chrome TLS fingerprint, no JS |
154
- | `playwright` | browser | `[playwright]` | real browser; engine chosen by `browser_engine` |
155
- | `nodriver` | undetected | `[undetected]` | CDP without webdriver, async |
156
- | `escalating` | all of them | whatever is installed | http → tls → browser → undetected |
157
- =======
158
- | name | tier | needs | notes |
159
- |--------------|-------------|-----------------------|-------------------------------------------------|
160
- | `aiohttp` / `http` | http | — | default |
161
- | `tls` | tls | `[tls]` | Chrome TLS fingerprint, no JavaScript |
162
- | `playwright` | browser | `[browser]` | real browser; engine chosen by `browser_engine` |
163
- | `nodriver` | undetected | `[undetected]` | CDP without webdriver |
164
- | `escalating` | all of them | whatever is installed | http → tls → browser → undetected |
165
- >>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
135
+ | name | tier | needs | notes |
136
+ | ----------------------------- | ----------- | --------------------- | ----------------------------------------------- |
137
+ | `aiohttp` / `http` / `async` | http | — | default |
138
+ | `tls` / `curl_cffi` | tls | `[tls]` | Chrome TLS fingerprint, no JavaScript |
139
+ | `playwright` | browser | `[browser]` | real browser; engine chosen by `browser_engine` |
140
+ | `nodriver` | undetected | `[undetected]` | CDP without webdriver |
141
+ | `escalating` | all of them | whatever is installed | http → tls → browser → undetected |
166
142
 
167
143
  `escalating` creates tiers lazily: a browser starts only when a cheaper tier is
168
144
  blocked. Tiers whose dependency is not installed are skipped.
@@ -178,13 +154,8 @@ Every response goes through `classify_block()`:
178
154
  | `none` | normal page |
179
155
  | `soft_block` | 429, plain 503, or 200 with an empty body |
180
156
  | `challenge` | Cloudflare "Just a moment", `cf-mitigated: challenge`, Incapsula, DDoS-Guard |
181
- <<<<<<< HEAD
182
- | `hard_block` | 403 "Access Denied", Akamai `_abck`, DataDome headers |
183
- | `captcha` | PerimeterX, DataDome captcha, reCAPTCHA / hCaptcha / Turnstile markup |
184
- =======
185
157
  | `hard_block` | 403/401/405 **with** a vendor or "Access Denied" marker (Akamai `_abck`, DataDome/Incapsula headers, `x-iinfo`, Cloudflare `server`) |
186
- | `captcha` | PerimeterX, DataDome captcha, reCAPTCHA / hCaptcha / Turnstile markup |
187
- >>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
158
+ | `captcha` | PerimeterX, DataDome captcha, reCAPTCHA / hCaptcha / Turnstile markup |
188
159
 
189
160
  Anything other than `none`, and network errors, move the request to the next tier.
190
161
 
@@ -254,25 +225,14 @@ policy = EscalationPolicy(
254
225
 
255
226
  ### Browser engines
256
227
 
257
- <<<<<<< HEAD
258
- The browser tier is stock `PlaywrightDriver`; the _engine_ decides which
259
- Playwright module and which Chromium binary it launches.
260
-
261
- | engine | what it is | falls back to |
262
- | -------------- | ------------------------------------------------------------- | ------------- |
263
- | `cloakbrowser` | Chromium with fingerprint patches compiled into the binary | `patchright` |
264
- | `patchright` | Playwright fork without automation leaks (`Runtime.enable` …) | `playwright` |
265
- | `playwright` | stock | — |
266
- =======
267
- The browser tier is `PlaywrightDriver`; the *engine* decides which Playwright
228
+ The browser tier is `PlaywrightDriver`; the _engine_ decides which Playwright
268
229
  module and which Chromium binary it launches.
269
230
 
270
231
  | engine | what it is | falls back to |
271
- |----------------|---------------------------------------------------------------|---------------|
232
+ | -------------- | ------------------------------------------------------------- | ------------- |
272
233
  | `cloakbrowser` | Chromium with fingerprint patches compiled into the binary | `patchright` |
273
234
  | `patchright` | Playwright fork without automation leaks (`Runtime.enable` …) | `playwright` |
274
235
  | `playwright` | stock Playwright | — |
275
- >>>>>>> d3db85cd9c8826d11c10e33988c837f3e8ffbe88
276
236
 
277
237
  A fallback is logged once at `INFO` with the reason. To get an `ImportError`
278
238
  instead of a silent downgrade, set `GC_ENGINE_STRICT=1`, or pass
@@ -335,6 +295,37 @@ A target is found by `selector`, `text`, `placeholder` or `label` (`nth` picks
335
295
  among visible matches). The first failing action stops the chain and is written
336
296
  to `node.metadata["actions_error"]`.
337
297
 
298
+ ## Controlling a running crawl
299
+
300
+ `async_crawl(..., control_channel=channel)` (and `crawl`, `GraphCrawlerClient.crawl`)
301
+ reads commands from the channel between pages:
302
+
303
+ ```python
304
+ import graph_crawler as gc
305
+ from graph_crawler.domain.interfaces import AsyncQueueControlChannel, force_visit_command, stop_command
306
+
307
+ channel = AsyncQueueControlChannel()
308
+ channel.send(force_visit_command("https://example.com/hidden-page"))
309
+ graph = await gc.async_crawl("https://example.com", control_channel=channel)
310
+
311
+ # from a plugin, a callback or another task while the crawl runs:
312
+ channel.send(stop_command("enough data")) # graph.metadata["stop_reason"] == "enough data"
313
+ ```
314
+
315
+ | command | `data` | effect |
316
+ | -------------- | ----------------------- | ---------------------------------------------- |
317
+ | `STOP` | `reason` (optional) | stops the crawl, `stop_reason` = `reason` |
318
+ | `PAUSE` | — | pauses until `RESUME` or `STOP` |
319
+ | `RESUME` | — | continues a paused crawl |
320
+ | `REPRIORITIZE` | `url`, `priority` | moves a queued URL in the queue |
321
+ | `FORCE_VISIT` | `url` | adds the URL to the queue with top priority |
322
+
323
+ `SPIDER_COMMANDS` is this set. `SKIP_URL`, `CHANGE_STRATEGY`, `SET_DEPTH_LIMIT`,
324
+ `REQUEST_STATE` and `UPDATE_GOAL` are declared in `CrawlCommand` but not executed:
325
+ the spider logs `WARNING Unsupported control command ...` (once per command type
326
+ per crawl) and goes on; they will be removed in the next minor release. A supported
327
+ command without its required `data` fields is also skipped with a `WARNING`.
328
+
338
329
  ## Plugins and custom nodes
339
330
 
340
331
  A node plugin runs on every fetched page and writes to `user_data`, `metadata`
@@ -68,6 +68,7 @@ async def async_crawl_impl(
68
68
  worker_pool_size: int | None = None, # A_liffe L3
69
69
  per_host_max_in_flight: int | None = None, # ліміт сторінок на хост (None = без ліміту)
70
70
  node_enrich_timeout: float | None = None, # per-node таймаут збагачення, с (None = 45 с)
71
+ redirect_host_alias: Any = None, # Callable[[src, dst], bool] — дозволені крос-хост редіректи
71
72
  driver: DriverType | None = None,
72
73
  driver_config: dict[str, Any] | None = None,
73
74
  storage: StorageType | None = None,
@@ -243,6 +244,7 @@ async def async_crawl_impl(
243
244
  worker_pool_size=worker_pool_size,
244
245
  per_host_max_in_flight=per_host_max_in_flight,
245
246
  node_enrich_timeout=node_enrich_timeout,
247
+ redirect_host_alias=redirect_host_alias,
246
248
  # Low-memory режим
247
249
  low_memory_mode=low_memory_mode,
248
250
  evict_threshold=evict_threshold,
@@ -56,6 +56,7 @@ async def async_crawl(
56
56
  worker_pool_size: int | None = None, # A_liffe L3
57
57
  per_host_max_in_flight: int | None = None,
58
58
  node_enrich_timeout: float | None = None,
59
+ redirect_host_alias: Any = None,
59
60
  driver: DriverType | None = None,
60
61
  driver_config: dict[str, Any] | None = None,
61
62
  storage: StorageType | None = None,
@@ -134,6 +135,7 @@ async def async_crawl(
134
135
  worker_pool_size=worker_pool_size,
135
136
  per_host_max_in_flight=per_host_max_in_flight,
136
137
  node_enrich_timeout=node_enrich_timeout,
138
+ redirect_host_alias=redirect_host_alias,
137
139
  driver=driver,
138
140
  driver_config=driver_config,
139
141
  storage=storage,
@@ -130,6 +130,8 @@ class GraphCrawlerClient:
130
130
  per_host_max_in_flight: int | None = None,
131
131
  # Per-node таймаут збагачення, с (None = DEFAULT_NODE_ENRICH_TIMEOUT)
132
132
  node_enrich_timeout: float | None = None,
133
+ # Callable[[source_url, target_url], bool] — дозволені крос-хост редіректи
134
+ redirect_host_alias: Any = None,
133
135
  # Low-memory режим
134
136
  low_memory_mode: bool = False,
135
137
  evict_threshold: int = 500,
@@ -195,6 +197,7 @@ class GraphCrawlerClient:
195
197
  worker_pool_size=worker_pool_size,
196
198
  per_host_max_in_flight=per_host_max_in_flight,
197
199
  node_enrich_timeout=node_enrich_timeout,
200
+ redirect_host_alias=redirect_host_alias,
198
201
  # Low-memory режим
199
202
  low_memory_mode=low_memory_mode,
200
203
  evict_threshold=evict_threshold,
@@ -50,6 +50,7 @@ def crawl(
50
50
  worker_pool_size: int | None = None,
51
51
  per_host_max_in_flight: int | None = None,
52
52
  node_enrich_timeout: float | None = None,
53
+ redirect_host_alias: Any = None,
53
54
  driver: DriverType | None = None,
54
55
  driver_config: dict[str, Any] | None = None,
55
56
  storage: StorageType | None = None,
@@ -98,6 +99,8 @@ def crawl(
98
99
  per_host_max_in_flight: ліміт одночасних запитів на хост (``None`` — без ліміту).
99
100
  node_enrich_timeout: ліміт збагачення однієї ноди, с (``None`` — 45 с); при перевищенні
100
101
  нода стає ERROR, лінки, витягнуті до таймауту, лишаються в графі.
102
+ redirect_host_alias: ``Callable[[source_url, target_url], bool]`` — дозволяє редірект
103
+ на хост поза ``allowed_domains`` (напр. між хостами однієї ATS); ``None`` — ні.
101
104
  driver: драйвер завантаження (``"http"``, ``"playwright"``, ``"escalating"`` тощо).
102
105
  driver_config: налаштування драйвера.
103
106
  storage: тип сховища (``"memory"``, ``"json"``, ``"sqlite"`` тощо).
@@ -178,6 +181,7 @@ def crawl(
178
181
  "worker_pool_size": worker_pool_size,
179
182
  "per_host_max_in_flight": per_host_max_in_flight,
180
183
  "node_enrich_timeout": node_enrich_timeout,
184
+ "redirect_host_alias": redirect_host_alias,
181
185
  "driver": driver,
182
186
  "driver_config": driver_config,
183
187
  "storage": storage,
@@ -59,6 +59,7 @@ class NodeMapper:
59
59
  "processed_at",
60
60
  "should_scan",
61
61
  "can_create_edges",
62
+ "pinned",
62
63
  "created_at",
63
64
  "metadata",
64
65
  "user_data",
@@ -1003,11 +1003,30 @@ class CrawlCoordinator:
1003
1003
  # Захист від cross-domain редіректів повз same_domain фільтр:
1004
1004
  # драйвер може прозоро фоловити Location: на чужий домен, тож
1005
1005
  # питаємо domain_filter перед тим як зафіксувати редірект у графі.
1006
+ #
1007
+ # P21: редірект на ТОЙ САМИЙ хост дозволяємо завжди (коротка форма ATS
1008
+ # `apply.workable.com/j/X` → `apply.workable.com/<slug>/j/X`). Для різних
1009
+ # хостів — domain_filter; якщо він відмовив, пробуємо точку розширення
1010
+ # «аліаси хостів» (`allow_redirect`), якою Parser передає свої
1011
+ # DOMAIN_POLICIES (`boards.greenhouse.io` → `job-boards.greenhouse.io`).
1006
1012
  try:
1013
+ from urllib.parse import urlsplit
1014
+
1015
+ src_host = urlsplit(original_node.url).hostname or ""
1016
+ tgt_host = urlsplit(final_url).hostname or ""
1017
+ same_host = bool(src_host) and src_host == tgt_host
1018
+
1007
1019
  domain_filter = getattr(self.processor, "domain_filter", None)
1008
- if domain_filter is not None and not domain_filter.is_allowed(
1009
- final_url, source_url=original_node.url
1010
- ):
1020
+ blocked = False
1021
+ if not same_host and domain_filter is not None:
1022
+ if not domain_filter.is_allowed(final_url, source_url=original_node.url):
1023
+ alias_hook = getattr(domain_filter, "allow_redirect", None)
1024
+ allowed_by_alias = bool(
1025
+ alias_hook and alias_hook(original_node.url, final_url)
1026
+ )
1027
+ blocked = not allowed_by_alias
1028
+
1029
+ if blocked:
1011
1030
  logger.info(
1012
1031
  "Redirect blocked by domain_filter: %s -> %s (cross-domain or disallowed)",
1013
1032
  original_node.url,
@@ -47,6 +47,13 @@ class DomainFilter(BaseURLFilter):
47
47
  super().__init__(config.model_dump() if hasattr(config, "model_dump") else dict(config))
48
48
  self.event_bus = event_bus
49
49
 
50
+ # P21: точка розширення «аліаси хостів». Callable[[source_url, target_url], bool].
51
+ # Коли редірект веде на хост поза allowed_domains, координатор питає цей
52
+ # хук — так Parser дозволяє редірект між хостами однієї ATS-політики
53
+ # (boards.greenhouse.io → job-boards.greenhouse.io), не відкриваючи весь
54
+ # інтернет. None → редірект за межі allowed_domains блокується, як раніше.
55
+ self.redirect_host_alias = None
56
+
50
57
  # Парсимо спеціальні патерни
51
58
  self._parse_special_patterns()
52
59
 
@@ -92,6 +99,30 @@ class DomainFilter(BaseURLFilter):
92
99
  def name(self) -> str:
93
100
  return "domain"
94
101
 
102
+ def allow_redirect(self, source_url: str, target_url: str) -> bool:
103
+ """Точка розширення P21: чи дозволити редірект на хост поза allowed_domains.
104
+
105
+ Координатор завжди дозволяє редірект на той самий хост сам; сюди
106
+ потрапляють лише крос-хостові редіректи, які не пройшли ``is_allowed``.
107
+ За замовчуванням ``False``; якщо встановлено ``redirect_host_alias``
108
+ (Parser передає свої DOMAIN_POLICIES) — делегуємо рішення йому.
109
+
110
+ Args:
111
+ source_url: URL, що редіректив.
112
+ target_url: Фінальний URL редіректу.
113
+
114
+ Returns:
115
+ True, якщо редірект на цей чужий хост дозволено як аліас.
116
+ """
117
+ hook = self.redirect_host_alias
118
+ if hook is None:
119
+ return False
120
+ try:
121
+ return bool(hook(source_url, target_url))
122
+ except Exception as exc: # noqa: BLE001 — хук не повинен валити обхід
123
+ logger.warning("redirect_host_alias hook failed for %s -> %s: %s", source_url, target_url, exc)
124
+ return False
125
+
95
126
  def _is_subdomain_of(self, domain: str, base_domain: str) -> bool:
96
127
  """
97
128
  Перевіряє чи domain є субдоменом base_domain або самим base_domain.