graph-crawler 5.4.1__tar.gz → 5.4.2__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 (478) hide show
  1. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/CHANGELOG.md +15 -0
  2. {graph_crawler-5.4.1/graph_crawler.egg-info → graph_crawler-5.4.2}/PKG-INFO +1 -1
  3. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/crawl_coordinator.py +65 -1
  4. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/worker_pool.py +4 -0
  5. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/page_fetch.py +40 -8
  6. {graph_crawler-5.4.1 → graph_crawler-5.4.2/graph_crawler.egg-info}/PKG-INFO +1 -1
  7. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/pyproject.toml +1 -1
  8. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/LICENSE +0 -0
  9. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/MANIFEST.in +0 -0
  10. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/README.md +0 -0
  11. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/__init__.py +0 -0
  12. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/__version__.py +0 -0
  13. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/ai/__init__.py +0 -0
  14. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/__init__.py +0 -0
  15. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/_core.py +0 -0
  16. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/_distributed.py +0 -0
  17. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/_shared.py +0 -0
  18. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/_sitemap_distributed.py +0 -0
  19. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/async_.py +0 -0
  20. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/cli.py +0 -0
  21. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/client/__init__.py +0 -0
  22. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/client/client.py +0 -0
  23. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/history_manager.py +0 -0
  24. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/project_init.py +0 -0
  25. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/server/__init__.py +0 -0
  26. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/server/crawl_monitor.py +0 -0
  27. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/server/dashboard.py +0 -0
  28. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/server/rest_api.py +0 -0
  29. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/server/webhooks.py +0 -0
  30. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/server/websocket_manager.py +0 -0
  31. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/stats_collector.py +0 -0
  32. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/api/sync.py +0 -0
  33. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/__init__.py +0 -0
  34. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/bootstrap.py +0 -0
  35. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/context/__init__.py +0 -0
  36. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/context/dependency_registry.py +0 -0
  37. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/context/graph_context.py +0 -0
  38. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/context/merge_context.py +0 -0
  39. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/__init__.py +0 -0
  40. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/edge_dto.py +0 -0
  41. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/graph_dto.py +0 -0
  42. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/mappers/__init__.py +0 -0
  43. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/mappers/edge_mapper.py +0 -0
  44. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/mappers/graph_mapper.py +0 -0
  45. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/mappers/node_data_codec.py +0 -0
  46. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/mappers/node_mapper.py +0 -0
  47. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/node_dto.py +0 -0
  48. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/dto/utils.py +0 -0
  49. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/__init__.py +0 -0
  50. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/application_container.py +0 -0
  51. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/driver_factory.py +0 -0
  52. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/__init__.py +0 -0
  53. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/base_exporter.py +0 -0
  54. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/csv_exporter.py +0 -0
  55. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/edge_exporter.py +0 -0
  56. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/excel_exporter.py +0 -0
  57. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/jsonl_exporter.py +0 -0
  58. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/node_exporter.py +0 -0
  59. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/parquet_exporter.py +0 -0
  60. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/exporters/sql_exporter.py +0 -0
  61. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/finalize.py +0 -0
  62. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/low_memory_graph_saver.py +0 -0
  63. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/resource_budget.py +0 -0
  64. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/scheduling/__init__.py +0 -0
  65. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/scheduling/rule_based_scheduler.py +0 -0
  66. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/services/storage_factory.py +0 -0
  67. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/__init__.py +0 -0
  68. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/__init__.py +0 -0
  69. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/adaptive_throttler.py +0 -0
  70. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/async_bridge.py +0 -0
  71. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/base_spider.py +0 -0
  72. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/celery_batch_spider.py +0 -0
  73. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/celery_spider.py +0 -0
  74. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/checkpoint.py +0 -0
  75. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/dead_letter_queue.py +0 -0
  76. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/domain_rate_limiter.py +0 -0
  77. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/filters/__init__.py +0 -0
  78. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/filters/base.py +0 -0
  79. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/filters/domain_filter.py +0 -0
  80. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/filters/domain_patterns.py +0 -0
  81. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/filters/path_filter.py +0 -0
  82. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/incremental_strategy.py +0 -0
  83. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/link_processor.py +0 -0
  84. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/multiprocess_spider.py +0 -0
  85. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/node_scanner.py +0 -0
  86. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/parsers/__init__.py +0 -0
  87. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/parsers/base.py +0 -0
  88. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/parsers/html_parser.py +0 -0
  89. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/progress_tracker.py +0 -0
  90. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/scheduler.py +0 -0
  91. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/serialization_mixin.py +0 -0
  92. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/sitemap_parser.py +0 -0
  93. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/sitemap_processor.py +0 -0
  94. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/sitemap_spider.py +0 -0
  95. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/spider.py +0 -0
  96. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/crawling/spider_lifecycle.py +0 -0
  97. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/graph_export.py +0 -0
  98. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/graph_merge/__init__.py +0 -0
  99. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/graph_merge/graph_operations.py +0 -0
  100. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/application/use_cases/graph_merge/merge_strategies.py +0 -0
  101. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/data/__init__.py +0 -0
  102. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/data/backends/__init__.py +0 -0
  103. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/data/backends/memory.py +0 -0
  104. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/data/backends/sqlite.py +0 -0
  105. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/data/interfaces.py +0 -0
  106. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/__init__.py +0 -0
  107. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/context/__init__.py +0 -0
  108. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/context/crawl_context.py +0 -0
  109. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/__init__.py +0 -0
  110. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/edge.py +0 -0
  111. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/edge_analysis.py +0 -0
  112. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/graph.py +0 -0
  113. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/graph_operations.py +0 -0
  114. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/graph_statistics.py +0 -0
  115. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/merge_strategies.py +0 -0
  116. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/node.py +0 -0
  117. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/redirect_mode.py +0 -0
  118. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/registries.py +0 -0
  119. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/sitemap_node.py +0 -0
  120. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/entities/strategies.py +0 -0
  121. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/__init__.py +0 -0
  122. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/base.py +0 -0
  123. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/crawl.py +0 -0
  124. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/edge.py +0 -0
  125. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/event_bus.py +0 -0
  126. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/events.py +0 -0
  127. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/fetch.py +0 -0
  128. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/idempotent_subscriber.py +0 -0
  129. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/legacy_type_map.py +0 -0
  130. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/llm.py +0 -0
  131. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/node.py +0 -0
  132. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/plugin.py +0 -0
  133. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/scheduler.py +0 -0
  134. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/events/storage.py +0 -0
  135. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/__init__.py +0 -0
  136. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/adapter.py +0 -0
  137. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/control_channel.py +0 -0
  138. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/crawl_context.py +0 -0
  139. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/distributed_spider.py +0 -0
  140. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/driver.py +0 -0
  141. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/event_bus.py +0 -0
  142. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/exporters.py +0 -0
  143. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/filter.py +0 -0
  144. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/merge_context.py +0 -0
  145. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/node_interfaces.py +0 -0
  146. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/parser.py +0 -0
  147. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/plugin_manager.py +0 -0
  148. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/processor.py +0 -0
  149. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/scanner.py +0 -0
  150. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/scheduler.py +0 -0
  151. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/scheduling/__init__.py +0 -0
  152. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/scheduling/frontier.py +0 -0
  153. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/scheduling/scheduler.py +0 -0
  154. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/spider.py +0 -0
  155. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/stop_condition.py +0 -0
  156. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/storage/__init__.py +0 -0
  157. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/storage/base.py +0 -0
  158. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/storage/eviction.py +0 -0
  159. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/interfaces/storage/queue.py +0 -0
  160. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/__init__.py +0 -0
  161. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/adjacency_maintenance.py +0 -0
  162. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/cycle_detection.py +0 -0
  163. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/degree_ranking.py +0 -0
  164. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/depth_context.py +0 -0
  165. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/display_title.py +0 -0
  166. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/duplicate_detection.py +0 -0
  167. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/duplicate_selector.py +0 -0
  168. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/duplicate_stats.py +0 -0
  169. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/edge_filter.py +0 -0
  170. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/edge_statistics.py +0 -0
  171. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/eviction_policy.py +0 -0
  172. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/graph_query_cascade.py +0 -0
  173. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/graph_statistics.py +0 -0
  174. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/graph_traversal.py +0 -0
  175. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/language_versions.py +0 -0
  176. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/lsh_index.py +0 -0
  177. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/minimap_builder.py +0 -0
  178. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/node_content_resolver.py +0 -0
  179. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/node_view_builder.py +0 -0
  180. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/pagination_guards.py +0 -0
  181. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/predicate_ops.py +0 -0
  182. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/schema_filter.py +0 -0
  183. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/search_haystack.py +0 -0
  184. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/simhash_clustering.py +0 -0
  185. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/simhash_fallback.py +0 -0
  186. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/similarity_ranking.py +0 -0
  187. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/text_search.py +0 -0
  188. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/url_clustering.py +0 -0
  189. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/url_deduplication.py +0 -0
  190. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/url_normalizer.py +0 -0
  191. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/url_rule_matcher.py +0 -0
  192. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/services/url_tree_builder.py +0 -0
  193. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/__init__.py +0 -0
  194. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/configs.py +0 -0
  195. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/domain_patterns.py +0 -0
  196. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/fetch_options.py +0 -0
  197. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/html_document.py +0 -0
  198. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/lifecycle.py +0 -0
  199. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/models.py +0 -0
  200. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/page_actions.py +0 -0
  201. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/redirect_hop.py +0 -0
  202. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/scheduling/__init__.py +0 -0
  203. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/scheduling/crawl_request.py +0 -0
  204. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/scheduling/retry_policy.py +0 -0
  205. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/settings.py +0 -0
  206. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/url_normalization.py +0 -0
  207. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/domain/value_objects/views.py +0 -0
  208. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/__init__.py +0 -0
  209. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/__init__.py +0 -0
  210. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/base.py +0 -0
  211. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/cache_middleware.py +0 -0
  212. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/chain.py +0 -0
  213. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/error_recovery_middleware.py +0 -0
  214. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/logging_middleware.py +0 -0
  215. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/proxy_health.py +0 -0
  216. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/proxy_middleware.py +0 -0
  217. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/proxy_models.py +0 -0
  218. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/proxy_selection.py +0 -0
  219. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/rate_limit_middleware.py +0 -0
  220. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/request_response_middleware.py +0 -0
  221. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/retry_middleware.py +0 -0
  222. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/robots_cache.py +0 -0
  223. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/robots_middleware.py +0 -0
  224. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/robots_validator.py +0 -0
  225. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/middleware/user_agent_middleware.py +0 -0
  226. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/__init__.py +0 -0
  227. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/builtin/__init__.py +0 -0
  228. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/builtin/stats_export_plugin.py +0 -0
  229. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/crawl_engine/__init__.py +0 -0
  230. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/crawl_engine/base.py +0 -0
  231. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/crawl_engine/priority_provider.py +0 -0
  232. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/crawl_engine/smart_crawl.py +0 -0
  233. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/__init__.py +0 -0
  234. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/anti_bot_detection.py +0 -0
  235. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/anti_bot_playwright.py +0 -0
  236. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/anti_bot_scripts.py +0 -0
  237. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/anti_bot_stealth.py +0 -0
  238. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/base.py +0 -0
  239. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/captcha/__init__.py +0 -0
  240. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/captcha/detector.py +0 -0
  241. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/captcha/models.py +0 -0
  242. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/captcha/plugin.py +0 -0
  243. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/engine/captcha/services.py +0 -0
  244. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/__init__.py +0 -0
  245. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/base.py +0 -0
  246. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/content_extractors/__init__.py +0 -0
  247. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/content_extractors/base.py +0 -0
  248. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/content_extractors/goose3_extractor.py +0 -0
  249. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/content_extractors/newspaper_extractor.py +0 -0
  250. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/content_extractors/plugin.py +0 -0
  251. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/content_extractors/readability_extractor.py +0 -0
  252. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/defaults.py +0 -0
  253. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/extractors/__init__.py +0 -0
  254. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/extractors/email_extractor.py +0 -0
  255. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/extractors/phone_extractor.py +0 -0
  256. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/extractors/price_extractor.py +0 -0
  257. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/links.py +0 -0
  258. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/markdown_plugin.py +0 -0
  259. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/metadata.py +0 -0
  260. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/smart_page_finder.py +0 -0
  261. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/__init__.py +0 -0
  262. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/constants.py +0 -0
  263. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/exceptions.py +0 -0
  264. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/extractor.py +0 -0
  265. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/options.py +0 -0
  266. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/__init__.py +0 -0
  267. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/base.py +0 -0
  268. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/jsonld.py +0 -0
  269. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/microdata.py +0 -0
  270. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/opengraph.py +0 -0
  271. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/rdfa.py +0 -0
  272. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/twitter.py +0 -0
  273. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/plugin.py +0 -0
  274. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/structured_data/result.py +0 -0
  275. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/text.py +0 -0
  276. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/node/url_normalizer.py +0 -0
  277. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/extensions/plugins/registry.py +0 -0
  278. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/__init__.py +0 -0
  279. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/adapters/__init__.py +0 -0
  280. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/adapters/base.py +0 -0
  281. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/adapters/selectolax_adapter.py +0 -0
  282. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/__init__.py +0 -0
  283. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/celery_app.py +0 -0
  284. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/celery_batch.py +0 -0
  285. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/celery_job_task.py +0 -0
  286. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/celery_unified.py +0 -0
  287. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/config.py +0 -0
  288. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/easy_crawler.py +0 -0
  289. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/messaging/worker_api.py +0 -0
  290. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/__init__.py +0 -0
  291. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/auto_storage.py +0 -0
  292. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/base.py +0 -0
  293. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/eviction.py +0 -0
  294. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/graph_repository.py +0 -0
  295. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/json/__init__.py +0 -0
  296. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/json_storage.py +0 -0
  297. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/lmdb_eviction_storage.py +0 -0
  298. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/memory/__init__.py +0 -0
  299. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/memory_dedup_store.py +0 -0
  300. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/memory_frontier.py +0 -0
  301. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/memory_politeness_store.py +0 -0
  302. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/memory_storage.py +0 -0
  303. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/mongodb_storage.py +0 -0
  304. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/naming_strategy.py +0 -0
  305. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/postgresql_storage.py +0 -0
  306. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/protocols/__init__.py +0 -0
  307. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/redis_dedup_store.py +0 -0
  308. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/redis_politeness_store.py +0 -0
  309. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/repository.py +0 -0
  310. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/sqlite/__init__.py +0 -0
  311. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/sqlite_eviction_storage.py +0 -0
  312. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/sqlite_storage.py +0 -0
  313. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/__init__.py +0 -0
  314. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/file_job_storage.py +0 -0
  315. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/file_queue_storage.py +0 -0
  316. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/memory_job_storage.py +0 -0
  317. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/memory_queue_storage.py +0 -0
  318. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/postgresql_job_storage.py +0 -0
  319. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/postgresql_queue_storage.py +0 -0
  320. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/persistence/unified/unified_storage.py +0 -0
  321. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/__init__.py +0 -0
  322. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/__init__.py +0 -0
  323. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/config.py +0 -0
  324. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/context.py +0 -0
  325. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/driver.py +0 -0
  326. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/driver_curl_cffi.py +0 -0
  327. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/plugins/__init__.py +0 -0
  328. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle.py +0 -0
  329. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle_adaptive.py +0 -0
  330. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/plugins/headers.py +0 -0
  331. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/plugins/http_cache.py +0 -0
  332. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/plugins/rate_limiter.py +0 -0
  333. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/plugins/retry.py +0 -0
  334. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/async_http/stages.py +0 -0
  335. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/base.py +0 -0
  336. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/base_plugin.py +0 -0
  337. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/compression/__init__.py +0 -0
  338. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/compression/strategy.py +0 -0
  339. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/connection_pool.py +0 -0
  340. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/context.py +0 -0
  341. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/core/__init__.py +0 -0
  342. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/core/base_async.py +0 -0
  343. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/core/body_decoder.py +0 -0
  344. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/core/body_limit.py +0 -0
  345. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/core/mixins.py +0 -0
  346. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/core/redirect_utils.py +0 -0
  347. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/escalation.py +0 -0
  348. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/factory.py +0 -0
  349. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/__init__.py +0 -0
  350. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/adaptive_semaphore.py +0 -0
  351. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/batch_scheduler.py +0 -0
  352. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/browser_factory.py +0 -0
  353. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/browser_profile.py +0 -0
  354. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/config.py +0 -0
  355. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/context.py +0 -0
  356. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/driver.py +0 -0
  357. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/engine.py +0 -0
  358. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/fetch_metrics.py +0 -0
  359. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/net/__init__.py +0 -0
  360. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/net/geoip.py +0 -0
  361. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/net/proxy_pool.py +0 -0
  362. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/net/rate_limiter.py +0 -0
  363. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/page_actions.py +0 -0
  364. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/__init__.py +0 -0
  365. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/_network_record.py +0 -0
  366. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_detector.py +0 -0
  367. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_solver.py +0 -0
  368. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/cookie_consent.py +0 -0
  369. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/form_filler.py +0 -0
  370. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/human_bezier.py +0 -0
  371. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/network_log.py +0 -0
  372. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/ram_adaptive.py +0 -0
  373. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/screenshot.py +0 -0
  374. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/plugins/user_profile.py +0 -0
  375. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/pool_lifecycle.py +0 -0
  376. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/pooled_driver.py +0 -0
  377. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/stages.py +0 -0
  378. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/playwright/stealth_hints.py +0 -0
  379. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/plugin_manager.py +0 -0
  380. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/protocols.py +0 -0
  381. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/undetected/__init__.py +0 -0
  382. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/infrastructure/transport/undetected/async_driver.py +0 -0
  383. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/native/__init__.py +0 -0
  384. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/native/bloom_filter.pyx +0 -0
  385. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/native/html_parser.pyx +0 -0
  386. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/native/setup.py +0 -0
  387. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/native/url_utils.pyx +0 -0
  388. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/__init__.py +0 -0
  389. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/error_tracing.py +0 -0
  390. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/extensions/__init__.py +0 -0
  391. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/extensions/memory_guard.py +0 -0
  392. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/extensions/state_manager.py +0 -0
  393. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/__init__.py +0 -0
  394. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/base.py +0 -0
  395. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/base_metrics_listener.py +0 -0
  396. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/crawl_listener.py +0 -0
  397. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/dlq_listener.py +0 -0
  398. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/error_listener.py +0 -0
  399. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/logging_listener.py +0 -0
  400. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/metrics_listener.py +0 -0
  401. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/node_listener.py +0 -0
  402. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/plugin_listener.py +0 -0
  403. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/storage_listener.py +0 -0
  404. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/listeners/url_listener.py +0 -0
  405. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/metrics/__init__.py +0 -0
  406. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/metrics/memory_profiler.py +0 -0
  407. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/metrics/metrics_collector.py +0 -0
  408. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/metrics/prometheus_metrics.py +0 -0
  409. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/metrics_core.py +0 -0
  410. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/structured_logging.py +0 -0
  411. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/observability/tracing.py +0 -0
  412. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/optimizations/__init__.py +0 -0
  413. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/optimizations/generators.py +0 -0
  414. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/optimizations/memory_slots.py +0 -0
  415. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/optimizations/simhash_numba.py +0 -0
  416. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/py.typed +0 -0
  417. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/__init__.py +0 -0
  418. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/constants.py +0 -0
  419. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/dto/__init__.py +0 -0
  420. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/dto/edge_dto.py +0 -0
  421. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/dto/graph_dto.py +0 -0
  422. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/dto/node_dto.py +0 -0
  423. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/error_handling/__init__.py +0 -0
  424. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/error_handling/error_handler.py +0 -0
  425. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/exceptions.py +0 -0
  426. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/platform/__init__.py +0 -0
  427. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/platform/adaptive_resolver.py +0 -0
  428. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/platform/config.py +0 -0
  429. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/platform/detector.py +0 -0
  430. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/platform/diagnostics.py +0 -0
  431. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/platform/metrics.py +0 -0
  432. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/security/__init__.py +0 -0
  433. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/security/url_sanitizer.py +0 -0
  434. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/security/url_validator.py +0 -0
  435. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/__init__.py +0 -0
  436. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/async_compat.py +0 -0
  437. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/best_effort.py +0 -0
  438. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/bloom_filter.py +0 -0
  439. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/captcha/__init__.py +0 -0
  440. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/captcha/base.py +0 -0
  441. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/captcha/manager.py +0 -0
  442. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/celery_config.py +0 -0
  443. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/celery_helpers.py +0 -0
  444. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/cgroup_limits.py +0 -0
  445. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/content_decoder.py +0 -0
  446. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/distributed_rate_limiter.py +0 -0
  447. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/distributed_rate_limiter_backends.py +0 -0
  448. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/dns_cache.py +0 -0
  449. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/enterprise_ua_generator.py +0 -0
  450. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/event_publisher_mixin.py +0 -0
  451. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/fast_json.py +0 -0
  452. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/fingerprint.py +0 -0
  453. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/fingerprint_data.py +0 -0
  454. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/fingerprint_generators.py +0 -0
  455. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/fingerprint_profile.py +0 -0
  456. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/fingerprint_stealth.py +0 -0
  457. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/html_utils.py +0 -0
  458. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/id_generator.py +0 -0
  459. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/isolated_executor.py +0 -0
  460. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/memory_optimizer.py +0 -0
  461. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/rate_limiter.py +0 -0
  462. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/regex_validator.py +0 -0
  463. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/shadow_dom.py +0 -0
  464. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/text_utils.py +0 -0
  465. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/url_patterns.py +0 -0
  466. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/url_utils.py +0 -0
  467. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/user_agent_rotator.py +0 -0
  468. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/validation_helpers.py +0 -0
  469. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/visualization.py +0 -0
  470. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/shared/utils/visualization_core.py +0 -0
  471. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/tools/__init__.py +0 -0
  472. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler/tools/db_cleanup.py +0 -0
  473. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler.egg-info/SOURCES.txt +0 -0
  474. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler.egg-info/dependency_links.txt +0 -0
  475. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler.egg-info/entry_points.txt +0 -0
  476. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler.egg-info/requires.txt +0 -0
  477. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/graph_crawler.egg-info/top_level.txt +0 -0
  478. {graph_crawler-5.4.1 → graph_crawler-5.4.2}/setup.cfg +0 -0
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.4.2 — 2026-10
4
+
5
+ ### Fixed
6
+
7
+ - **A frozen browser tab no longer holds the crawl until the crawl-level timeout** (Parser `WEB_GRAF_ISSUES.md` §6,
8
+ NEW-3-5). After `fetch_timeout` the playwright fetcher asked the tab for partial HTML (`page.content()`) and
9
+ provenance (`page.evaluate`) without any bound; a tab whose renderer is busy with page script never answers, so the
10
+ worker hung (`Crawl-level timeout … in_flight=1`, ~12 min idle per scan in production). Recovery is now bounded by
11
+ `TIMEOUT_RECOVERY_S` (5 s); an unresponsive tab is logged (`Browser tab frozen after fetch timeout`) and closed, the
12
+ pool opens a fresh one. A slow but alive tab still returns partial HTML as before.
13
+ - **Worker-pool stall watchdog for browser drivers.** If no node finishes for
14
+ `2 × driver.fetch_timeout + node_enrich_timeout + 10 s`, stuck workers are cancelled (`Worker pool stalled`) and the
15
+ pool stops; with a non-empty frontier the stop is recorded as `stalled`. HTTP drivers are not affected.
16
+ - Tests: `tests/test_new_3_5_stalled_browser_pool.py` (real Chromium part: `GC_TEST_REAL_BROWSER=1`).
17
+
3
18
  ## 5.4.1 — 2026-10
