graph-crawler 5.3.5__tar.gz → 5.3.7__tar.gz

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