4
19
 
5
20
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graph-crawler
3
- Version: 5.4.1
3
+ Version: 5.4.2
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
@@ -42,6 +42,7 @@ from graph_crawler.shared.constants import (
42
42
  BROWSER_PAGE_RAM_ESTIMATE_MB,
43
43
  BROWSER_RAM_BASELINE_MB,
44
44
  DEFAULT_BATCH_SIZE,
45
+ DEFAULT_NODE_ENRICH_TIMEOUT,
45
46
  MAX_PAGES_LIMIT,
46
47
  )
47
48
  from graph_crawler.shared.utils.cgroup_limits import (
@@ -85,6 +86,12 @@ logger = logging.getLogger(__name__)
85
86
  # Скільки секунд після crawl-level timeout даємо in-flight нодам дозавершитись.
86
87
  TIMEOUT_DRAIN_GRACE_S = 1.0
87
88
 
89
+ # NEW-3-5: браузерний пул «завис», якщо жодна нода не завершилась за
90
+ # 2 × fetch_timeout + enrich-таймаут + запас (найдовший законний шлях однієї ноди).
91
+ STALL_FETCH_TIMEOUT_MULTIPLIER = 2.0
92
+ STALL_GRACE_S = 10.0
93
+ STALL_CHECK_INTERVAL_S = 5.0
94
+
88
95
 
89
96
  def _stats_for_log(stats: dict) -> dict:
90
97
  """Копія stats для логу без шумового ``redirect_deduped_scanned=0``.
@@ -425,6 +432,14 @@ class CrawlCoordinator:
425
432
  asyncio.create_task(self._worker_loop(i, state), name=f"crawl-worker-{i}")
426
433
  for i in range(pool_size)
427
434
  ]
435
+ stall_timeout = self._resolve_stall_timeout()
436
+ watchdog = (
437
+ asyncio.create_task(
438
+ self._stall_watchdog(workers, state, stall_timeout), name="crawl-stall-watchdog"
439
+ )
440
+ if stall_timeout
441
+ else None
442
+ )
428
443
  try:
429
444
  await self._await_workers(workers, state)
430
445
  except asyncio.CancelledError:
@@ -436,6 +451,9 @@ class CrawlCoordinator:
436
451
  await asyncio.gather(*workers, return_exceptions=True)
437
452
  raise
438
453
  finally:
454
+ if watchdog is not None:
455
+ watchdog.cancel()
456
+ await asyncio.gather(watchdog, return_exceptions=True)
439
457
  self._pool_state = None
440
458
 
441
459
  if state.stop_reason == "timeout":
@@ -444,6 +462,8 @@ class CrawlCoordinator:
444
462
  self.spider, "stop_reason", None
445
463
  ):
446
464
  self._record_stop(state.stop_reason, "limit")
465
+ if state.stop_reason == "stalled" and not self.scheduler.is_empty():
466
+ self._record_stop("stalled", "limit")
447
467
 
448
468
  logger.debug(
449
469
  "Worker pool finished: %d nodes processed, reason=%s, %.1fs",
@@ -457,7 +477,8 @@ class CrawlCoordinator:
457
477
  async def _await_workers(self, workers: list[asyncio.Task], state: PoolState) -> None:
458
478
  """Чекає воркерів; при crawl-level timeout скасовує in-flight (інакше ретраї тягнуть хвіст)."""
459
479
  if self.timeout is None or not self.start_time:
460
- await asyncio.gather(*workers)
480
+ done, _ = await asyncio.wait(workers)
481
+ self._raise_worker_errors(done)
461
482
  return
462
483
  deadline = self.start_time + self.timeout + TIMEOUT_DRAIN_GRACE_S
463
484
  done, pending = await asyncio.wait(workers, timeout=max(0.0, deadline - time.time()))
@@ -478,10 +499,53 @@ class CrawlCoordinator:
478
499
  for task in pending:
479
500
  task.cancel()
480
501
  await asyncio.gather(*pending, return_exceptions=True)
502
+ self._raise_worker_errors(done)
503
+
504
+ @staticmethod
505
+ def _raise_worker_errors(done: set[asyncio.Task]) -> None:
506
+ """Скасований сторожем простою воркер — не помилка; справжню помилку воркера прокидаємо."""
481
507
  for task in done:
482
508
  if not task.cancelled() and task.exception() is not None:
483
509
  raise task.exception()
484
510
 
511
+ def _resolve_stall_timeout(self) -> float | None:
512
+ """Поріг «пул завис» (NEW-3-5): лише для браузерних драйверів, що мають ``fetch_timeout``."""
513
+ if not _is_js_heavy_driver(self.driver):
514
+ return None
515
+ fetch_timeout = getattr(self.driver, "fetch_timeout", None)
516
+ if isinstance(fetch_timeout, bool) or not isinstance(fetch_timeout, (int, float)):
517
+ return None
518
+ if fetch_timeout <= 0:
519
+ return None
520
+ enrich_timeout = (
521
+ getattr(self.config, "node_enrich_timeout", None) or DEFAULT_NODE_ENRICH_TIMEOUT
522
+ )
523
+ return STALL_FETCH_TIMEOUT_MULTIPLIER * fetch_timeout + enrich_timeout + STALL_GRACE_S
524
+
525
+ async def _stall_watchdog(
526
+ self, workers: list[asyncio.Task], state: PoolState, stall_timeout: float
527
+ ) -> None:
528
+ """Жодна нода не завершилась за ``stall_timeout`` — вкладки зависли: скасовуємо їх, фаза завершується."""
529
+ interval = min(STALL_CHECK_INTERVAL_S, stall_timeout / 4)
530
+ while not state.stop:
531
+ await asyncio.sleep(interval)
532
+ idle = time.monotonic() - state.last_progress_at
533
+ if state.in_flight == 0 or idle < stall_timeout:
534
+ continue
535
+ logger.warning(
536
+ "Worker pool stalled: no page finished for %.0fs (limit %.0fs), in_flight=%d, "
537
+ "queue=%d, stuck=%s - cancelling stuck workers",
538
+ idle,
539
+ stall_timeout,
540
+ state.in_flight,
541
+ self.scheduler.size() if hasattr(self.scheduler, "size") else 0,
542
+ list(state.in_flight_urls)[:5],
543
+ )
544
+ state.request_stop("stalled")
545
+ for task in workers:
546
+ task.cancel()
547
+ return
548
+
485
549
  def _resolve_worker_pool_size(self) -> int:
486
550
  """N воркерів: override з конфігу → конкурентність драйвера → кап для JS-heavy.
487
551
 
@@ -15,6 +15,7 @@ from __future__ import annotations
15
15
 
16
16
  import asyncio
17
17
  import logging
18
+ import time
18
19
  from collections import defaultdict
19
20
  from urllib.parse import urlsplit
20
21
 
@@ -119,6 +120,8 @@ class PoolState:
119
120
  self.stop = False
120
121
  self.stop_reason: str | None = None
121
122
  self.wakeup = asyncio.Event()
123
+ # NEW-3-5: час останньої завершеної ноди — сторож простою пулу.
124
+ self.last_progress_at = time.monotonic()
122
125
 
123
126
  def request_stop(self, reason: str) -> None:
124
127
  if not self.stop:
@@ -141,6 +144,7 @@ class PoolState:
141
144
  """Повертає True, якщо eviction-watermark просунувся."""
142
145
  self.in_flight -= 1
143
146
  self.completed += 1
147
+ self.last_progress_at = time.monotonic()
144
148
  if node_id:
145
149
  self.in_flight_node_ids.discard(node_id)
146
150
  self.in_flight_urls.pop(url, None)
@@ -28,6 +28,7 @@ from graph_crawler.infrastructure.transport.core.redirect_utils import (
28
28
  )
29
29
  from graph_crawler.infrastructure.transport.playwright.context import BrowserContext
30
30
  from graph_crawler.infrastructure.transport.playwright.stages import BrowserStage
31
+ from graph_crawler.shared.utils.best_effort import close_quietly
31
32
  from graph_crawler.shared.utils.shadow_dom import expose_shadow_roots
32
33
 
33
34
  logger = logging.getLogger(__name__)
@@ -49,6 +50,11 @@ logger = logging.getLogger(__name__)
49
50
  # =============================================================================
50
51
  PARTIAL_TIMEOUT_STATUS = 504
51
52
 
53
+ # NEW-3-5: після fetch_timeout вкладку ще питають про частковий HTML і адресу; зависла вкладка
54
+ # (скрипт сторінки тримає її) не відповідає ніколи — без ліміту воркер чекав до кінця фази.
55
+ TIMEOUT_RECOVERY_S = 5.0
56
+ FROZEN_PAGE_CLOSE_S = 3.0
57
+
52
58
  # ``page.content()`` серіалізує лише light DOM: вміст відкритих shadow root'ів (веб-компоненти,
53
59
  # Lit/Stencil/Salesforce LWC, вбудовані віджети цін) у HTML не потрапляє, і парсер «не бачить»
54
60
  # того, що бачить користувач. Якщо на сторінці є відкриті shadow root'и, серіалізуємо DOM
@@ -228,12 +234,7 @@ class PageFetcher:
228
234
  timeout=float(self.fetch_timeout),
229
235
  )
230
236
  except asyncio.TimeoutError:
231
- # Спробувати отримати частковий HTML що вже завантажився
232
- partial_html = None
233
- async with protect.suppress(
234
- Exception, log=logger.debug, message="_fetch_with_page_impl"
235
- ):
236
- partial_html = await page.content()
237
+ partial_html, frozen = await self._partial_html_after_timeout(page, url)
237
238
  partial_html = self._drop_oversized_partial(partial_html, url)
238
239
 
239
240
  error_msg = f"Fetch timeout ({self.fetch_timeout}s) for {url}"
@@ -261,8 +262,18 @@ class PageFetcher:
261
262
  )
262
263
  # Навіть у fetch-timeout page.url може містити final URL.
263
264
  # Зберігаємо провенанс, щоб graph.handle_redirect() спрацював
264
- # для timeout-нод з JS-редіректом.
265
- ft_final, ft_chain, ft_hops = await self._build_playwright_provenance(url, page, None)
265
+ # для timeout-нод з JS-редіректом. Завислу вкладку не питаємо (NEW-3-5).
266
+ ft_final, ft_chain, ft_hops = url, [], []
267
+ if not frozen:
268
+ try:
269
+ ft_final, ft_chain, ft_hops = await asyncio.wait_for(
270
+ self._build_playwright_provenance(url, page, None),
271
+ timeout=TIMEOUT_RECOVERY_S,
272
+ )
273
+ except asyncio.TimeoutError:
274
+ frozen = True
275
+ if frozen:
276
+ await self._close_frozen_page(page, url)
266
277
  return self._finalize_response(
267
278
  FetchResponse(
268
279
  url=url,
@@ -282,6 +293,27 @@ class PageFetcher:
282
293
  logger.error(error_msg)
283
294
  return FetchResponse(url=url, html=None, status_code=None, headers={}, error=error_msg)
284
295
 
296
+ @staticmethod
297
+ async def _partial_html_after_timeout(page: Any, url: str) -> tuple[str | None, bool]:
298
+ """Частковий HTML після fetch_timeout і ознака «вкладка зависла» (не відповіла за ``TIMEOUT_RECOVERY_S``)."""
299
+ try:
300
+ return await asyncio.wait_for(page.content(), timeout=TIMEOUT_RECOVERY_S), False
301
+ except asyncio.TimeoutError:
302
+ logger.warning(
303
+ "Browser tab frozen after fetch timeout: page.content() did not answer in %.0fs for %s",
304
+ TIMEOUT_RECOVERY_S,
305
+ url,
306
+ )
307
+ return None, True
308
+ except Exception as e: # noqa: BLE001 — сторінка закрилась/переходить
309
+ logger.debug("partial content after timeout unavailable for %s: %s", url, e)
310
+ return None, False
311
+
312
+ @staticmethod
313
+ async def _close_frozen_page(page: Any, url: str) -> None:
314
+ """Закрита вкладка не повертається в пул — пул відкриє нову замість завислої."""
315
+ await close_quietly(page.close(), name=f"frozen page {url}", timeout=FROZEN_PAGE_CLOSE_S)
316
+
285
317
  @staticmethod
286
318
  def _log_fetch_timeout(
287
319
  error_msg: str,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graph-crawler
3
- Version: 5.4.1
3
+ Version: 5.4.2
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.4.1"
7
+ version = "5.4.2"
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