graph-crawler 5.3.7__tar.gz → 5.4.1__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 (955) hide show
  1. graph_crawler-5.4.1/CHANGELOG.md +637 -0
  2. graph_crawler-5.4.1/LICENSE +21 -0
  3. graph_crawler-5.4.1/MANIFEST.in +16 -0
  4. graph_crawler-5.4.1/PKG-INFO +523 -0
  5. graph_crawler-5.4.1/README.md +401 -0
  6. graph_crawler-5.4.1/graph_crawler/__init__.py +495 -0
  7. graph_crawler-5.4.1/graph_crawler/__version__.py +9 -0
  8. graph_crawler-5.4.1/graph_crawler/ai/__init__.py +50 -0
  9. graph_crawler-5.4.1/graph_crawler/api/__init__.py +28 -0
  10. graph_crawler-5.4.1/graph_crawler/api/_core.py +283 -0
  11. graph_crawler-5.4.1/graph_crawler/api/_distributed.py +172 -0
  12. graph_crawler-5.4.1/graph_crawler/api/_shared.py +110 -0
  13. graph_crawler-5.4.1/graph_crawler/api/_sitemap_distributed.py +237 -0
  14. graph_crawler-5.4.1/graph_crawler/api/async_.py +329 -0
  15. graph_crawler-5.4.1/graph_crawler/api/cli.py +440 -0
  16. graph_crawler-5.4.1/graph_crawler/api/client/__init__.py +8 -0
  17. graph_crawler-5.4.1/graph_crawler/api/client/client.py +479 -0
  18. graph_crawler-5.4.1/graph_crawler/api/history_manager.py +55 -0
  19. graph_crawler-5.4.1/graph_crawler/api/project_init.py +196 -0
  20. graph_crawler-5.4.1/graph_crawler/api/server/__init__.py +18 -0
  21. graph_crawler-5.4.1/graph_crawler/api/server/crawl_monitor.py +145 -0
  22. graph_crawler-5.4.1/graph_crawler/api/server/dashboard.py +219 -0
  23. graph_crawler-5.4.1/graph_crawler/api/server/rest_api.py +704 -0
  24. graph_crawler-5.4.1/graph_crawler/api/server/webhooks.py +434 -0
  25. graph_crawler-5.4.1/graph_crawler/api/server/websocket_manager.py +89 -0
  26. graph_crawler-5.4.1/graph_crawler/api/stats_collector.py +88 -0
  27. graph_crawler-5.4.1/graph_crawler/api/sync.py +550 -0
  28. graph_crawler-5.4.1/graph_crawler/application/__init__.py +38 -0
  29. graph_crawler-5.4.1/graph_crawler/application/bootstrap.py +215 -0
  30. graph_crawler-5.4.1/graph_crawler/application/context/__init__.py +31 -0
  31. graph_crawler-5.4.1/graph_crawler/application/context/dependency_registry.py +354 -0
  32. graph_crawler-5.4.1/graph_crawler/application/context/graph_context.py +217 -0
  33. graph_crawler-5.4.1/graph_crawler/application/context/merge_context.py +221 -0
  34. graph_crawler-5.4.1/graph_crawler/application/dto/__init__.py +67 -0
  35. graph_crawler-5.4.1/graph_crawler/application/dto/edge_dto.py +25 -0
  36. graph_crawler-5.4.1/graph_crawler/application/dto/graph_dto.py +27 -0
  37. graph_crawler-5.4.1/graph_crawler/application/dto/mappers/__init__.py +14 -0
  38. graph_crawler-5.4.1/graph_crawler/application/dto/mappers/edge_mapper.py +149 -0
  39. graph_crawler-5.4.1/graph_crawler/application/dto/mappers/graph_mapper.py +606 -0
  40. graph_crawler-5.4.1/graph_crawler/application/dto/mappers/node_data_codec.py +190 -0
  41. graph_crawler-5.4.1/graph_crawler/application/dto/mappers/node_mapper.py +421 -0
  42. graph_crawler-5.4.1/graph_crawler/application/dto/node_dto.py +27 -0
  43. graph_crawler-5.4.1/graph_crawler/application/dto/utils.py +272 -0
  44. graph_crawler-5.4.1/graph_crawler/application/services/__init__.py +60 -0
  45. graph_crawler-5.4.1/graph_crawler/application/services/application_container.py +199 -0
  46. graph_crawler-5.4.1/graph_crawler/application/services/driver_factory.py +147 -0
  47. graph_crawler-5.4.1/graph_crawler/application/services/exporters/__init__.py +26 -0
  48. graph_crawler-5.4.1/graph_crawler/application/services/exporters/base_exporter.py +150 -0
  49. graph_crawler-5.4.1/graph_crawler/application/services/exporters/csv_exporter.py +318 -0
  50. graph_crawler-5.4.1/graph_crawler/application/services/exporters/edge_exporter.py +432 -0
  51. graph_crawler-5.4.1/graph_crawler/application/services/exporters/excel_exporter.py +297 -0
  52. graph_crawler-5.4.1/graph_crawler/application/services/exporters/jsonl_exporter.py +291 -0
  53. graph_crawler-5.4.1/graph_crawler/application/services/exporters/node_exporter.py +284 -0
  54. graph_crawler-5.4.1/graph_crawler/application/services/exporters/parquet_exporter.py +248 -0
  55. graph_crawler-5.4.1/graph_crawler/application/services/exporters/sql_exporter.py +267 -0
  56. graph_crawler-5.4.1/graph_crawler/application/services/finalize.py +115 -0
  57. graph_crawler-5.4.1/graph_crawler/application/services/low_memory_graph_saver.py +341 -0
  58. graph_crawler-5.4.1/graph_crawler/application/services/resource_budget.py +179 -0
  59. graph_crawler-5.4.1/graph_crawler/application/services/scheduling/__init__.py +12 -0
  60. graph_crawler-5.4.1/graph_crawler/application/services/scheduling/rule_based_scheduler.py +105 -0
  61. graph_crawler-5.4.1/graph_crawler/application/services/storage_factory.py +195 -0
  62. graph_crawler-5.4.1/graph_crawler/application/use_cases/__init__.py +11 -0
  63. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/__init__.py +81 -0
  64. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/adaptive_throttler.py +369 -0
  65. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/async_bridge.py +45 -0
  66. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/base_spider.py +155 -0
  67. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/celery_batch_spider.py +645 -0
  68. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/celery_spider.py +446 -0
  69. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/checkpoint.py +426 -0
  70. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/crawl_coordinator.py +1068 -0
  71. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/dead_letter_queue.py +413 -0
  72. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/domain_rate_limiter.py +262 -0
  73. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/filters/__init__.py +10 -0
  74. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/filters/base.py +74 -0
  75. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/filters/domain_filter.py +228 -0
  76. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/filters/domain_patterns.py +30 -0
  77. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/filters/path_filter.py +95 -0
  78. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/incremental_strategy.py +239 -0
  79. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/link_processor.py +884 -0
  80. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/multiprocess_spider.py +505 -0
  81. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/node_scanner.py +873 -0
  82. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/parsers/__init__.py +6 -0
  83. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/parsers/base.py +73 -0
  84. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/parsers/html_parser.py +154 -0
  85. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/progress_tracker.py +235 -0
  86. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/scheduler.py +760 -0
  87. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/serialization_mixin.py +218 -0
  88. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/sitemap_parser.py +1092 -0
  89. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/sitemap_processor.py +405 -0
  90. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/sitemap_spider.py +665 -0
  91. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/spider.py +873 -0
  92. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/spider_lifecycle.py +108 -0
  93. graph_crawler-5.4.1/graph_crawler/application/use_cases/crawling/worker_pool.py +154 -0
  94. graph_crawler-5.4.1/graph_crawler/application/use_cases/graph_export.py +504 -0
  95. graph_crawler-5.4.1/graph_crawler/application/use_cases/graph_merge/__init__.py +33 -0
  96. graph_crawler-5.4.1/graph_crawler/application/use_cases/graph_merge/graph_operations.py +628 -0
  97. graph_crawler-5.4.1/graph_crawler/application/use_cases/graph_merge/merge_strategies.py +347 -0
  98. graph_crawler-5.4.1/graph_crawler/data/__init__.py +28 -0
  99. graph_crawler-5.4.1/graph_crawler/data/backends/__init__.py +21 -0
  100. graph_crawler-5.4.1/graph_crawler/data/backends/memory.py +486 -0
  101. graph_crawler-5.4.1/graph_crawler/data/backends/sqlite.py +1900 -0
  102. graph_crawler-5.4.1/graph_crawler/data/interfaces.py +563 -0
  103. graph_crawler-5.4.1/graph_crawler/domain/__init__.py +62 -0
  104. graph_crawler-5.4.1/graph_crawler/domain/context/__init__.py +14 -0
  105. graph_crawler-5.4.1/graph_crawler/domain/context/crawl_context.py +531 -0
  106. graph_crawler-5.4.1/graph_crawler/domain/entities/__init__.py +32 -0
  107. graph_crawler-5.4.1/graph_crawler/domain/entities/edge.py +345 -0
  108. graph_crawler-5.4.1/graph_crawler/domain/entities/edge_analysis.py +223 -0
  109. graph_crawler-5.4.1/graph_crawler/domain/entities/graph.py +4276 -0
  110. graph_crawler-5.4.1/graph_crawler/domain/entities/graph_operations.py +30 -0
  111. graph_crawler-5.4.1/graph_crawler/domain/entities/graph_statistics.py +10 -0
  112. graph_crawler-5.4.1/graph_crawler/domain/entities/merge_strategies.py +33 -0
  113. graph_crawler-5.4.1/graph_crawler/domain/entities/node.py +966 -0
  114. graph_crawler-5.4.1/graph_crawler/domain/entities/redirect_mode.py +23 -0
  115. graph_crawler-5.4.1/graph_crawler/domain/entities/registries.py +223 -0
  116. graph_crawler-5.4.1/graph_crawler/domain/entities/sitemap_node.py +77 -0
  117. graph_crawler-5.4.1/graph_crawler/domain/entities/strategies.py +202 -0
  118. graph_crawler-5.4.1/graph_crawler/domain/events/__init__.py +153 -0
  119. graph_crawler-5.4.1/graph_crawler/domain/events/base.py +60 -0
  120. graph_crawler-5.4.1/graph_crawler/domain/events/crawl.py +112 -0
  121. graph_crawler-5.4.1/graph_crawler/domain/events/edge.py +43 -0
  122. graph_crawler-5.4.1/graph_crawler/domain/events/event_bus.py +250 -0
  123. graph_crawler-5.4.1/graph_crawler/domain/events/events.py +313 -0
  124. graph_crawler-5.4.1/graph_crawler/domain/events/fetch.py +120 -0
  125. graph_crawler-5.4.1/graph_crawler/domain/events/idempotent_subscriber.py +120 -0
  126. graph_crawler-5.4.1/graph_crawler/domain/events/legacy_type_map.py +45 -0
  127. graph_crawler-5.4.1/graph_crawler/domain/events/llm.py +149 -0
  128. graph_crawler-5.4.1/graph_crawler/domain/events/node.py +111 -0
  129. graph_crawler-5.4.1/graph_crawler/domain/events/plugin.py +92 -0
  130. graph_crawler-5.4.1/graph_crawler/domain/events/scheduler.py +98 -0
  131. graph_crawler-5.4.1/graph_crawler/domain/events/storage.py +161 -0
  132. graph_crawler-5.4.1/graph_crawler/domain/interfaces/__init__.py +113 -0
  133. graph_crawler-5.4.1/graph_crawler/domain/interfaces/adapter.py +29 -0
  134. graph_crawler-5.4.1/graph_crawler/domain/interfaces/control_channel.py +381 -0
  135. graph_crawler-5.4.1/graph_crawler/domain/interfaces/crawl_context.py +49 -0
  136. graph_crawler-5.4.1/graph_crawler/domain/interfaces/distributed_spider.py +62 -0
  137. graph_crawler-5.4.1/graph_crawler/domain/interfaces/driver.py +86 -0
  138. graph_crawler-5.4.1/graph_crawler/domain/interfaces/event_bus.py +61 -0
  139. graph_crawler-5.4.1/graph_crawler/domain/interfaces/exporters.py +69 -0
  140. graph_crawler-5.4.1/graph_crawler/domain/interfaces/filter.py +19 -0
  141. graph_crawler-5.4.1/graph_crawler/domain/interfaces/merge_context.py +52 -0
  142. graph_crawler-5.4.1/graph_crawler/domain/interfaces/node_interfaces.py +388 -0
  143. graph_crawler-5.4.1/graph_crawler/domain/interfaces/parser.py +88 -0
  144. graph_crawler-5.4.1/graph_crawler/domain/interfaces/plugin_manager.py +53 -0
  145. graph_crawler-5.4.1/graph_crawler/domain/interfaces/processor.py +11 -0
  146. graph_crawler-5.4.1/graph_crawler/domain/interfaces/scanner.py +57 -0
  147. graph_crawler-5.4.1/graph_crawler/domain/interfaces/scheduler.py +43 -0
  148. graph_crawler-5.4.1/graph_crawler/domain/interfaces/scheduling/__init__.py +40 -0
  149. graph_crawler-5.4.1/graph_crawler/domain/interfaces/scheduling/frontier.py +97 -0
  150. graph_crawler-5.4.1/graph_crawler/domain/interfaces/scheduling/scheduler.py +107 -0
  151. graph_crawler-5.4.1/graph_crawler/domain/interfaces/spider.py +63 -0
  152. graph_crawler-5.4.1/graph_crawler/domain/interfaces/stop_condition.py +211 -0
  153. graph_crawler-5.4.1/graph_crawler/domain/interfaces/storage/__init__.py +43 -0
  154. graph_crawler-5.4.1/graph_crawler/domain/interfaces/storage/base.py +66 -0
  155. graph_crawler-5.4.1/graph_crawler/domain/interfaces/storage/eviction.py +98 -0
  156. graph_crawler-5.4.1/graph_crawler/domain/interfaces/storage/queue.py +234 -0
  157. graph_crawler-5.4.1/graph_crawler/domain/services/__init__.py +216 -0
  158. graph_crawler-5.4.1/graph_crawler/domain/services/adjacency_maintenance.py +95 -0
  159. graph_crawler-5.4.1/graph_crawler/domain/services/cycle_detection.py +92 -0
  160. graph_crawler-5.4.1/graph_crawler/domain/services/degree_ranking.py +47 -0
  161. graph_crawler-5.4.1/graph_crawler/domain/services/depth_context.py +123 -0
  162. graph_crawler-5.4.1/graph_crawler/domain/services/display_title.py +47 -0
  163. graph_crawler-5.4.1/graph_crawler/domain/services/duplicate_detection.py +141 -0
  164. graph_crawler-5.4.1/graph_crawler/domain/services/duplicate_selector.py +127 -0
  165. graph_crawler-5.4.1/graph_crawler/domain/services/duplicate_stats.py +63 -0
  166. graph_crawler-5.4.1/graph_crawler/domain/services/edge_filter.py +82 -0
  167. graph_crawler-5.4.1/graph_crawler/domain/services/edge_statistics.py +110 -0
  168. graph_crawler-5.4.1/graph_crawler/domain/services/eviction_policy.py +83 -0
  169. graph_crawler-5.4.1/graph_crawler/domain/services/graph_query_cascade.py +178 -0
  170. graph_crawler-5.4.1/graph_crawler/domain/services/graph_statistics.py +186 -0
  171. graph_crawler-5.4.1/graph_crawler/domain/services/graph_traversal.py +150 -0
  172. graph_crawler-5.4.1/graph_crawler/domain/services/language_versions.py +73 -0
  173. graph_crawler-5.4.1/graph_crawler/domain/services/lsh_index.py +99 -0
  174. graph_crawler-5.4.1/graph_crawler/domain/services/minimap_builder.py +147 -0
  175. graph_crawler-5.4.1/graph_crawler/domain/services/node_content_resolver.py +128 -0
  176. graph_crawler-5.4.1/graph_crawler/domain/services/node_view_builder.py +163 -0
  177. graph_crawler-5.4.1/graph_crawler/domain/services/pagination_guards.py +136 -0
  178. graph_crawler-5.4.1/graph_crawler/domain/services/predicate_ops.py +77 -0
  179. graph_crawler-5.4.1/graph_crawler/domain/services/schema_filter.py +138 -0
  180. graph_crawler-5.4.1/graph_crawler/domain/services/search_haystack.py +102 -0
  181. graph_crawler-5.4.1/graph_crawler/domain/services/simhash_clustering.py +96 -0
  182. graph_crawler-5.4.1/graph_crawler/domain/services/simhash_fallback.py +59 -0
  183. graph_crawler-5.4.1/graph_crawler/domain/services/similarity_ranking.py +62 -0
  184. graph_crawler-5.4.1/graph_crawler/domain/services/text_search.py +157 -0
  185. graph_crawler-5.4.1/graph_crawler/domain/services/url_clustering.py +88 -0
  186. graph_crawler-5.4.1/graph_crawler/domain/services/url_deduplication.py +101 -0
  187. graph_crawler-5.4.1/graph_crawler/domain/services/url_normalizer.py +117 -0
  188. graph_crawler-5.4.1/graph_crawler/domain/services/url_rule_matcher.py +111 -0
  189. graph_crawler-5.4.1/graph_crawler/domain/services/url_tree_builder.py +197 -0
  190. graph_crawler-5.4.1/graph_crawler/domain/value_objects/__init__.py +88 -0
  191. graph_crawler-5.4.1/graph_crawler/domain/value_objects/configs.py +1079 -0
  192. graph_crawler-5.4.1/graph_crawler/domain/value_objects/domain_patterns.py +71 -0
  193. graph_crawler-5.4.1/graph_crawler/domain/value_objects/fetch_options.py +57 -0
  194. graph_crawler-5.4.1/graph_crawler/domain/value_objects/html_document.py +59 -0
  195. graph_crawler-5.4.1/graph_crawler/domain/value_objects/lifecycle.py +30 -0
  196. graph_crawler-5.4.1/graph_crawler/domain/value_objects/models.py +1315 -0
  197. graph_crawler-5.4.1/graph_crawler/domain/value_objects/page_actions.py +76 -0
  198. graph_crawler-5.4.1/graph_crawler/domain/value_objects/redirect_hop.py +49 -0
  199. graph_crawler-5.4.1/graph_crawler/domain/value_objects/scheduling/__init__.py +23 -0
  200. graph_crawler-5.4.1/graph_crawler/domain/value_objects/scheduling/crawl_request.py +153 -0
  201. graph_crawler-5.4.1/graph_crawler/domain/value_objects/scheduling/retry_policy.py +109 -0
  202. graph_crawler-5.4.1/graph_crawler/domain/value_objects/settings.py +350 -0
  203. graph_crawler-5.4.1/graph_crawler/domain/value_objects/url_normalization.py +497 -0
  204. graph_crawler-5.4.1/graph_crawler/domain/value_objects/views.py +293 -0
  205. graph_crawler-5.4.1/graph_crawler/extensions/__init__.py +1 -0
  206. graph_crawler-5.4.1/graph_crawler/extensions/middleware/__init__.py +65 -0
  207. graph_crawler-5.4.1/graph_crawler/extensions/middleware/base.py +138 -0
  208. graph_crawler-5.4.1/graph_crawler/extensions/middleware/cache_middleware.py +279 -0
  209. graph_crawler-5.4.1/graph_crawler/extensions/middleware/chain.py +158 -0
  210. graph_crawler-5.4.1/graph_crawler/extensions/middleware/error_recovery_middleware.py +442 -0
  211. graph_crawler-5.4.1/graph_crawler/extensions/middleware/logging_middleware.py +108 -0
  212. graph_crawler-5.4.1/graph_crawler/extensions/middleware/proxy_health.py +137 -0
  213. graph_crawler-5.4.1/graph_crawler/extensions/middleware/proxy_middleware.py +209 -0
  214. graph_crawler-5.4.1/graph_crawler/extensions/middleware/proxy_models.py +80 -0
  215. graph_crawler-5.4.1/graph_crawler/extensions/middleware/proxy_selection.py +87 -0
  216. graph_crawler-5.4.1/graph_crawler/extensions/middleware/rate_limit_middleware.py +262 -0
  217. graph_crawler-5.4.1/graph_crawler/extensions/middleware/request_response_middleware.py +478 -0
  218. graph_crawler-5.4.1/graph_crawler/extensions/middleware/retry_middleware.py +279 -0
  219. graph_crawler-5.4.1/graph_crawler/extensions/middleware/robots_cache.py +85 -0
  220. graph_crawler-5.4.1/graph_crawler/extensions/middleware/robots_middleware.py +110 -0
  221. graph_crawler-5.4.1/graph_crawler/extensions/middleware/robots_validator.py +60 -0
  222. graph_crawler-5.4.1/graph_crawler/extensions/middleware/user_agent_middleware.py +251 -0
  223. graph_crawler-5.4.1/graph_crawler/extensions/plugins/__init__.py +57 -0
  224. graph_crawler-5.4.1/graph_crawler/extensions/plugins/builtin/__init__.py +7 -0
  225. graph_crawler-5.4.1/graph_crawler/extensions/plugins/builtin/stats_export_plugin.py +99 -0
  226. graph_crawler-5.4.1/graph_crawler/extensions/plugins/crawl_engine/__init__.py +24 -0
  227. graph_crawler-5.4.1/graph_crawler/extensions/plugins/crawl_engine/base.py +155 -0
  228. graph_crawler-5.4.1/graph_crawler/extensions/plugins/crawl_engine/priority_provider.py +249 -0
  229. graph_crawler-5.4.1/graph_crawler/extensions/plugins/crawl_engine/smart_crawl.py +295 -0
  230. graph_crawler-5.4.1/graph_crawler/extensions/plugins/engine/__init__.py +128 -0
  231. graph_crawler-5.4.1/graph_crawler/extensions/plugins/engine/anti_bot_detection.py +55 -0
  232. graph_crawler-5.4.1/graph_crawler/extensions/plugins/engine/anti_bot_playwright.py +88 -0
  233. graph_crawler-5.4.1/graph_crawler/extensions/plugins/engine/anti_bot_scripts.py +132 -0
  234. graph_crawler-5.4.1/graph_crawler/extensions/plugins/engine/anti_bot_stealth.py +177 -0
  235. graph_crawler-5.4.1/graph_crawler/extensions/plugins/engine/base.py +121 -0
  236. graph_crawler-5.4.1/graph_crawler/extensions/plugins/engine/captcha/__init__.py +22 -0
  237. graph_crawler-5.4.1/graph_crawler/extensions/plugins/engine/captcha/detector.py +94 -0
  238. graph_crawler-5.4.1/graph_crawler/extensions/plugins/engine/captcha/models.py +74 -0
  239. graph_crawler-5.4.1/graph_crawler/extensions/plugins/engine/captcha/plugin.py +230 -0
  240. graph_crawler-5.4.1/graph_crawler/extensions/plugins/engine/captcha/services.py +389 -0
  241. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/__init__.py +86 -0
  242. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/base.py +525 -0
  243. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/content_extractors/__init__.py +15 -0
  244. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/content_extractors/base.py +82 -0
  245. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/content_extractors/goose3_extractor.py +81 -0
  246. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/content_extractors/newspaper_extractor.py +92 -0
  247. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/content_extractors/plugin.py +135 -0
  248. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/content_extractors/readability_extractor.py +92 -0
  249. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/defaults.py +32 -0
  250. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/extractors/__init__.py +30 -0
  251. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/extractors/email_extractor.py +148 -0
  252. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/extractors/phone_extractor.py +136 -0
  253. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/extractors/price_extractor.py +137 -0
  254. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/links.py +184 -0
  255. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/markdown_plugin.py +250 -0
  256. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/metadata.py +377 -0
  257. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/smart_page_finder.py +752 -0
  258. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/structured_data/__init__.py +51 -0
  259. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/structured_data/constants.py +34 -0
  260. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/structured_data/exceptions.py +18 -0
  261. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/structured_data/extractor.py +375 -0
  262. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/structured_data/options.py +83 -0
  263. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/structured_data/parsers/__init__.py +25 -0
  264. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/structured_data/parsers/base.py +43 -0
  265. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/structured_data/parsers/jsonld.py +404 -0
  266. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/structured_data/parsers/microdata.py +115 -0
  267. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/structured_data/parsers/opengraph.py +65 -0
  268. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/structured_data/parsers/rdfa.py +120 -0
  269. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/structured_data/parsers/twitter.py +65 -0
  270. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/structured_data/plugin.py +132 -0
  271. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/structured_data/result.py +233 -0
  272. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/text.py +77 -0
  273. graph_crawler-5.4.1/graph_crawler/extensions/plugins/node/url_normalizer.py +363 -0
  274. graph_crawler-5.4.1/graph_crawler/extensions/plugins/registry.py +97 -0
  275. graph_crawler-5.4.1/graph_crawler/infrastructure/__init__.py +1 -0
  276. graph_crawler-5.4.1/graph_crawler/infrastructure/adapters/__init__.py +60 -0
  277. graph_crawler-5.4.1/graph_crawler/infrastructure/adapters/base.py +498 -0
  278. graph_crawler-5.4.1/graph_crawler/infrastructure/adapters/selectolax_adapter.py +233 -0
  279. graph_crawler-5.4.1/graph_crawler/infrastructure/messaging/__init__.py +37 -0
  280. graph_crawler-5.4.1/graph_crawler/infrastructure/messaging/celery_app.py +202 -0
  281. graph_crawler-5.4.1/graph_crawler/infrastructure/messaging/celery_batch.py +284 -0
  282. graph_crawler-5.4.1/graph_crawler/infrastructure/messaging/celery_job_task.py +414 -0
  283. graph_crawler-5.4.1/graph_crawler/infrastructure/messaging/celery_unified.py +419 -0
  284. graph_crawler-5.4.1/graph_crawler/infrastructure/messaging/config.py +201 -0
  285. graph_crawler-5.4.1/graph_crawler/infrastructure/messaging/easy_crawler.py +279 -0
  286. graph_crawler-5.4.1/graph_crawler/infrastructure/messaging/worker_api.py +609 -0
  287. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/__init__.py +135 -0
  288. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/auto_storage.py +336 -0
  289. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/base.py +111 -0
  290. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/eviction.py +100 -0
  291. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/graph_repository.py +398 -0
  292. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/json/__init__.py +10 -0
  293. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/json_storage.py +373 -0
  294. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/lmdb_eviction_storage.py +416 -0
  295. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/memory/__init__.py +1 -0
  296. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/memory_dedup_store.py +90 -0
  297. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/memory_frontier.py +180 -0
  298. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/memory_politeness_store.py +88 -0
  299. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/memory_storage.py +166 -0
  300. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/mongodb_storage.py +382 -0
  301. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/naming_strategy.py +174 -0
  302. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/postgresql_storage.py +450 -0
  303. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/protocols/__init__.py +1 -0
  304. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/redis_dedup_store.py +119 -0
  305. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/redis_politeness_store.py +122 -0
  306. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/repository.py +130 -0
  307. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/sqlite/__init__.py +10 -0
  308. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/sqlite_eviction_storage.py +905 -0
  309. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/sqlite_storage.py +767 -0
  310. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/unified/__init__.py +48 -0
  311. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/unified/file_job_storage.py +245 -0
  312. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/unified/file_queue_storage.py +247 -0
  313. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/unified/memory_job_storage.py +94 -0
  314. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/unified/memory_queue_storage.py +117 -0
  315. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/unified/postgresql_job_storage.py +317 -0
  316. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/unified/postgresql_queue_storage.py +366 -0
  317. graph_crawler-5.4.1/graph_crawler/infrastructure/persistence/unified/unified_storage.py +244 -0
  318. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/__init__.py +135 -0
  319. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/async_http/__init__.py +38 -0
  320. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/async_http/config.py +57 -0
  321. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/async_http/context.py +55 -0
  322. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/async_http/driver.py +792 -0
  323. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/async_http/driver_curl_cffi.py +437 -0
  324. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/async_http/plugins/__init__.py +48 -0
  325. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle.py +484 -0
  326. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle_adaptive.py +1430 -0
  327. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/async_http/plugins/headers.py +69 -0
  328. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/async_http/plugins/http_cache.py +321 -0
  329. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/async_http/plugins/rate_limiter.py +96 -0
  330. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/async_http/plugins/retry.py +315 -0
  331. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/async_http/stages.py +33 -0
  332. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/base.py +344 -0
  333. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/base_plugin.py +151 -0
  334. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/compression/__init__.py +23 -0
  335. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/compression/strategy.py +220 -0
  336. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/connection_pool.py +373 -0
  337. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/context.py +114 -0
  338. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/core/__init__.py +15 -0
  339. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/core/base_async.py +370 -0
  340. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/core/body_decoder.py +28 -0
  341. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/core/body_limit.py +223 -0
  342. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/core/mixins.py +258 -0
  343. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/core/redirect_utils.py +169 -0
  344. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/escalation.py +1177 -0
  345. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/factory.py +211 -0
  346. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/__init__.py +19 -0
  347. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/adaptive_semaphore.py +65 -0
  348. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/batch_scheduler.py +772 -0
  349. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/browser_factory.py +603 -0
  350. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/browser_profile.py +716 -0
  351. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/config.py +93 -0
  352. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/context.py +46 -0
  353. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/driver.py +78 -0
  354. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/engine.py +480 -0
  355. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/fetch_metrics.py +115 -0
  356. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/net/__init__.py +41 -0
  357. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/net/geoip.py +136 -0
  358. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/net/proxy_pool.py +223 -0
  359. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/net/rate_limiter.py +135 -0
  360. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/page_actions.py +217 -0
  361. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/page_fetch.py +1054 -0
  362. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/plugins/__init__.py +71 -0
  363. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/plugins/_network_record.py +66 -0
  364. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/plugins/captcha_detector.py +297 -0
  365. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/plugins/captcha_solver.py +107 -0
  366. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/plugins/cookie_consent.py +547 -0
  367. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/plugins/form_filler.py +179 -0
  368. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/plugins/human_bezier.py +290 -0
  369. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/plugins/network_log.py +418 -0
  370. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/plugins/ram_adaptive.py +208 -0
  371. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/plugins/screenshot.py +171 -0
  372. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/plugins/user_profile.py +546 -0
  373. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/pool_lifecycle.py +659 -0
  374. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/pooled_driver.py +702 -0
  375. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/stages.py +35 -0
  376. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/playwright/stealth_hints.py +154 -0
  377. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/plugin_manager.py +317 -0
  378. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/protocols.py +132 -0
  379. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/undetected/__init__.py +8 -0
  380. graph_crawler-5.4.1/graph_crawler/infrastructure/transport/undetected/async_driver.py +479 -0
  381. graph_crawler-5.4.1/graph_crawler/native/__init__.py +199 -0
  382. graph_crawler-5.4.1/graph_crawler/native/bloom_filter.pyx +255 -0
  383. graph_crawler-5.4.1/graph_crawler/native/html_parser.pyx +262 -0
  384. graph_crawler-5.4.1/graph_crawler/native/setup.py +103 -0
  385. graph_crawler-5.4.1/graph_crawler/native/url_utils.pyx +209 -0
  386. graph_crawler-5.4.1/graph_crawler/observability/__init__.py +62 -0
  387. graph_crawler-5.4.1/graph_crawler/observability/error_tracing.py +361 -0
  388. graph_crawler-5.4.1/graph_crawler/observability/extensions/__init__.py +18 -0
  389. graph_crawler-5.4.1/graph_crawler/observability/extensions/memory_guard.py +237 -0
  390. graph_crawler-5.4.1/graph_crawler/observability/extensions/state_manager.py +319 -0
  391. graph_crawler-5.4.1/graph_crawler/observability/listeners/__init__.py +33 -0
  392. graph_crawler-5.4.1/graph_crawler/observability/listeners/base.py +82 -0
  393. graph_crawler-5.4.1/graph_crawler/observability/listeners/base_metrics_listener.py +18 -0
  394. graph_crawler-5.4.1/graph_crawler/observability/listeners/crawl_listener.py +28 -0
  395. graph_crawler-5.4.1/graph_crawler/observability/listeners/dlq_listener.py +215 -0
  396. graph_crawler-5.4.1/graph_crawler/observability/listeners/error_listener.py +18 -0
  397. graph_crawler-5.4.1/graph_crawler/observability/listeners/logging_listener.py +88 -0
  398. graph_crawler-5.4.1/graph_crawler/observability/listeners/metrics_listener.py +109 -0
  399. graph_crawler-5.4.1/graph_crawler/observability/listeners/node_listener.py +33 -0
  400. graph_crawler-5.4.1/graph_crawler/observability/listeners/plugin_listener.py +28 -0
  401. graph_crawler-5.4.1/graph_crawler/observability/listeners/storage_listener.py +21 -0
  402. graph_crawler-5.4.1/graph_crawler/observability/listeners/url_listener.py +29 -0
  403. graph_crawler-5.4.1/graph_crawler/observability/metrics/__init__.py +23 -0
  404. graph_crawler-5.4.1/graph_crawler/observability/metrics/memory_profiler.py +510 -0
  405. graph_crawler-5.4.1/graph_crawler/observability/metrics/metrics_collector.py +408 -0
  406. graph_crawler-5.4.1/graph_crawler/observability/metrics/prometheus_metrics.py +293 -0
  407. graph_crawler-5.4.1/graph_crawler/observability/metrics_core.py +389 -0
  408. graph_crawler-5.4.1/graph_crawler/observability/structured_logging.py +216 -0
  409. graph_crawler-5.4.1/graph_crawler/observability/tracing.py +295 -0
  410. graph_crawler-5.4.1/graph_crawler/optimizations/__init__.py +89 -0
  411. graph_crawler-5.4.1/graph_crawler/optimizations/generators.py +477 -0
  412. graph_crawler-5.4.1/graph_crawler/optimizations/memory_slots.py +641 -0
  413. graph_crawler-5.4.1/graph_crawler/optimizations/simhash_numba.py +284 -0
  414. graph_crawler-5.4.1/graph_crawler/shared/__init__.py +25 -0
  415. graph_crawler-5.4.1/graph_crawler/shared/constants.py +952 -0
  416. graph_crawler-5.4.1/graph_crawler/shared/dto/__init__.py +38 -0
  417. graph_crawler-5.4.1/graph_crawler/shared/dto/edge_dto.py +52 -0
  418. graph_crawler-5.4.1/graph_crawler/shared/dto/graph_dto.py +97 -0
  419. graph_crawler-5.4.1/graph_crawler/shared/dto/node_dto.py +193 -0
  420. graph_crawler-5.4.1/graph_crawler/shared/error_handling/__init__.py +15 -0
  421. graph_crawler-5.4.1/graph_crawler/shared/error_handling/error_handler.py +333 -0
  422. graph_crawler-5.4.1/graph_crawler/shared/exceptions.py +394 -0
  423. graph_crawler-5.4.1/graph_crawler/shared/platform/__init__.py +125 -0
  424. graph_crawler-5.4.1/graph_crawler/shared/platform/adaptive_resolver.py +418 -0
  425. graph_crawler-5.4.1/graph_crawler/shared/platform/config.py +617 -0
  426. graph_crawler-5.4.1/graph_crawler/shared/platform/detector.py +326 -0
  427. graph_crawler-5.4.1/graph_crawler/shared/platform/diagnostics.py +650 -0
  428. graph_crawler-5.4.1/graph_crawler/shared/platform/metrics.py +291 -0
  429. graph_crawler-5.4.1/graph_crawler/shared/security/__init__.py +22 -0
  430. graph_crawler-5.4.1/graph_crawler/shared/security/url_sanitizer.py +100 -0
  431. graph_crawler-5.4.1/graph_crawler/shared/security/url_validator.py +299 -0
  432. graph_crawler-5.4.1/graph_crawler/shared/utils/__init__.py +213 -0
  433. graph_crawler-5.4.1/graph_crawler/shared/utils/async_compat.py +141 -0
  434. graph_crawler-5.4.1/graph_crawler/shared/utils/best_effort.py +48 -0
  435. graph_crawler-5.4.1/graph_crawler/shared/utils/bloom_filter.py +373 -0
  436. graph_crawler-5.4.1/graph_crawler/shared/utils/captcha/__init__.py +36 -0
  437. graph_crawler-5.4.1/graph_crawler/shared/utils/captcha/base.py +92 -0
  438. graph_crawler-5.4.1/graph_crawler/shared/utils/captcha/manager.py +464 -0
  439. graph_crawler-5.4.1/graph_crawler/shared/utils/celery_config.py +308 -0
  440. graph_crawler-5.4.1/graph_crawler/shared/utils/celery_helpers.py +155 -0
  441. graph_crawler-5.4.1/graph_crawler/shared/utils/cgroup_limits.py +223 -0
  442. graph_crawler-5.4.1/graph_crawler/shared/utils/content_decoder.py +150 -0
  443. graph_crawler-5.4.1/graph_crawler/shared/utils/distributed_rate_limiter.py +265 -0
  444. graph_crawler-5.4.1/graph_crawler/shared/utils/distributed_rate_limiter_backends.py +286 -0
  445. graph_crawler-5.4.1/graph_crawler/shared/utils/dns_cache.py +368 -0
  446. graph_crawler-5.4.1/graph_crawler/shared/utils/enterprise_ua_generator.py +294 -0
  447. graph_crawler-5.4.1/graph_crawler/shared/utils/event_publisher_mixin.py +190 -0
  448. graph_crawler-5.4.1/graph_crawler/shared/utils/fast_json.py +240 -0
  449. graph_crawler-5.4.1/graph_crawler/shared/utils/fingerprint.py +36 -0
  450. graph_crawler-5.4.1/graph_crawler/shared/utils/fingerprint_data.py +99 -0
  451. graph_crawler-5.4.1/graph_crawler/shared/utils/fingerprint_generators.py +128 -0
  452. graph_crawler-5.4.1/graph_crawler/shared/utils/fingerprint_profile.py +121 -0
  453. graph_crawler-5.4.1/graph_crawler/shared/utils/fingerprint_stealth.py +105 -0
  454. graph_crawler-5.4.1/graph_crawler/shared/utils/html_utils.py +118 -0
  455. graph_crawler-5.4.1/graph_crawler/shared/utils/id_generator.py +195 -0
  456. graph_crawler-5.4.1/graph_crawler/shared/utils/isolated_executor.py +200 -0
  457. graph_crawler-5.4.1/graph_crawler/shared/utils/memory_optimizer.py +536 -0
  458. graph_crawler-5.4.1/graph_crawler/shared/utils/rate_limiter.py +185 -0
  459. graph_crawler-5.4.1/graph_crawler/shared/utils/regex_validator.py +966 -0
  460. graph_crawler-5.4.1/graph_crawler/shared/utils/shadow_dom.py +40 -0
  461. graph_crawler-5.4.1/graph_crawler/shared/utils/text_utils.py +387 -0
  462. graph_crawler-5.4.1/graph_crawler/shared/utils/url_patterns.py +135 -0
  463. graph_crawler-5.4.1/graph_crawler/shared/utils/url_utils.py +595 -0
  464. graph_crawler-5.4.1/graph_crawler/shared/utils/user_agent_rotator.py +333 -0
  465. graph_crawler-5.4.1/graph_crawler/shared/utils/validation_helpers.py +225 -0
  466. graph_crawler-5.4.1/graph_crawler/shared/utils/visualization.py +134 -0
  467. graph_crawler-5.4.1/graph_crawler/shared/utils/visualization_core.py +558 -0
  468. graph_crawler-5.4.1/graph_crawler/tools/__init__.py +1 -0
  469. graph_crawler-5.4.1/graph_crawler/tools/db_cleanup.py +237 -0
  470. graph_crawler-5.4.1/graph_crawler.egg-info/PKG-INFO +523 -0
  471. graph_crawler-5.4.1/graph_crawler.egg-info/SOURCES.txt +476 -0
  472. graph_crawler-5.4.1/graph_crawler.egg-info/requires.txt +126 -0
  473. graph_crawler-5.4.1/pyproject.toml +302 -0
  474. graph_crawler-5.4.1/setup.cfg +4 -0
  475. graph_crawler-5.3.7/CHANGELOG.md +0 -546
  476. graph_crawler-5.3.7/LICENSE +0 -21
  477. graph_crawler-5.3.7/MANIFEST.in +0 -16
  478. graph_crawler-5.3.7/PKG-INFO +0 -522
  479. graph_crawler-5.3.7/README.md +0 -401
  480. graph_crawler-5.3.7/graph_crawler/__init__.py +0 -496
  481. graph_crawler-5.3.7/graph_crawler/__version__.py +0 -8
  482. graph_crawler-5.3.7/graph_crawler/ai/__init__.py +0 -50
  483. graph_crawler-5.3.7/graph_crawler/api/__init__.py +0 -28
  484. graph_crawler-5.3.7/graph_crawler/api/_core.py +0 -287
  485. graph_crawler-5.3.7/graph_crawler/api/_distributed.py +0 -172
  486. graph_crawler-5.3.7/graph_crawler/api/_shared.py +0 -110
  487. graph_crawler-5.3.7/graph_crawler/api/_sitemap_distributed.py +0 -238
  488. graph_crawler-5.3.7/graph_crawler/api/async_.py +0 -327
  489. graph_crawler-5.3.7/graph_crawler/api/cli.py +0 -440
  490. graph_crawler-5.3.7/graph_crawler/api/client/__init__.py +0 -8
  491. graph_crawler-5.3.7/graph_crawler/api/client/client.py +0 -476
  492. graph_crawler-5.3.7/graph_crawler/api/history_manager.py +0 -55
  493. graph_crawler-5.3.7/graph_crawler/api/project_init.py +0 -196
  494. graph_crawler-5.3.7/graph_crawler/api/server/__init__.py +0 -18
  495. graph_crawler-5.3.7/graph_crawler/api/server/crawl_monitor.py +0 -145
  496. graph_crawler-5.3.7/graph_crawler/api/server/dashboard.py +0 -221
  497. graph_crawler-5.3.7/graph_crawler/api/server/rest_api.py +0 -704
  498. graph_crawler-5.3.7/graph_crawler/api/server/webhooks.py +0 -421
  499. graph_crawler-5.3.7/graph_crawler/api/server/websocket_manager.py +0 -89
  500. graph_crawler-5.3.7/graph_crawler/api/stats_collector.py +0 -88
  501. graph_crawler-5.3.7/graph_crawler/api/sync.py +0 -544
  502. graph_crawler-5.3.7/graph_crawler/application/__init__.py +0 -38
  503. graph_crawler-5.3.7/graph_crawler/application/bootstrap.py +0 -211
  504. graph_crawler-5.3.7/graph_crawler/application/context/__init__.py +0 -31
  505. graph_crawler-5.3.7/graph_crawler/application/context/dependency_registry.py +0 -354
  506. graph_crawler-5.3.7/graph_crawler/application/context/graph_context.py +0 -217
  507. graph_crawler-5.3.7/graph_crawler/application/context/merge_context.py +0 -221
  508. graph_crawler-5.3.7/graph_crawler/application/dto/__init__.py +0 -67
  509. graph_crawler-5.3.7/graph_crawler/application/dto/edge_dto.py +0 -25
  510. graph_crawler-5.3.7/graph_crawler/application/dto/graph_dto.py +0 -27
  511. graph_crawler-5.3.7/graph_crawler/application/dto/mappers/__init__.py +0 -14
  512. graph_crawler-5.3.7/graph_crawler/application/dto/mappers/edge_mapper.py +0 -149
  513. graph_crawler-5.3.7/graph_crawler/application/dto/mappers/graph_mapper.py +0 -622
  514. graph_crawler-5.3.7/graph_crawler/application/dto/mappers/node_data_codec.py +0 -190
  515. graph_crawler-5.3.7/graph_crawler/application/dto/mappers/node_mapper.py +0 -418
  516. graph_crawler-5.3.7/graph_crawler/application/dto/node_dto.py +0 -27
  517. graph_crawler-5.3.7/graph_crawler/application/dto/utils.py +0 -272
  518. graph_crawler-5.3.7/graph_crawler/application/services/__init__.py +0 -60
  519. graph_crawler-5.3.7/graph_crawler/application/services/application_container.py +0 -208
  520. graph_crawler-5.3.7/graph_crawler/application/services/driver_factory.py +0 -147
  521. graph_crawler-5.3.7/graph_crawler/application/services/exporters/__init__.py +0 -26
  522. graph_crawler-5.3.7/graph_crawler/application/services/exporters/base_exporter.py +0 -150
  523. graph_crawler-5.3.7/graph_crawler/application/services/exporters/csv_exporter.py +0 -318
  524. graph_crawler-5.3.7/graph_crawler/application/services/exporters/edge_exporter.py +0 -432
  525. graph_crawler-5.3.7/graph_crawler/application/services/exporters/excel_exporter.py +0 -301
  526. graph_crawler-5.3.7/graph_crawler/application/services/exporters/jsonl_exporter.py +0 -290
  527. graph_crawler-5.3.7/graph_crawler/application/services/exporters/node_exporter.py +0 -284
  528. graph_crawler-5.3.7/graph_crawler/application/services/exporters/parquet_exporter.py +0 -248
  529. graph_crawler-5.3.7/graph_crawler/application/services/exporters/sql_exporter.py +0 -267
  530. graph_crawler-5.3.7/graph_crawler/application/services/finalize.py +0 -115
  531. graph_crawler-5.3.7/graph_crawler/application/services/low_memory_graph_saver.py +0 -337
  532. graph_crawler-5.3.7/graph_crawler/application/services/resource_budget.py +0 -179
  533. graph_crawler-5.3.7/graph_crawler/application/services/scheduling/__init__.py +0 -12
  534. graph_crawler-5.3.7/graph_crawler/application/services/scheduling/rule_based_scheduler.py +0 -105
  535. graph_crawler-5.3.7/graph_crawler/application/services/storage_factory.py +0 -195
  536. graph_crawler-5.3.7/graph_crawler/application/use_cases/__init__.py +0 -11
  537. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/__init__.py +0 -81
  538. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/adaptive_throttler.py +0 -373
  539. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/async_bridge.py +0 -45
  540. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/base_spider.py +0 -155
  541. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/celery_batch_spider.py +0 -656
  542. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/celery_spider.py +0 -445
  543. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/checkpoint.py +0 -421
  544. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/crawl_coordinator.py +0 -1068
  545. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/dead_letter_queue.py +0 -413
  546. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/domain_rate_limiter.py +0 -262
  547. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/filters/__init__.py +0 -10
  548. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/filters/base.py +0 -74
  549. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/filters/domain_filter.py +0 -226
  550. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/filters/domain_patterns.py +0 -30
  551. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/filters/path_filter.py +0 -95
  552. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/incremental_strategy.py +0 -239
  553. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/link_processor.py +0 -885
  554. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/multiprocess_spider.py +0 -511
  555. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/node_scanner.py +0 -867
  556. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/parsers/__init__.py +0 -6
  557. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/parsers/base.py +0 -73
  558. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/parsers/html_parser.py +0 -154
  559. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/progress_tracker.py +0 -235
  560. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/scheduler.py +0 -757
  561. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/serialization_mixin.py +0 -218
  562. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/sitemap_parser.py +0 -793
  563. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/sitemap_processor.py +0 -404
  564. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/sitemap_spider.py +0 -653
  565. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/spider.py +0 -858
  566. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/spider_lifecycle.py +0 -108
  567. graph_crawler-5.3.7/graph_crawler/application/use_cases/crawling/worker_pool.py +0 -154
  568. graph_crawler-5.3.7/graph_crawler/application/use_cases/graph_export.py +0 -504
  569. graph_crawler-5.3.7/graph_crawler/application/use_cases/graph_merge/__init__.py +0 -33
  570. graph_crawler-5.3.7/graph_crawler/application/use_cases/graph_merge/graph_operations.py +0 -632
  571. graph_crawler-5.3.7/graph_crawler/application/use_cases/graph_merge/merge_strategies.py +0 -348
  572. graph_crawler-5.3.7/graph_crawler/data/__init__.py +0 -28
  573. graph_crawler-5.3.7/graph_crawler/data/backends/__init__.py +0 -21
  574. graph_crawler-5.3.7/graph_crawler/data/backends/memory.py +0 -486
  575. graph_crawler-5.3.7/graph_crawler/data/backends/sqlite.py +0 -1891
  576. graph_crawler-5.3.7/graph_crawler/data/interfaces.py +0 -563
  577. graph_crawler-5.3.7/graph_crawler/domain/__init__.py +0 -62
  578. graph_crawler-5.3.7/graph_crawler/domain/context/__init__.py +0 -14
  579. graph_crawler-5.3.7/graph_crawler/domain/context/crawl_context.py +0 -531
  580. graph_crawler-5.3.7/graph_crawler/domain/entities/__init__.py +0 -32
  581. graph_crawler-5.3.7/graph_crawler/domain/entities/edge.py +0 -345
  582. graph_crawler-5.3.7/graph_crawler/domain/entities/edge_analysis.py +0 -223
  583. graph_crawler-5.3.7/graph_crawler/domain/entities/graph.py +0 -4291
  584. graph_crawler-5.3.7/graph_crawler/domain/entities/graph_operations.py +0 -30
  585. graph_crawler-5.3.7/graph_crawler/domain/entities/graph_statistics.py +0 -10
  586. graph_crawler-5.3.7/graph_crawler/domain/entities/merge_strategies.py +0 -33
  587. graph_crawler-5.3.7/graph_crawler/domain/entities/node.py +0 -962
  588. graph_crawler-5.3.7/graph_crawler/domain/entities/redirect_mode.py +0 -23
  589. graph_crawler-5.3.7/graph_crawler/domain/entities/registries.py +0 -223
  590. graph_crawler-5.3.7/graph_crawler/domain/entities/sitemap_node.py +0 -77
  591. graph_crawler-5.3.7/graph_crawler/domain/entities/strategies.py +0 -202
  592. graph_crawler-5.3.7/graph_crawler/domain/events/__init__.py +0 -162
  593. graph_crawler-5.3.7/graph_crawler/domain/events/base.py +0 -60
  594. graph_crawler-5.3.7/graph_crawler/domain/events/crawl.py +0 -112
  595. graph_crawler-5.3.7/graph_crawler/domain/events/edge.py +0 -43
  596. graph_crawler-5.3.7/graph_crawler/domain/events/event_bus.py +0 -250
  597. graph_crawler-5.3.7/graph_crawler/domain/events/events.py +0 -313
  598. graph_crawler-5.3.7/graph_crawler/domain/events/fetch.py +0 -120
  599. graph_crawler-5.3.7/graph_crawler/domain/events/idempotent_subscriber.py +0 -120
  600. graph_crawler-5.3.7/graph_crawler/domain/events/legacy_type_map.py +0 -45
  601. graph_crawler-5.3.7/graph_crawler/domain/events/llm.py +0 -148
  602. graph_crawler-5.3.7/graph_crawler/domain/events/node.py +0 -111
  603. graph_crawler-5.3.7/graph_crawler/domain/events/plugin.py +0 -92
  604. graph_crawler-5.3.7/graph_crawler/domain/events/scheduler.py +0 -98
  605. graph_crawler-5.3.7/graph_crawler/domain/events/storage.py +0 -161
  606. graph_crawler-5.3.7/graph_crawler/domain/interfaces/__init__.py +0 -113
  607. graph_crawler-5.3.7/graph_crawler/domain/interfaces/adapter.py +0 -29
  608. graph_crawler-5.3.7/graph_crawler/domain/interfaces/control_channel.py +0 -381
  609. graph_crawler-5.3.7/graph_crawler/domain/interfaces/crawl_context.py +0 -49
  610. graph_crawler-5.3.7/graph_crawler/domain/interfaces/distributed_spider.py +0 -62
  611. graph_crawler-5.3.7/graph_crawler/domain/interfaces/driver.py +0 -87
  612. graph_crawler-5.3.7/graph_crawler/domain/interfaces/event_bus.py +0 -61
  613. graph_crawler-5.3.7/graph_crawler/domain/interfaces/exporters.py +0 -69
  614. graph_crawler-5.3.7/graph_crawler/domain/interfaces/filter.py +0 -19
  615. graph_crawler-5.3.7/graph_crawler/domain/interfaces/merge_context.py +0 -52
  616. graph_crawler-5.3.7/graph_crawler/domain/interfaces/node_interfaces.py +0 -389
  617. graph_crawler-5.3.7/graph_crawler/domain/interfaces/parser.py +0 -88
  618. graph_crawler-5.3.7/graph_crawler/domain/interfaces/plugin_manager.py +0 -53
  619. graph_crawler-5.3.7/graph_crawler/domain/interfaces/processor.py +0 -11
  620. graph_crawler-5.3.7/graph_crawler/domain/interfaces/scanner.py +0 -57
  621. graph_crawler-5.3.7/graph_crawler/domain/interfaces/scheduler.py +0 -43
  622. graph_crawler-5.3.7/graph_crawler/domain/interfaces/scheduling/__init__.py +0 -40
  623. graph_crawler-5.3.7/graph_crawler/domain/interfaces/scheduling/frontier.py +0 -97
  624. graph_crawler-5.3.7/graph_crawler/domain/interfaces/scheduling/scheduler.py +0 -107
  625. graph_crawler-5.3.7/graph_crawler/domain/interfaces/spider.py +0 -63
  626. graph_crawler-5.3.7/graph_crawler/domain/interfaces/stop_condition.py +0 -211
  627. graph_crawler-5.3.7/graph_crawler/domain/interfaces/storage/__init__.py +0 -43
  628. graph_crawler-5.3.7/graph_crawler/domain/interfaces/storage/base.py +0 -68
  629. graph_crawler-5.3.7/graph_crawler/domain/interfaces/storage/eviction.py +0 -98
  630. graph_crawler-5.3.7/graph_crawler/domain/interfaces/storage/queue.py +0 -234
  631. graph_crawler-5.3.7/graph_crawler/domain/services/__init__.py +0 -216
  632. graph_crawler-5.3.7/graph_crawler/domain/services/adjacency_maintenance.py +0 -95
  633. graph_crawler-5.3.7/graph_crawler/domain/services/cycle_detection.py +0 -92
  634. graph_crawler-5.3.7/graph_crawler/domain/services/degree_ranking.py +0 -47
  635. graph_crawler-5.3.7/graph_crawler/domain/services/depth_context.py +0 -123
  636. graph_crawler-5.3.7/graph_crawler/domain/services/display_title.py +0 -47
  637. graph_crawler-5.3.7/graph_crawler/domain/services/duplicate_detection.py +0 -141
  638. graph_crawler-5.3.7/graph_crawler/domain/services/duplicate_selector.py +0 -127
  639. graph_crawler-5.3.7/graph_crawler/domain/services/duplicate_stats.py +0 -63
  640. graph_crawler-5.3.7/graph_crawler/domain/services/edge_filter.py +0 -82
  641. graph_crawler-5.3.7/graph_crawler/domain/services/edge_statistics.py +0 -110
  642. graph_crawler-5.3.7/graph_crawler/domain/services/eviction_policy.py +0 -83
  643. graph_crawler-5.3.7/graph_crawler/domain/services/graph_query_cascade.py +0 -178
  644. graph_crawler-5.3.7/graph_crawler/domain/services/graph_statistics.py +0 -186
  645. graph_crawler-5.3.7/graph_crawler/domain/services/graph_traversal.py +0 -150
  646. graph_crawler-5.3.7/graph_crawler/domain/services/language_versions.py +0 -73
  647. graph_crawler-5.3.7/graph_crawler/domain/services/lsh_index.py +0 -99
  648. graph_crawler-5.3.7/graph_crawler/domain/services/minimap_builder.py +0 -147
  649. graph_crawler-5.3.7/graph_crawler/domain/services/node_content_resolver.py +0 -128
  650. graph_crawler-5.3.7/graph_crawler/domain/services/node_view_builder.py +0 -163
  651. graph_crawler-5.3.7/graph_crawler/domain/services/pagination_guards.py +0 -136
  652. graph_crawler-5.3.7/graph_crawler/domain/services/predicate_ops.py +0 -77
  653. graph_crawler-5.3.7/graph_crawler/domain/services/schema_filter.py +0 -138
  654. graph_crawler-5.3.7/graph_crawler/domain/services/search_haystack.py +0 -102
  655. graph_crawler-5.3.7/graph_crawler/domain/services/simhash_clustering.py +0 -96
  656. graph_crawler-5.3.7/graph_crawler/domain/services/simhash_fallback.py +0 -59
  657. graph_crawler-5.3.7/graph_crawler/domain/services/similarity_ranking.py +0 -62
  658. graph_crawler-5.3.7/graph_crawler/domain/services/text_search.py +0 -157
  659. graph_crawler-5.3.7/graph_crawler/domain/services/url_clustering.py +0 -88
  660. graph_crawler-5.3.7/graph_crawler/domain/services/url_deduplication.py +0 -101
  661. graph_crawler-5.3.7/graph_crawler/domain/services/url_normalizer.py +0 -117
  662. graph_crawler-5.3.7/graph_crawler/domain/services/url_rule_matcher.py +0 -111
  663. graph_crawler-5.3.7/graph_crawler/domain/services/url_tree_builder.py +0 -197
  664. graph_crawler-5.3.7/graph_crawler/domain/value_objects/__init__.py +0 -88
  665. graph_crawler-5.3.7/graph_crawler/domain/value_objects/configs.py +0 -894
  666. graph_crawler-5.3.7/graph_crawler/domain/value_objects/domain_patterns.py +0 -71
  667. graph_crawler-5.3.7/graph_crawler/domain/value_objects/fetch_options.py +0 -57
  668. graph_crawler-5.3.7/graph_crawler/domain/value_objects/html_document.py +0 -59
  669. graph_crawler-5.3.7/graph_crawler/domain/value_objects/lifecycle.py +0 -30
  670. graph_crawler-5.3.7/graph_crawler/domain/value_objects/models.py +0 -1315
  671. graph_crawler-5.3.7/graph_crawler/domain/value_objects/page_actions.py +0 -72
  672. graph_crawler-5.3.7/graph_crawler/domain/value_objects/redirect_hop.py +0 -49
  673. graph_crawler-5.3.7/graph_crawler/domain/value_objects/scheduling/__init__.py +0 -23
  674. graph_crawler-5.3.7/graph_crawler/domain/value_objects/scheduling/crawl_request.py +0 -153
  675. graph_crawler-5.3.7/graph_crawler/domain/value_objects/scheduling/retry_policy.py +0 -109
  676. graph_crawler-5.3.7/graph_crawler/domain/value_objects/settings.py +0 -350
  677. graph_crawler-5.3.7/graph_crawler/domain/value_objects/url_normalization.py +0 -497
  678. graph_crawler-5.3.7/graph_crawler/domain/value_objects/views.py +0 -293
  679. graph_crawler-5.3.7/graph_crawler/extensions/__init__.py +0 -1
  680. graph_crawler-5.3.7/graph_crawler/extensions/middleware/__init__.py +0 -65
  681. graph_crawler-5.3.7/graph_crawler/extensions/middleware/base.py +0 -136
  682. graph_crawler-5.3.7/graph_crawler/extensions/middleware/cache_middleware.py +0 -277
  683. graph_crawler-5.3.7/graph_crawler/extensions/middleware/chain.py +0 -155
  684. graph_crawler-5.3.7/graph_crawler/extensions/middleware/error_recovery_middleware.py +0 -442
  685. graph_crawler-5.3.7/graph_crawler/extensions/middleware/logging_middleware.py +0 -108
  686. graph_crawler-5.3.7/graph_crawler/extensions/middleware/proxy_health.py +0 -141
  687. graph_crawler-5.3.7/graph_crawler/extensions/middleware/proxy_middleware.py +0 -209
  688. graph_crawler-5.3.7/graph_crawler/extensions/middleware/proxy_models.py +0 -80
  689. graph_crawler-5.3.7/graph_crawler/extensions/middleware/proxy_selection.py +0 -87
  690. graph_crawler-5.3.7/graph_crawler/extensions/middleware/rate_limit_middleware.py +0 -262
  691. graph_crawler-5.3.7/graph_crawler/extensions/middleware/request_response_middleware.py +0 -476
  692. graph_crawler-5.3.7/graph_crawler/extensions/middleware/retry_middleware.py +0 -279
  693. graph_crawler-5.3.7/graph_crawler/extensions/middleware/robots_cache.py +0 -85
  694. graph_crawler-5.3.7/graph_crawler/extensions/middleware/robots_middleware.py +0 -110
  695. graph_crawler-5.3.7/graph_crawler/extensions/middleware/robots_validator.py +0 -60
  696. graph_crawler-5.3.7/graph_crawler/extensions/middleware/user_agent_middleware.py +0 -251
  697. graph_crawler-5.3.7/graph_crawler/extensions/plugins/__init__.py +0 -57
  698. graph_crawler-5.3.7/graph_crawler/extensions/plugins/builtin/__init__.py +0 -7
  699. graph_crawler-5.3.7/graph_crawler/extensions/plugins/builtin/stats_export_plugin.py +0 -99
  700. graph_crawler-5.3.7/graph_crawler/extensions/plugins/crawl_engine/__init__.py +0 -24
  701. graph_crawler-5.3.7/graph_crawler/extensions/plugins/crawl_engine/base.py +0 -155
  702. graph_crawler-5.3.7/graph_crawler/extensions/plugins/crawl_engine/priority_provider.py +0 -247
  703. graph_crawler-5.3.7/graph_crawler/extensions/plugins/crawl_engine/smart_crawl.py +0 -296
  704. graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/__init__.py +0 -128
  705. graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/anti_bot_detection.py +0 -55
  706. graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/anti_bot_playwright.py +0 -88
  707. graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/anti_bot_scripts.py +0 -136
  708. graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/anti_bot_stealth.py +0 -177
  709. graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/base.py +0 -121
  710. graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/captcha/__init__.py +0 -22
  711. graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/captcha/detector.py +0 -94
  712. graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/captcha/models.py +0 -74
  713. graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/captcha/plugin.py +0 -228
  714. graph_crawler-5.3.7/graph_crawler/extensions/plugins/engine/captcha/services.py +0 -389
  715. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/__init__.py +0 -86
  716. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/base.py +0 -520
  717. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/content_extractors/__init__.py +0 -15
  718. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/content_extractors/base.py +0 -82
  719. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/content_extractors/goose3_extractor.py +0 -80
  720. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/content_extractors/newspaper_extractor.py +0 -93
  721. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/content_extractors/plugin.py +0 -132
  722. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/content_extractors/readability_extractor.py +0 -91
  723. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/defaults.py +0 -32
  724. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/extractors/__init__.py +0 -30
  725. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/extractors/email_extractor.py +0 -146
  726. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/extractors/phone_extractor.py +0 -136
  727. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/extractors/price_extractor.py +0 -137
  728. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/links.py +0 -184
  729. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/markdown_plugin.py +0 -249
  730. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/metadata.py +0 -384
  731. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/smart_page_finder.py +0 -749
  732. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/__init__.py +0 -51
  733. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/constants.py +0 -27
  734. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/exceptions.py +0 -18
  735. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/extractor.py +0 -361
  736. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/options.py +0 -62
  737. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/parsers/__init__.py +0 -25
  738. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/parsers/base.py +0 -43
  739. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/parsers/jsonld.py +0 -331
  740. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/parsers/microdata.py +0 -117
  741. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/parsers/opengraph.py +0 -66
  742. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/parsers/rdfa.py +0 -122
  743. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/parsers/twitter.py +0 -66
  744. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/plugin.py +0 -132
  745. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/structured_data/result.py +0 -233
  746. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/text.py +0 -76
  747. graph_crawler-5.3.7/graph_crawler/extensions/plugins/node/url_normalizer.py +0 -363
  748. graph_crawler-5.3.7/graph_crawler/extensions/plugins/registry.py +0 -97
  749. graph_crawler-5.3.7/graph_crawler/infrastructure/__init__.py +0 -1
  750. graph_crawler-5.3.7/graph_crawler/infrastructure/adapters/__init__.py +0 -60
  751. graph_crawler-5.3.7/graph_crawler/infrastructure/adapters/base.py +0 -498
  752. graph_crawler-5.3.7/graph_crawler/infrastructure/adapters/selectolax_adapter.py +0 -233
  753. graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/__init__.py +0 -37
  754. graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/celery_app.py +0 -202
  755. graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/celery_batch.py +0 -284
  756. graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/celery_job_task.py +0 -415
  757. graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/celery_unified.py +0 -419
  758. graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/config.py +0 -201
  759. graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/easy_crawler.py +0 -279
  760. graph_crawler-5.3.7/graph_crawler/infrastructure/messaging/worker_api.py +0 -607
  761. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/__init__.py +0 -135
  762. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/auto_storage.py +0 -336
  763. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/base.py +0 -111
  764. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/eviction.py +0 -100
  765. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/graph_repository.py +0 -401
  766. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/json/__init__.py +0 -10
  767. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/json_storage.py +0 -375
  768. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/lmdb_eviction_storage.py +0 -401
  769. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/memory/__init__.py +0 -1
  770. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/memory_dedup_store.py +0 -90
  771. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/memory_frontier.py +0 -180
  772. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/memory_politeness_store.py +0 -88
  773. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/memory_storage.py +0 -166
  774. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/mongodb_storage.py +0 -383
  775. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/naming_strategy.py +0 -174
  776. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/postgresql_storage.py +0 -449
  777. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/protocols/__init__.py +0 -1
  778. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/redis_dedup_store.py +0 -119
  779. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/redis_politeness_store.py +0 -122
  780. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/repository.py +0 -130
  781. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/sqlite/__init__.py +0 -10
  782. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/sqlite_eviction_storage.py +0 -919
  783. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/sqlite_storage.py +0 -775
  784. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/__init__.py +0 -47
  785. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/file_job_storage.py +0 -245
  786. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/file_queue_storage.py +0 -248
  787. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/memory_job_storage.py +0 -94
  788. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/memory_queue_storage.py +0 -117
  789. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/postgresql_job_storage.py +0 -317
  790. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/postgresql_queue_storage.py +0 -364
  791. graph_crawler-5.3.7/graph_crawler/infrastructure/persistence/unified/unified_storage.py +0 -244
  792. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/__init__.py +0 -135
  793. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/__init__.py +0 -38
  794. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/config.py +0 -47
  795. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/context.py +0 -55
  796. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/driver.py +0 -724
  797. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/driver_curl_cffi.py +0 -407
  798. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/plugins/__init__.py +0 -48
  799. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle.py +0 -484
  800. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle_adaptive.py +0 -1435
  801. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/plugins/headers.py +0 -69
  802. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/plugins/http_cache.py +0 -321
  803. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/plugins/rate_limiter.py +0 -96
  804. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/plugins/retry.py +0 -316
  805. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/async_http/stages.py +0 -33
  806. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/base.py +0 -349
  807. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/base_plugin.py +0 -151
  808. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/compression/__init__.py +0 -23
  809. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/compression/strategy.py +0 -219
  810. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/connection_pool.py +0 -349
  811. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/context.py +0 -114
  812. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/core/__init__.py +0 -15
  813. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/core/base_async.py +0 -378
  814. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/core/body_decoder.py +0 -26
  815. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/core/mixins.py +0 -258
  816. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/core/redirect_utils.py +0 -168
  817. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/escalation.py +0 -1105
  818. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/factory.py +0 -210
  819. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/__init__.py +0 -19
  820. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/adaptive_semaphore.py +0 -65
  821. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/batch_scheduler.py +0 -764
  822. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/browser_factory.py +0 -580
  823. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/browser_profile.py +0 -718
  824. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/config.py +0 -84
  825. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/context.py +0 -46
  826. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/driver.py +0 -80
  827. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/engine.py +0 -480
  828. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/fetch_metrics.py +0 -115
  829. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/net/__init__.py +0 -41
  830. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/net/geoip.py +0 -136
  831. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/net/proxy_pool.py +0 -225
  832. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/net/rate_limiter.py +0 -139
  833. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/page_actions.py +0 -186
  834. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/page_fetch.py +0 -968
  835. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/__init__.py +0 -71
  836. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/_network_record.py +0 -66
  837. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/captcha_detector.py +0 -315
  838. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/captcha_solver.py +0 -108
  839. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/cookie_consent.py +0 -565
  840. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/form_filler.py +0 -177
  841. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/human_bezier.py +0 -297
  842. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/network_log.py +0 -406
  843. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/ram_adaptive.py +0 -208
  844. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/screenshot.py +0 -169
  845. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/plugins/user_profile.py +0 -556
  846. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/pool_lifecycle.py +0 -658
  847. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/pooled_driver.py +0 -706
  848. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/stages.py +0 -35
  849. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/playwright/stealth_hints.py +0 -153
  850. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/plugin_manager.py +0 -316
  851. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/protocols.py +0 -132
  852. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/undetected/__init__.py +0 -8
  853. graph_crawler-5.3.7/graph_crawler/infrastructure/transport/undetected/async_driver.py +0 -465
  854. graph_crawler-5.3.7/graph_crawler/native/__init__.py +0 -199
  855. graph_crawler-5.3.7/graph_crawler/native/bloom_filter.pyx +0 -255
  856. graph_crawler-5.3.7/graph_crawler/native/html_parser.pyx +0 -262
  857. graph_crawler-5.3.7/graph_crawler/native/setup.py +0 -103
  858. graph_crawler-5.3.7/graph_crawler/native/url_utils.pyx +0 -209
  859. graph_crawler-5.3.7/graph_crawler/observability/__init__.py +0 -62
  860. graph_crawler-5.3.7/graph_crawler/observability/decorators/__init__.py +0 -11
  861. graph_crawler-5.3.7/graph_crawler/observability/decorators/cache.py +0 -126
  862. graph_crawler-5.3.7/graph_crawler/observability/decorators/log.py +0 -56
  863. graph_crawler-5.3.7/graph_crawler/observability/decorators/retry.py +0 -121
  864. graph_crawler-5.3.7/graph_crawler/observability/decorators/timing.py +0 -43
  865. graph_crawler-5.3.7/graph_crawler/observability/error_tracing.py +0 -361
  866. graph_crawler-5.3.7/graph_crawler/observability/extensions/__init__.py +0 -18
  867. graph_crawler-5.3.7/graph_crawler/observability/extensions/memory_guard.py +0 -237
  868. graph_crawler-5.3.7/graph_crawler/observability/extensions/state_manager.py +0 -319
  869. graph_crawler-5.3.7/graph_crawler/observability/listeners/__init__.py +0 -33
  870. graph_crawler-5.3.7/graph_crawler/observability/listeners/base.py +0 -82
  871. graph_crawler-5.3.7/graph_crawler/observability/listeners/base_metrics_listener.py +0 -18
  872. graph_crawler-5.3.7/graph_crawler/observability/listeners/crawl_listener.py +0 -28
  873. graph_crawler-5.3.7/graph_crawler/observability/listeners/dlq_listener.py +0 -215
  874. graph_crawler-5.3.7/graph_crawler/observability/listeners/error_listener.py +0 -18
  875. graph_crawler-5.3.7/graph_crawler/observability/listeners/logging_listener.py +0 -88
  876. graph_crawler-5.3.7/graph_crawler/observability/listeners/metrics_listener.py +0 -109
  877. graph_crawler-5.3.7/graph_crawler/observability/listeners/node_listener.py +0 -33
  878. graph_crawler-5.3.7/graph_crawler/observability/listeners/plugin_listener.py +0 -28
  879. graph_crawler-5.3.7/graph_crawler/observability/listeners/storage_listener.py +0 -21
  880. graph_crawler-5.3.7/graph_crawler/observability/listeners/url_listener.py +0 -29
  881. graph_crawler-5.3.7/graph_crawler/observability/metrics/__init__.py +0 -23
  882. graph_crawler-5.3.7/graph_crawler/observability/metrics/memory_profiler.py +0 -508
  883. graph_crawler-5.3.7/graph_crawler/observability/metrics/metrics_collector.py +0 -408
  884. graph_crawler-5.3.7/graph_crawler/observability/metrics/prometheus_metrics.py +0 -293
  885. graph_crawler-5.3.7/graph_crawler/observability/metrics_core.py +0 -389
  886. graph_crawler-5.3.7/graph_crawler/observability/structured_logging.py +0 -216
  887. graph_crawler-5.3.7/graph_crawler/observability/tracing.py +0 -291
  888. graph_crawler-5.3.7/graph_crawler/optimizations/__init__.py +0 -89
  889. graph_crawler-5.3.7/graph_crawler/optimizations/generators.py +0 -477
  890. graph_crawler-5.3.7/graph_crawler/optimizations/memory_slots.py +0 -641
  891. graph_crawler-5.3.7/graph_crawler/optimizations/simhash_numba.py +0 -284
  892. graph_crawler-5.3.7/graph_crawler/shared/__init__.py +0 -25
  893. graph_crawler-5.3.7/graph_crawler/shared/constants.py +0 -943
  894. graph_crawler-5.3.7/graph_crawler/shared/dto/__init__.py +0 -38
  895. graph_crawler-5.3.7/graph_crawler/shared/dto/edge_dto.py +0 -52
  896. graph_crawler-5.3.7/graph_crawler/shared/dto/graph_dto.py +0 -97
  897. graph_crawler-5.3.7/graph_crawler/shared/dto/node_dto.py +0 -193
  898. graph_crawler-5.3.7/graph_crawler/shared/error_handling/__init__.py +0 -15
  899. graph_crawler-5.3.7/graph_crawler/shared/error_handling/error_handler.py +0 -333
  900. graph_crawler-5.3.7/graph_crawler/shared/exceptions.py +0 -394
  901. graph_crawler-5.3.7/graph_crawler/shared/platform/__init__.py +0 -125
  902. graph_crawler-5.3.7/graph_crawler/shared/platform/adaptive_resolver.py +0 -414
  903. graph_crawler-5.3.7/graph_crawler/shared/platform/config.py +0 -635
  904. graph_crawler-5.3.7/graph_crawler/shared/platform/detector.py +0 -334
  905. graph_crawler-5.3.7/graph_crawler/shared/platform/diagnostics.py +0 -660
  906. graph_crawler-5.3.7/graph_crawler/shared/platform/metrics.py +0 -299
  907. graph_crawler-5.3.7/graph_crawler/shared/security/__init__.py +0 -22
  908. graph_crawler-5.3.7/graph_crawler/shared/security/url_sanitizer.py +0 -100
  909. graph_crawler-5.3.7/graph_crawler/shared/security/url_validator.py +0 -299
  910. graph_crawler-5.3.7/graph_crawler/shared/utils/__init__.py +0 -212
  911. graph_crawler-5.3.7/graph_crawler/shared/utils/async_compat.py +0 -140
  912. graph_crawler-5.3.7/graph_crawler/shared/utils/bloom_filter.py +0 -373
  913. graph_crawler-5.3.7/graph_crawler/shared/utils/captcha/__init__.py +0 -36
  914. graph_crawler-5.3.7/graph_crawler/shared/utils/captcha/base.py +0 -92
  915. graph_crawler-5.3.7/graph_crawler/shared/utils/captcha/manager.py +0 -474
  916. graph_crawler-5.3.7/graph_crawler/shared/utils/celery_config.py +0 -332
  917. graph_crawler-5.3.7/graph_crawler/shared/utils/celery_helpers.py +0 -153
  918. graph_crawler-5.3.7/graph_crawler/shared/utils/cgroup_limits.py +0 -227
  919. graph_crawler-5.3.7/graph_crawler/shared/utils/content_decoder.py +0 -150
  920. graph_crawler-5.3.7/graph_crawler/shared/utils/distributed_rate_limiter.py +0 -265
  921. graph_crawler-5.3.7/graph_crawler/shared/utils/distributed_rate_limiter_backends.py +0 -284
  922. graph_crawler-5.3.7/graph_crawler/shared/utils/dns_cache.py +0 -370
  923. graph_crawler-5.3.7/graph_crawler/shared/utils/enterprise_ua_generator.py +0 -294
  924. graph_crawler-5.3.7/graph_crawler/shared/utils/event_publisher_mixin.py +0 -187
  925. graph_crawler-5.3.7/graph_crawler/shared/utils/fast_json.py +0 -240
  926. graph_crawler-5.3.7/graph_crawler/shared/utils/fingerprint.py +0 -36
  927. graph_crawler-5.3.7/graph_crawler/shared/utils/fingerprint_data.py +0 -99
  928. graph_crawler-5.3.7/graph_crawler/shared/utils/fingerprint_generators.py +0 -128
  929. graph_crawler-5.3.7/graph_crawler/shared/utils/fingerprint_profile.py +0 -121
  930. graph_crawler-5.3.7/graph_crawler/shared/utils/fingerprint_stealth.py +0 -105
  931. graph_crawler-5.3.7/graph_crawler/shared/utils/html_utils.py +0 -118
  932. graph_crawler-5.3.7/graph_crawler/shared/utils/id_generator.py +0 -195
  933. graph_crawler-5.3.7/graph_crawler/shared/utils/isolated_executor.py +0 -200
  934. graph_crawler-5.3.7/graph_crawler/shared/utils/memory_optimizer.py +0 -536
  935. graph_crawler-5.3.7/graph_crawler/shared/utils/rate_limiter.py +0 -185
  936. graph_crawler-5.3.7/graph_crawler/shared/utils/regex_validator.py +0 -966
  937. graph_crawler-5.3.7/graph_crawler/shared/utils/shadow_dom.py +0 -36
  938. graph_crawler-5.3.7/graph_crawler/shared/utils/text_utils.py +0 -387
  939. graph_crawler-5.3.7/graph_crawler/shared/utils/url_patterns.py +0 -135
  940. graph_crawler-5.3.7/graph_crawler/shared/utils/url_utils.py +0 -595
  941. graph_crawler-5.3.7/graph_crawler/shared/utils/user_agent_rotator.py +0 -333
  942. graph_crawler-5.3.7/graph_crawler/shared/utils/validation_helpers.py +0 -225
  943. graph_crawler-5.3.7/graph_crawler/shared/utils/visualization.py +0 -134
  944. graph_crawler-5.3.7/graph_crawler/shared/utils/visualization_core.py +0 -566
  945. graph_crawler-5.3.7/graph_crawler/tools/__init__.py +0 -1
  946. graph_crawler-5.3.7/graph_crawler/tools/db_cleanup.py +0 -263
  947. graph_crawler-5.3.7/graph_crawler.egg-info/PKG-INFO +0 -522
  948. graph_crawler-5.3.7/graph_crawler.egg-info/SOURCES.txt +0 -479
  949. graph_crawler-5.3.7/graph_crawler.egg-info/requires.txt +0 -125
  950. graph_crawler-5.3.7/pyproject.toml +0 -301
  951. graph_crawler-5.3.7/setup.cfg +0 -4
  952. {graph_crawler-5.3.7 → graph_crawler-5.4.1}/graph_crawler/py.typed +0 -0
  953. {graph_crawler-5.3.7 → graph_crawler-5.4.1}/graph_crawler.egg-info/dependency_links.txt +0 -0
  954. {graph_crawler-5.3.7 → graph_crawler-5.4.1}/graph_crawler.egg-info/entry_points.txt +0 -0
  955. {graph_crawler-5.3.7 → graph_crawler-5.4.1}/graph_crawler.egg-info/top_level.txt +0 -0
@@ -0,0 +1,637 @@
1
+ # Changelog
2
+
3
+ ## 5.4.1 — 2026-10
4
+
5
+ ### Fixed
6
+
7
+ - **A page whose script rewrites the address bar is no longer treated as a redirect** (Parser `WEB_GRAF_ISSUES.md` §5,
8
+ NEW-1-15). Playwright provenance took `page.url` as the final URL, so `history.pushState/replaceState`
9
+ (e.g. `/en/vacancies/<slug>` → `/en`) looked like a JS redirect and `Graph._handle_redirect_canonical` merged the
10
+ scanned page into the existing `/en` node — its content was dropped (`deduped by canonical-redirect: N`).
11
+ `_build_playwright_provenance` now compares `page.url` with the URL of the loaded document
12
+ (`performance.getEntriesByType('navigation')[0].name`): if they differ, the document URL is the final one
13
+ (no redirect, or the server-3xx target); the rewrite is logged at INFO
14
+ `Address rewritten by page script (history API), not a redirect`. Real redirects (server 3xx, `location.href`)
15
+ are unchanged; if the document URL cannot be read, the old behaviour is kept.
16
+ - Tests: `tests/transport/test_history_api_not_redirect.py` (real Chromium part: `GC_TEST_REAL_BROWSER=1`).
17
+
18
+ ### Changed
19
+
20
+ - **Non-network error policies go through `resilix` (new core dependency `resilix>=1.1,<2`, zero-dependency).**
21
+ - `shared/utils/best_effort.close_quietly` — one best-effort close with timeout for browser/context/page/CDP
22
+ and the persistent engine session (`BrowserFactory._safe_close`, `close_browser`, `detach_page_cdp`,
23
+ PID-probe CDP detach, `PoolLifecycle._stop_persistent_session`). Log format is unchanged
24
+ (`<name> close timeout (<t>s)` at WARNING, other errors at DEBUG, `TargetClosedError` is silent);
25
+ external `CancelledError` is not swallowed.
26
+ - Functions whose whole body was one `try → default` are now `@protect.when(..., return_=...)`
27
+ (playwright `browser_factory`, `cookie_consent`, nodriver driver, captcha balance checks, storages
28
+ `clear`/`save_partial`, `cgroup_limits`, `url_validator.is_url_safe`, …).
29
+ - `try/except Exception: logger.debug("Suppressed …")` blocks are now `protect.suppress(...)` with the
30
+ calling function name as the log message.
31
+ - Batch fetches (`fetch_many`, `fetch_many_fast`) and sitemap fan-out use `resilix.gather`: results keep input
32
+ order, a failed URL becomes an error `FetchResponse`, concurrency is capped by `limit=max_concurrent`, and
33
+ an external `CancelledError` now propagates instead of being returned as a result.
34
+ - Fire-and-forget tasks (async middleware `setup()`, dashboard broadcast, event publishing, sitemap session
35
+ close) use `resilix.spawn` — no lost tasks, failures are logged.
36
+ - `CODE_STYLE.md` §7.3/§7.11: non-network policies — only `resilix.protect`; network retries — only
37
+ `RetryMiddleware`/`RetryPolicy`; `protect.timeout` only for `async def`.
38
+
39
+ ### Removed
40
+
41
+ - Unused `graph_crawler.observability.decorators` (`retry`, `async_retry`, `timed`, `cached`, `log_calls`;
42
+ 357 lines, no imports anywhere).
43
+
44
+ ## 5.4.0 — 2026-10
45
+
46
+ ### Fixed
47
+
48
+ - **Oversized and binary responses are no longer downloaded in full** (Parser `WEB_GRAF_ISSUES.md` §1).
49
+ The dead `DEFAULT_MAX_CONTENT_LENGTH` constant is now enforced by `transport/core/body_limit.py` on every tier:
50
+ - HTTP tiers (aiohttp, curl_cffi — `fetch` and `fetch_fast`): a `Content-Length` above `max_response_bytes`
51
+ (default 10 MB) or a non-textual `Content-Type` (`application/octet-stream`, `video/*`, `application/zip`…,
52
+ `skip_non_textual_bodies=True`) skips the body entirely; without `Content-Length` the body is streamed in chunks
53
+ and the connection is dropped as soon as the limit is exceeded; decoded HTML is also capped by
54
+ `max_page_html_bytes` (default 15 MB);
55
+ - browser tiers: `max_page_html_bytes` caps the rendered HTML (Playwright incl. partial HTML after a timeout, and
56
+ nodriver); `max_page_total_bytes` (default 0 = off) caps the sum of all responses of a page and aborts further
57
+ non-document requests;
58
+ - a skipped body is returned as `html=None`, `error="response_too_large: …" / "non_textual_content: …"`,
59
+ `extras["body_skipped"]="too_large" | "non_textual"`; `EscalatingDriver` does not escalate it, forwards all
60
+ limit keys to its tiers, and the node gets `metadata["body_skipped"]` / `metadata["page_total_bytes_exceeded"]`;
61
+ - sitemaps keep their own larger limit: a sitemap skipped by the driver is refetched via the sitemap path.
62
+ - `0` disables any of the limits.
63
+ - `AsyncUndetectedConfig.sandbox` was declared but ignored (hard-coded `False`); it is now passed to `uc.start()`
64
+ (default changed to `False`, i.e. the effective behaviour is unchanged).
65
+ - Tests: `tests/transport/test_body_limits.py`.
66
+ - **Compressed sitemaps (`.gz`) are read completely** (Parser `WEB_GRAF_ISSUES.md` §2, NEW-3-9).
67
+ `SitemapParser._fetch_aiohttp` used a single `response.content.read(MAX_SITEMAP_SIZE)`, which returns only the bytes
68
+ already received (usually the first network chunk), so a 30–300 KB `.gz` sitemap was cut to a few KB, failed to
69
+ decompress and was logged as `Sitemap truncated (exceeded 52428800 bytes)`. The body is now read in chunks until EOF
70
+ or until the limit; `truncated` is set only when the file is really larger than the limit (the log line now also
71
+ shows the number of bytes read). A `Content-Length` above the limit still skips the body.
72
+ - New parameter `max_sitemap_bytes` in `crawl_sitemap` / `async_crawl_sitemap` / `SitemapSpider`
73
+ (`None` → `MAX_SITEMAP_SIZE` 50 MB, `0` → no limit); previously the limit could be set only on a hand-built
74
+ `SitemapParser`.
75
+ - Tests: `tests/test_sitemap_gz_streaming.py` (local server streams a 5 000-URL `.gz` in 4 KB chunks).
76
+ - **Redirect merge no longer logs `backend checkpoint skipped`** (Parser `WEB_GRAF_ISSUES.md` §3, NEW-3-19).
77
+ After merging a redirected node into an existing final node, `Graph._handle_redirect_canonical` called
78
+ `backend._run_async(backend.checkpoint())` without the `result_in_loop` argument added in 5.3.7, so every merge raised
79
+ `TypeError` (caught, logged as a warning, coroutine never run). The WAL checkpoint is not needed there —
80
+ `delete_node_sync` has already removed the node and its edges from the DB and the cache, and readers share the same
81
+ connection — so the call is removed. `SQLiteBackend._run_async` now defaults `result_in_loop=None`, which also fixes
82
+ the `Graph.remove_node` fallback for backends without `delete_node_sync`.
83
+ Tests: `tests/test_redirect_canonical_sqlite.py`.
84
+ - **A large JSON-LD block with a `JobPosting` is no longer dropped whole** (Parser `WEB_GRAF_ISSUES.md` §4, NEW-2-12).
85
+ A block larger than `max_jsonld_size` (100 KB) but not larger than `max_jsonld_oversized_size` (new, default 5 MB) is
86
+ parsed when a quick regex finds an object of a type from `oversized_keep_types` (new, default `["JobPosting"]`;
87
+ plain, list and `https://schema.org/…` forms of `@type`), and only those objects are kept — also from `@graph`,
88
+ arrays and nested `ItemList` items; the rest of the block is discarded. Other oversized blocks are skipped as before;
89
+ the warning now states the size and why (`no JobPosting inside` / `contains JobPosting but exceeds hard limit N` /
90
+ `JobPosting mentioned but no such object found`). `oversized_keep_types=[]` restores the plain size limit.
91
+ Tests: `tests/unit/plugins/node/structured_data/test_jsonld_oversized.py`.
92
+ - `LMDBEvictionStorage` had no `load_all_edges_sync`, so `Graph.load_all_from_disk()` with the LMDB backend (chosen
93
+ automatically when `lmdb` is installed) restored nodes but silently dropped evicted edges. Tests:
94
+ `tests/unit/test_p9_hotpath.py` (SQLite and LMDB).
95
+
96
+ ## 5.3.7 — 2026-10
97
+
98
+ ### Fixed
99
+
100
+ - **A crawl worker no longer dies on a canonical redirect while another SQLite statement is running.**
101
+ `SQLiteBackend._sync_execute` treated `sqlite3.OperationalError: cannot commit transaction - SQL statements in
102
+ progress` as a failed write, although the DELETE/UPDATE had already run in the open transaction of the shared
103
+ connection. The fallback `_run_async` then ran the coroutine on a new loop in a thread (`asyncio.Lock ... is bound to
104
+ a different event loop`) and, catching that `RuntimeError`, called `asyncio.run()` inside the running loop — the
105
+ worker failed (`Graph.remove_node` → `delete_node_sync`). Now:
106
+ - a commit refused because of in-progress statements is deferred: the change stays in the open transaction and is
107
+ committed by the next async commit or by `close()`;
108
+ - `_run_async` inside a running loop schedules the write as a task on that loop (errors are logged) instead of
109
+ blocking on a foreign loop; outside a loop it still runs synchronously;
110
+ - `close()` awaits scheduled writes and commits before closing.
111
+ - Tests: `tests/test_sqlite_sync_write_in_loop.py`.
112
+
113
+ ## 5.3.6 — 2026-06
114
+
115
+ ### Fixed
116
+
117
+ - **A timeout on robots.txt / sitemap stops the sitemap phase of the host (1.6, daxx.com).** A down origin behind
118
+ Cloudflare does not always answer 52x: robots.txt came back after ~20 s and every sitemap request hung until the
119
+ client timeout, then the default candidates (`sitemap.xml`, `sitemap_index.xml`) and the driver's retries/tier
120
+ escalation repeated it (80–180 s per domain). Now:
121
+ - `SitemapParser._fetch` bounds the whole request by `SitemapParser.timeout` (covers driver retries and tier
122
+ escalation); a timeout is an answer "origin does not respond", not a reason to try again;
123
+ - the first timeout (no status + timeout error, incl. the driver's `tier timeout after …`) or CF 520–527 marks the
124
+ host `ORIGIN_DOWN` (`SitemapParser.mark_origin_down` / `origin_down_reason`); no further request to that host in
125
+ this sitemap phase — no default candidates, no nested sitemaps, no retries;
126
+ - `result["origin_down_reason"]` = `"timeout"` | `"cf_52x"`; `SitemapSpider.origin_down`,
127
+ `get_stats()["origin_down"]`, `SITEMAP_CRAWL_COMPLETED.data["origin_down"]`, and the robots.txt node gets
128
+ `error_message = "origin_down: <reason>"`. A spider-level `SITEMAP_PARSE_TIMEOUT` also marks the host down.
129
+ - Non-timeout network errors (DNS, connection refused) keep the old behaviour (default candidates are tried).
130
+ Live: daxx.com sitemap phase 80 s → 50 s (robots ~20 s + one 30 s timeout), healthy sites unchanged.
131
+ - Tests: `tests/test_sitemap_origin_down_timeout.py` (8).
132
+
133
+ ## 5.3.5 — 2026-06
134
+
135
+ ### Fixed
136
+
137
+ - **Dirty `Sitemap:` value in robots.txt no longer loses the sitemap (P19).** `SitemapParser._parse_robots_txt`
138
+ took the whole directive value verbatim (`line[8:].strip()`), so `Sitemap: sitemap URL https://site.com/sitemap_index.xml`
139
+ was glued into a broken address and the sitemap was lost. It now extracts the first `https?://\S+` from the value;
140
+ `urljoin` is applied only when the value is a relative path starting with `/`. New `SitemapParser._clean_sitemap_value`.
141
+ - **A 404/410 is never a bot block (P19 side-finding).** `escalation._classify` returns `BotBlockStatus.NONE` for
142
+ status 404/410 right after the authoritative challenge headers. A dead sitemap candidate (or any not-found page) is
143
+ no longer classified as `captcha`/`challenge` and dragged through every tier (http→tls→browser). A real challenge
144
+ uses 403/503, not 404, so the authoritative header check still wins. Invariant: 404 is a definitive "not found"
145
+ from the server, not a page served instead of content. (After N3, verify & lock with a test — see Parser side.)
146
+ - **A redirect inside the same ATS no longer blocks `domain_filter` (P21).** `CrawlCoordinator._handle_redirect_after_scan`
147
+ always allows a redirect to the **same host** (short ATS forms `apply.workable.com/j/X` → `apply.workable.com/<slug>/j/X`).
148
+ For a different host it still asks `domain_filter.is_allowed`, and when that refuses it consults a new host-alias
149
+ extension point `DomainFilter.allow_redirect(source, target)` (driven by a settable `redirect_host_alias` hook) — so
150
+ the Parser can pass its `DOMAIN_POLICIES` and allow `boards.greenhouse.io` → `job-boards.greenhouse.io` without
151
+ opening the whole internet.
152
+ - **Sitemap under a Cloudflare 52x origin error stops without retries (1.6).** On the first `520`–`527` response to
153
+ robots.txt / sitemap, `SitemapParser` stops the sitemap phase and returns `result["origin_down"] = True` (logged as
154
+ `ORIGIN_DOWN`) instead of burning 100–180 s on retries/escalation against a down origin. New
155
+ `SitemapParser._is_origin_down`; `origin_down` is propagated through robots / default / nested sitemap paths.
156
+ - **Nodes evicted by low-memory eviction are no longer lost in a browser full rescan (P23).**
157
+ `Graph.iter_nodes_async(include_evicted=True)` now also yields nodes that were evicted to disk (read-only, without
158
+ pulling them back into RAM), so a merge that iterates the source graph sees them. `Node.pinned` + `Graph.pin_node()`
159
+ / `Graph.pin_seed_nodes()` keep seed (`depth == 0`) vacancies in RAM — they were the oldest and got evicted first.
160
+ `Graph.eviction_stats` exposes the graph's own eviction counters (a backend-less graph evicts to LMDB/SQLite, which
161
+ `backend.get_stats()` reported as 0).
162
+ - **A 403 storm without vendor markers is treated as a block (P24).** `EscalatingDriver` tracks a per-host window of
163
+ recent HTTP statuses; if a host that recently returned 2xx then returns 403 with no anti-bot markers and either ≥3
164
+ consecutive 403 or >30 % 403 in the window, the 403 is reclassified as `SOFT_BLOCK` (`escalation_reason=http_403_storm`)
165
+ and the tier escalates. A single 403 on a 2xx background is still treated as auth/method (no escalation). New
166
+ `_HostStatusWindow`.
167
+ - **The HTML enrichment pool is priority-aware (2.1 / §15.4).** `run_in_html_pool(fn, *args, priority=...)` admits the
168
+ highest-priority waiter first when the pool is saturated (job-URLs and career subdomains enrich before catalog/blog
169
+ pages); with a free slot and empty queue it behaves exactly like the old FIFO semaphore. `Node` passes its URL-rule
170
+ priority. New `_PrioritySemaphore`.
171
+ - **Cloudflare 520–527 is never a bot block.** `escalation._classify` returns `NONE` for 520–527 (as for 404/410):
172
+ the CF error page embeds `challenge-platform`, so a down origin was classified `challenge` and escalated
173
+ http→tls→browser (~20 s per tier) before `SitemapParser` could stop on `ORIGIN_DOWN`.
174
+ - **robots.txt after a domain move keeps the new host's sitemap.** The same-origin gate and relative `Sitemap:`
175
+ paths now use the host that actually served robots.txt (`final_url`): `kilo.health` → `kilo.co/robots.txt` with
176
+ `Sitemap: … https://kilo.co/sitemap_index.xml` was dropped as cross-origin even after P19 cleaning.
177
+ - **Cancelling a call waiting in the HTML-pool queue resumes the enrich budget.** With the priority semaphore the
178
+ acquire moved out of the `try`, so a cancellation in the queue left the node deadline suspended.
179
+ - **`Node.pinned` does not leak into `user_data['_custom_fields']`** (`NodeMapper`, SQLite eviction storage base
180
+ fields) — it broke DTO/user-data invariants (`test_reserved_user_data_keys`, `test_node_mapper_content_depth`).
181
+ - Tests: `tests/test_v535_fixes.py` (28, offline: P19, 404/52x, 1.6, P21 incl. end-to-end `async_crawl`, P23, P24, 2.1).
182
+
183
+ ### Added
184
+
185
+ - `crawl` / `async_crawl` / `GraphCrawlerClient.crawl` / `CrawlerConfig`: `redirect_host_alias=` —
186
+ `Callable[[source_url, target_url], bool]` installed on the spider's `DomainFilter.redirect_host_alias`
187
+ (the extension point was otherwise unreachable through the public API).
188
+ - `SitemapParser._clean_sitemap_value`, `SitemapParser._is_origin_down`; `result["origin_down"]` key.
189
+ - `DomainFilter.allow_redirect(source_url, target_url)` and settable `DomainFilter.redirect_host_alias` hook.
190
+ - `Node.pinned`; `Graph.pin_node`, `Graph.pin_seed_nodes`, `Graph.eviction_stats`; `iter_nodes_async(include_evicted=)`.
191
+ - `run_in_html_pool(..., priority=)`.
192
+
193
+ ### Fixed (carried from Unreleased)
194
+
195
+ - **Sitemap `<lastmod>` is no longer dropped.** `SitemapParser` reads `<lastmod>` of every `<url>` (with or without
196
+ namespace) into `result["lastmod"]` (`{url: value}`, merged across robots/default/nested sitemaps), and
197
+ `SitemapProcessor.create_url_nodes(lastmod=...)` stores it on the new `SitemapNode.lastmod` field (W3C datetime as
198
+ in the file). Consumers can now tell a 2021 post from a fresh page; `_parse_urlset` keeps its contract.
199
+
200
+ - **An embedded captcha widget on a page with content is not a bot block.** `classify_block` follows the same
201
+ principle as the browser `CaptchaDetectorPlugin`: a block is a page served *instead of* content. After the
202
+ authoritative challenge headers (`cf-mitigated`, `x-amzn-waf-action`, `x-vercel-mitigated`), a non-blocking
203
+ response whose `<body>` has ≥1000 visible characters or ≥15 links is `NONE`, whatever widget or protection
204
+ script it embeds. Previously visible text was measured over the first 64 KB of the document (in WordPress that is
205
+ almost all `<head>`), and `cf-turnstile` / `challenge-platform` (Cloudflare loads its JSD script on regular pages)
206
+ were unconditional markers — so every page with reCAPTCHA/hCaptcha/Turnstile in a contact or apply form was
207
+ `captcha`, escalated HTTP→TLS and pinned the whole host to the TLS tier. `cf-turnstile`, `captcha-container`,
208
+ `challenge-platform`, `_incapsula_resource`, `awswafintegration` now count only on pages without content or with a
209
+ blocking status; markers and text are read from `<body>` up to 1 MB.
210
+
211
+ ## 5.3.4 — 2026-10
212
+
213
+ ### Fixed
214
+
215
+ - **The per-node enrichment limit measures active work, not the HTML-pool queue.** `NodeScanner` wraps
216
+ `enrich_node` in `enrich_budget(seconds)` (`graph_crawler.domain.entities.node`): while a call waits for a thread
217
+ of the HTML executor in `run_in_html_pool` (parse, `_update_from_context`, hashes), the deadline is suspended and
218
+ resumed with the same remainder the moment the work starts. With 5.3.3 the hook and the hashes moved into the
219
+ shared pool, so under load a node's wall-clock depended on how many neighbours stood in front of it: a burst of
220
+ large pages produced a cascade of `Per-node timeout … marking as ERROR` for pages whose own processing took a
221
+ fraction of the limit (and every stage re-queued behind the others). A page whose **own** processing exceeds
222
+ `node_enrich_timeout` still times out and still returns its discovered links. The crawl-level timeout remains the
223
+ upper bound for a node that waits in the queue.
224
+ - **`IsolatedExecutor.run` admits at most `max_workers` tasks at a time** (`threading.BoundedSemaphore`): the task
225
+ `timeout` now measures execution in the child process, not the wait behind other tasks. Previously a burst of
226
+ submissions could expire the timeout of a queued task, which killed the whole pool and failed every task in flight.
227
+
228
+ - **Control commands are no longer dropped silently.** `GraphSpider.process_control_messages` executes only
229
+ `SPIDER_COMMANDS` (`STOP`, `PAUSE`, `RESUME`, `REPRIORITIZE`, `FORCE_VISIT`); any other `CrawlCommand` logs
230
+ `WARNING Unsupported control command '<value>' from <sender> is ignored (supported: ...)` once per command type
231
+ per crawl (repeats at `DEBUG`) and the crawl goes on. `REPRIORITIZE` without `url`/`priority` and `FORCE_VISIT`
232
+ without `url` log a `WARNING` with the received `data` instead of doing nothing.
233
+
234
+ ### Added
235
+
236
+ - `EnrichBudget` (`pause()` / `resume()`) and `enrich_budget()` in `graph_crawler.domain.entities.node`.
237
+ - `SPIDER_COMMANDS` (`graph_crawler.domain.interfaces`): the commands `GraphSpider` executes.
238
+
239
+ ### Deprecated
240
+
241
+ - `CrawlCommand.SKIP_URL`, `CHANGE_STRATEGY`, `SET_DEPTH_LIMIT`, `REQUEST_STATE`, `UPDATE_GOAL` were never executed
242
+ by the spider. They will be removed from the enum in 5.4.0.
243
+
244
+ ## 5.3.3 — 2026-10
245
+
246
+ ### Added
247
+
248
+ - **`IsolatedExecutor` / `IsolatedTaskError`** (`from graph_crawler import IsolatedExecutor`). Runs CPU-bound functions
249
+ in a `spawn` process pool: a native crash (SIGSEGV in libxml2/lxml, trafilatura, any C extension) or a hang of a
250
+ worker becomes `IsolatedTaskError(reason="crashed" | "timeout" | "unavailable")` in the caller, the pool restarts
251
+ and the crawl goes on. Tasks that shared the broken pool are retried once; a hung worker is killed after
252
+ `timeout` (default `DEFAULT_ISOLATED_TASK_TIMEOUT` = 30 s). Ordinary exceptions of the function propagate as is.
253
+ - **`CrawlerConfig.node_enrich_timeout`** and `crawl(..., node_enrich_timeout=...)` / `async_crawl(...)` /
254
+ `GraphCrawlerClient.crawl(...)`: per-node enrichment limit (HTML parse, plugins, subclass hook, hashes).
255
+ `None` keeps `DEFAULT_NODE_ENRICH_TIMEOUT` (45 s, `shared.constants`). `NodeScanner(driver, enrich_timeout=...)`.
256
+ - `run_in_html_pool(fn, *args)` (`graph_crawler.domain.entities.node`): runs `fn` in the HTML executor under its
257
+ semaphore. `Node.pop_discovered_links()` returns links extracted on `ON_HTML_PARSED` of an interrupted
258
+ `enrich_node`.
259
+ - `URLRuleMatcher.rule_matches(rule, url)`.
260
+
261
+ ### Changed
262
+
263
+ - **The `Node._update_from_context` hook and `get_content_hash`/`get_simhash` run in the HTML executor** under the
264
+ same semaphore as HTML parsing (they used the default `asyncio.to_thread` pool, `min(32, cpu+4)` threads, outside
265
+ the CPU budget). Concurrency of heavy subclass code is now bounded by `GRAPH_CRAWLER_HTML_PARSER_WORKERS`
266
+ (default — the cgroup CPU quota); `=1` serialises it completely. Code that is not thread-safe must use its own
267
+ lock or `GRAPH_CRAWLER_HTML_PARSER_WORKERS=1`; native code that can crash — `IsolatedExecutor`.
268
+ - **One URL rule — one decision.** `CrawlScheduler`, `LinkProcessor` and `RuleBasedScheduler` share
269
+ `URLRuleMatcher`: first match in list order, `SmartURLRule` is matched against its `scope` (the scheduler used to
270
+ run `re.search` over the full URL, so `SmartURLRule(r"/help", scope=PATH, should_scan=False)` excluded
271
+ `https://helpware.com/`). `GraphSpider` no longer copies `url_rules` with `should_scan=False` into `PathFilter`
272
+ (it applied them to the path, ignoring order and scope); `excluded_paths` / `included_paths` are unchanged.
273
+ - A URL rule whose `priority` is outside `[MIN_URL_PRIORITY, MAX_URL_PRIORITY]` (`[-100, 100]`) gets a WARNING when
274
+ the scheduler is created: `priority` is a queue position and is clamped; rule precedence is list order.
275
+ - `IDriver.fetch(url, *, referer, extra_headers, cookies)` is the contract of every driver: `BaseDriver`,
276
+ `EscalatingDriver` (caller cookies/headers merged with the host session — the session wins on a cookie name
277
+ clash, the session `User-Agent` is applied on top for HTTP/TLS tiers), `PooledPlaywrightDriver` (`extra_headers`),
278
+ `IAsyncDriver`, `IBrowserDriver`. `BaseDriver.fetch_many` passes per-URL `extra_headers_list` / `cookies_list` to
279
+ `fetch` (they were dropped).
280
+
281
+ ### Fixed
282
+
283
+ - **Per-node timeout no longer drops discovered links.** `scan_node` / `scan_batch` return the links extracted on
284
+ `ON_HTML_PARSED` when enrichment exceeds the limit; the node is still `ERROR` / `FAILED`. Large listing pages, the
285
+ first to hit the limit, keep their edges.
286
+
287
+ ## 5.3.2 — 2026-06
288
+
289
+ ### Added
290
+
291
+ - **Fetch provenance on the node.** `Node.channel`, `bot_block_status`, `served_by_driver`, `served_by_engine`,
292
+ `escalations` and `fetch_info` read `metadata["fetch"]`; `Node.ok` (scanned, 2xx/3xx, not blocked, no error) and
293
+ `Node.is_blocked` separate useful pages from block and error pages.
294
+ - **Plugin routing.** `BaseNodePlugin.domains` (domain and subdomains) and `url_pattern` (regex), on the class or via
295
+ `config={"domains": [...], "url_pattern": ...}`, plus an overridable `applies_to(url)`. An invalid `url_pattern`
296
+ raises `ConfigurationError` on `register()`. Several instances of one plugin class with different routes run
297
+ side by side (they used to replace each other).
298
+ - **Typed plugin output.** `NodePluginContext.set_node_field(name, value)` writes a field declared by a `Node`
299
+ subclass, validated against its annotation (`"100"` → `int`); base fields are read-only. Fields are rolled back
300
+ together with `metadata`/`user_data` when the plugin fails.
301
+ - **Empty extraction warning.** `BaseNodePlugin.expects_nonempty` + `extraction_key`: a WARNING when the plugin left
302
+ `user_data[extraction_key]` empty.
303
+ - **`Graph.search(..., user_data_keys=("jobs",))`** searches the listed `user_data` keys as well.
304
+ - **`Node.public_user_data`** and `RESERVED_USER_DATA_KEYS` (`_link_anchors`, `_custom_fields`, `_node_class`).
305
+
306
+ ### Changed
307
+
308
+ - A response with status 400 or higher gets `metadata["error"] = "HTTP <code>"` and is logged as a WARNING
309
+ (`HTTP 404 for <url> — page not scanned`).
310
+ - A bare 403/401/405 without vendor markers is still classified as `none`; the decision is now logged at INFO.
311
+ - Quieter logs: import writes nothing at INFO, platform detection runs once, the start of a crawl is one line,
312
+ and each page is one INFO line with `[channel=…]`, `[block=…]`, `[error=…]`.
313
+ - The temporary `_link_anchors` map is removed from the node once its edges are created (the anchor text stays in
314
+ `edge.metadata["anchor_text"]`). The default JSON export writes `public_user_data`.
315
+ - A third-party plugin that overwrites a reserved `user_data` key gets a WARNING.
316
+
317
+ ### Fixed
318
+
319
+ - `load_graph()` without `node_class=` restores a custom `Node` subclass when its module is already imported (the
320
+ class name is stored with the node; nothing is imported from the file). `Graph.copy()`, `union`, `intersection`,
321
+ `difference` and nodes created in a loaded graph keep the node class.
322
+ - `metadata` written by a plugin on `ON_NODE_CREATED` or `ON_AFTER_SCAN` is no longer lost.
323
+ - **Placeholder lists are detected as a JavaScript shell.** A 2xx body with five or more `placeholder` /
324
+ `skeleton` / `shimmer` elements makes `needs_browser` true, so `escalating` goes straight to the browser tier.
325
+ With `driver="http"` the node gets `metadata["js_shell_suspected"]` and a WARNING (once per host) suggests
326
+ `driver="escalating"` or `"playwright"`.
327
+ - **No duplicate edges.** A link repeated on a page (menu + footer + body) created a new `Edge` each time
328
+ (17.5 % of edges on uk.wikipedia); `LinkProcessor` now skips existing `(source, target)` pairs. `Graph.has_edge`
329
+ also sees the backend edge index.
330
+ - **`h1` children are no longer joined with spaces** (`Портал : Поточні події` → `Портал:Поточні події`).
331
+ - **Default ports are normalized** (`http://x:80`, `https://x:443`, empty port).
332
+ - `Graph.normalize_url()` is public; `LinkProcessor` checks the scheduler with the canonical form as well.
333
+ - **`NetworkLogPlugin` waits for network quiet** before publishing the log (`settle_ms`=2000, `quiet_ms`=300;
334
+ `settle_ms=0` restores the old behaviour) — late XHR (search/listing after hydration) was lost.
335
+ - `create_trailing_slash_rule` keeps the root path; an empty path is `/` for PATH-scope rules.
336
+
337
+ ## 5.3.1 — 2026-06
338
+
339
+ ### Added
340
+
341
+ - **Hydration state globals snapshot.** The Playwright driver serialises `window.__NAME__` objects (`__NUXT__`,
342
+ `__APOLLO_STATE__`, `__INITIAL_STATE__`, `__PINIA__`, …) with `JSON.stringify` after the page has run and embeds
343
+ them into the HTML snapshot as `<script type="application/json" data-agc-state="__NAME__">`. Nuxt 2 ships its
344
+ state as an IIFE (`window.__NUXT__=(function(a,b){return {…}}(…))`) which is JavaScript, not JSON, and was
345
+ invisible to any static state parser. Globals that already exist as a JSON `<script id="__NAME__">`
346
+ (`__NEXT_DATA__`) are not duplicated; `PageFetcher.state_globals_max_chars` (5 MB) caps the snapshot.
347
+ `inject_state_globals(html, states)` is exported for tests and other drivers.
348
+
349
+ ## 5.3.0 — 2026-06
350
+
351
+ ### Added
352
+
353
+ - **Page actions as public API.** `driver_config={"actions": [PageAction(...) | dict, ...]}` for the Playwright driver:
354
+ `click` / `fill` / `select` / `press` / `wait_for(response=…|selector=…)` / `scroll`, target by `selector`, `text`,
355
+ `placeholder` or `label` (`nth` among visible matches). Actions are validated when the driver is created and run
356
+ after content is ready and before the HTML snapshot and `CONTENT_READY` hooks — the network log is published after
357
+ the actions. Results land in `node.metadata["actions_log"]`, `["requests_after_actions"]` and `["actions_error"]`
358
+ (the first failing action stops the chain with a reason). `from graph_crawler import PageAction`.
359
+ - `engine.page_error_types()` — page error classes of all installed browser SDKs.
360
+ - `PageAction(type="back")`; action errors name the element that intercepts pointer events.
361
+
362
+ ### Fixed
363
+
364
+ - Binary request bodies (gzip beacons) no longer raise `UnicodeDecodeError` inside Playwright event listeners
365
+ (`NetworkLogPlugin` and the action recorder read `post_data_buffer` with `errors="replace"`); the error used to break
366
+ the next Playwright call with `TypeError: function takes exactly 5 arguments`.
367
+
368
+ ### Fixed
369
+
370
+ - `graph.get_node_by_url(seed)` finds the node after a redirect in the graph returned by `async_crawl`: `add_node`
371
+ restores the alias from `metadata["was_redirected_from"]`.
372
+
373
+ ## 5.2.2 — 2026-06
374
+
375
+ ### Fixed (test infrastructure)
376
+
377
+ - **The library no longer configures logging.** `GraphCrawlerClient.__init__` called `logging.basicConfig(INFO)`
378
+ when its logger had no handlers, so every crawl printed to stderr and reconfigured the application's root
379
+ logger. Removed; `graph_crawler` gets a `NullHandler`.
380
+ - **Noise demoted:** `HTML executor shutdown` and `lmdb not installed … Falling back to SQLiteEvictionStorage` are
381
+ DEBUG; `channel='chrome' not installed, falling back to bundled Chromium` is a WARNING once per process.
382
+ - **One version source.** `graph_crawler.__version__` (and the default User-Agent) come from the installed
383
+ package metadata (`pyproject.toml`); `__version__.py` no longer hard-codes a number that drifted (5.1.0 vs 5.2.0).
384
+ - **Test suite collects without optional extras.** Tests that need `psutil`, `rapidfuzz`, `stop-words`,
385
+ `html2md-clean` or `playwright` are skipped via `importorskip` instead of failing collection under `-x`.
386
+ `test_div_inline_fix.py` / `test_phase1_fixes.py` tested `html2md_clean` itself (no `graph_crawler` code) and were
387
+ removed from the core suite.
388
+ - GitLab CI (`.gitlab-ci.yml`): offline suite with `[dev]` and a minimal job without extras.
389
+
390
+ ## 5.2.1 — 2026-06
391
+
392
+ ### Fixed (freelance cases)
393
+
394
+ - **Captured XHR/fetch traffic could not be replayed outside the browser.** `NetworkLogPlugin` recorded the
395
+ URL and method of a request but not its body, so every POST/GraphQL endpoint (robota.ua lists vacancies via
396
+ `POST dracula.robota.ua/?q=getPublishedVacanciesList`) was unusable: a GET of the same URL returns 400.
397
+ Records now carry `post_data` and (on `response`) `request_headers`.
398
+ - **GraphQL responses overwrote each other.** `body_ref` was `sha1(url|status|content-type)` — all operations
399
+ POSTed to one `/graphql` URL shared a key and only the last body survived. The key now includes method and
400
+ request body.
401
+ - **JSON family by default.** The capture allow-list matched exact MIME types only; `application/graphql-
402
+ response+json`, `problem+json`, `vnd.api+json`, `text/json` were dropped. Without an explicit
403
+ `capture_content_types` every `*/json` and `*+json` type is captured (RFC 6839). Default `max_body_kb` 64 → 256
404
+ (listing APIs routinely exceed 64 KB).
405
+ - **Shadow DOM was invisible.** `page.content()` serializes only light DOM: content of open shadow roots (web
406
+ components, embedded price widgets) never reached the node. When a page has open shadow roots, the browser
407
+ driver serializes it with `Element.getHTML({shadowRoots})` and expands the declarative shadow roots into their
408
+ hosts (`shared/utils/shadow_dom.expose_shadow_roots`) — Lexbor, like the HTML spec, does not descend into
409
+ `<template>` content, so a plain declarative-shadow-DOM serialization would still hide it from selectors and link
410
+ extraction. Pages without shadow roots are untouched.
411
+ - `tests/unit/domain/entities/test_graph_new_features.py` still expected the `DeprecationWarning` that
412
+ `export_nodes()` stopped emitting in 4aab7525; it now asserts the opposite.
413
+
414
+ ### Fixed (real-site tests)
415
+
416
+ - **Ctrl+C during `crawl()` hung until the crawl-level timeout** (28 s with `timeout=25`; forever without
417
+ one). `asyncio.wait_for` in Python 3.11 swallows the outer cancellation when the awaited coroutine finishes
418
+ at the same moment, so some workers survived `KeyboardInterrupt` and kept crawling. `NodeScanner` and the
419
+ escalation tier budget now use `asyncio.timeout()`; `CrawlCoordinator` cancels its workers on
420
+ `CancelledError`. Ctrl+C now returns in ~0.3 s.
421
+ - **`same_domain=False` was ignored** — without `allowed_domains` the `DomainFilter` default
422
+ (`domain+subdomains`) still applied and external links never entered the graph. Now maps to `allowed_domains=["*"]`.
423
+ - **`graph.metadata["stop_reason"]` was `None` after `timeout=` / `max_pages`** — only stop conditions were
424
+ recorded. Now `"timeout"`, `"max_pages"` or `"limit"`.
425
+ - **Network errors were invisible on the node**: a DNS/connection failure left `response_status=None`,
426
+ `scanned=True` and no message; `get_failed_nodes()` returned `[]`. The error text is now in
427
+ `node.metadata["error"]`, the HTTP status (if any) is kept, and `get_failed_nodes()` with default
428
+ arguments includes network-error nodes.
429
+ - **`storage="json", storage_config={"path": "site.json"}` was silently ignored** (file written to
430
+ `crawler_data/json/graph.json`). `path` is now honoured, like for sqlite (`JSONStorage(file_name=)`).
431
+ - `Graph.export_nodes()` / `Graph.export_edges()` are no longer deprecated: they are the public one-liners the
432
+ README shows and now delegate to `GraphExportUseCase` silently (no `DeprecationWarning`).
433
+ - `tests/manual/real_sites/run_all.py -j N` runs scenarios in parallel; `tests/manual/real_sites/test_user_cases.py`
434
+ — 23 pytest user scenarios on Wikipedia / robota.ua / GitHub / example.com, each hard-capped at 40 s.
435
+
436
+ ### Changed
437
+
438
+ - **Engine contract verified end-to-end**. `tests/transport/
439
+ test_engine_contract.py` drives the real `PlaywrightDriver.fetch()` through three engines registered via the
440
+ public `register_engine()` on a recording fake SDK: the call sequence is byte-for-byte identical across
441
+ engines, stealth is injected iff `provides_stealth is False`, and `executable_path`/`ignore_default_args`/
442
+ launch args come only from the engine. No runtime code changed.
443
+ - **`LexborDocument.clean_text()` no longer re-parses the page**. To strip `script/style/noscript/svg/head` without mutating the shared tree it
444
+ serialised the document and built a second `LexborHTMLParser` — i.e. every page with the default
445
+ `TextExtractorPlugin` was parsed twice (`count_parses.py`: 2.0 → **1.0** per page; with `MarkdownPlugin`
446
+ 1 parse + 1 clone). It now uses `tree.clone()`.
447
+ - **One `ContentDecoder` for every HTTP tier**. `AsyncDriver` (aiohttp) and `AsyncCurlCffiDriver` (curl_cffi) no longer rely on the client's own
448
+ `response.text`/`.text()`: they read the raw body and decode it through `ContentDecoder`
449
+ (`Content-Type` charset → `<meta charset>` → BOM → chardet → strict utf-8). Pages whose charset is only
450
+ declared in `<meta>` (e.g. windows-1251 without a header) were previously decoded as utf-8 by aiohttp,
451
+ failed, and silently became `html=None` with a "successful" 200. An undecodable body is now reported as
452
+ `FetchResponse.error` (`"ContentDecoder: cannot decode body …"`) with the status code preserved;
453
+ `ConnectionPoolManager.fetch_text()` goes through the same decoder. `FetchResponse.html` is a strict
454
+ `str` field — passing `bytes` raises `ValidationError` instead of being coerced. Browser tiers
455
+ (Playwright `page.content()`, nodriver `tab.get_content()`) already return `str`. Contract test:
456
+ `tests/transport/test_html_is_str_contract.py` (driver matrix returns `str`; cp1251 via meta/header;
457
+ binary → error).
458
+ - **Lazy public facade**. `graph_crawler/__init__.py` now
459
+ eagerly imports only `crawl`, `async_crawl`, `Graph`, `Node`, `save_graph`, `load_graph`,
460
+ `BaseStopCondition`; every other name in `__all__` (drivers, storages, exceptions, settings, views,
461
+ celery tasks, …) resolves on first access via module `__getattr__` and is cached. `aiohttp` is no longer
462
+ loaded by `import graph_crawler`. `bootstrap()` no longer calls `collect_system_metrics()`
463
+ (`psutil.cpu_percent(interval=0.1)` blocked import for 100 ms for a DEBUG line). Import time here:
464
+ 470 ms → 228 ms. Optional names (`PlaywrightDriver`, `celery`, `EasyDistributedCrawler`, …) still return
465
+ `None` without their extra.
466
+ - **Escalation re-probe replays the solving tier's User-Agent**.
467
+ Playwright and nodriver now export `navigator.userAgent` as `extras["user_agent"]`;
468
+ `SessionState.absorb` records it (pinned once the session is solved, like `impersonate`/`proxy`) and
469
+ `EscalatingDriver._fetch_tier` sends it as `extra_headers={"User-Agent": …}` to HTTP/TLS tiers together
470
+ with the stored cookies. `cf_clearance` is bound to UA+IP+JA4, so a re-probe on Tier 1 with aiohttp's
471
+ default UA was rejected and the host never descended from the browser tier.
472
+
473
+ ### Removed
474
+
475
+ - **`NodePluginContext.parser`**. Node plugins now read the
476
+ single Lexbor tree through the `HTMLDocument`/`HTMLNode` protocols: `LinkExtractorPlugin`
477
+ (`doc.css("a[href]")`, `doc.base_href()`), `MetadataExtractorPlugin`, `TextExtractorPlugin`
478
+ (`doc.clean_text()`, new in the protocol), `StructuredDataPlugin` (OG/Twitter/Microdata/RDFa parsers take
479
+ an `HTMLDocument`; `StructuredDataExtractor.extract(html=, doc=)`), `Email`/`PhoneExtractorPlugin`.
480
+ Migration: `context.parser.find_all(sel)` → `context.html_tree.css(sel)`, `.find()` → `.css_first()`,
481
+ `el.get_attribute()` → `el.attr()`. Fixed along the way in `LexborNode`: `attr()` returns `""` (not
482
+ `None`) for valueless boolean attributes such as `itemscope`, so nested Microdata/RDFa items are now
483
+ detected; `css()`/`css_first()` return descendants only (selectolax included the node itself, which made
484
+ Microdata recurse to `max_nesting_depth`).
485
+ - **`PooledPlaywrightDriver` legacy batch path**: `batch_legacy.py`
486
+ (`LegacyBatch._fetch_batch_legacy`, `_fetch_chain`) is gone. `persistent_browser=False` keeps its
487
+ "launch/stop per batch" semantics through the pool path: after a batch, once no fetch is in flight, the
488
+ pool and the engine session are closed (`_close_pool()` + `_stop_persistent_session()`, shared with
489
+ `close()`). Dead `self.playwright`/`self.browsers` cleanup removed from `close()`.
490
+ - **Escalation.** `classify_block()` rewritten to the
491
+ 6.0 decision table: challenge/captcha vendor headers first, then unambiguous body captcha/challenge
492
+ markers (ahead of vendor hard-block headers), then short/blocking-page widgets, status-based verdicts
493
+ and empty-2xx. A bare `401/403/405` with no vendor markers is no longer a block. On the classifier corpus
494
+ it goes 25/37 → **37/37**.
495
+
496
+ - `needs_browser` heuristic (SPA shell: little visible text + SPA root / `<noscript>` JS hint / no
497
+ `<a href>`) is now the default `EscalationPolicy.needs_browser`; on a clean HTTP/TLS response it jumps
498
+ straight to the first browser tier (`escalation_reason="needs_browser"`).
499
+ - Per-tier `TierTimeouts` + `escalation_deadline_s` (60s) budget: a tier is skipped (last response
500
+ returned, browser never started) when the remaining budget is below its `min_budget`; each tier fetch
501
+ is wrapped in `asyncio.wait_for`. New `PER_NODE_FETCH_TIMEOUT` constant with a validator warning.
502
+ - Cross-tier session: `SessionState` / `HostSessionStore` carry solved cookies (`cf_clearance`, `__cf_bm`,
503
+ `datadome`, …) forward and back down to cheaper tiers on re-probe; `impersonate`/`proxy` are pinned once
504
+ a session is solved. curl_cffi pins the impersonate fingerprint; Playwright/nodriver export context
505
+ cookies via `extras["cookies"]`.
506
+ - `extras` contract: every driver stamps `channel`/`driver`/`engine` (via `BaseDriver`/`BaseAsyncDriver`
507
+ `_stamp_extras` + `_finalize_response`) even outside escalation.
508
+ - New Prometheus metrics `crawl_fetch_duration_seconds{channel,engine}`, `crawl_escalations_total`,
509
+ `crawl_escalation_deadline_hits_total`.
510
+
511
+ - **HTML parsing: Lexbor (selectolax) is the only core parser**.
512
+ `beautifulsoup4`, `lxml`, `lxml_html_clean` removed from `[project].dependencies`;
513
+ `BeautifulSoupAdapter`, `LxmlAdapter`, `ScrapyAdapter` deleted. New `HTMLDocument` / `HTMLNode`
514
+ protocols in `domain/value_objects/html_document.py`, implemented by `LexborDocument` in
515
+ `infrastructure/adapters/selectolax_adapter.py`. `<template>` is renamed to `gc-template` before
516
+ parsing so Vue/React SSR links inside templates are not lost. `IParser.parse()` returns `HTMLDocument`;
517
+ `NodePluginContext.html_tree` is now a `LexborDocument`. `get_parser()` accepts `auto|selectolax|lexbor`.
518
+ - `HTMLUtils.extract_links/extract_text/extract_metadata` accept an `HTMLDocument` (string input still
519
+ works but is deprecated).
520
+ - `SitemapParser.parse_large_sitemap_streaming()` uses stdlib `iterparse` by default; lxml is an
521
+ optional fast-path via the new extra `sitemap-fast`.
522
+
523
+ - **`html2md-clean` moved to the optional extra `markdown`** (`pip install "graph-crawler[markdown]"`);
524
+ `MarkdownGenerator/MarkdownOptions/MarkdownResult` re-exports in `graph_crawler.shared.utils` are lazy.
525
+ `MarkdownPlugin` feeds the already-parsed Lexbor tree to html2md-clean 0.5 (`generate_from_tree`) —
526
+ one parse per page instead of 2–4. `import graph_crawler` no longer loads bs4/lxml.
527
+
528
+ - **Dependency map**. Core is now 10 packages: `requests`,
529
+ `httpx`, `pydantic-settings`, `pybloom-live`, `fastapi`, `tenacity`, `stop-words`, `tiktoken`,
530
+ `markdown-it-py` removed from `[project].dependencies`. New extras: `browser` (`playwright` kept as alias),
531
+ `server` (fastapi/uvicorn/websockets), `text` (tiktoken/stop-words), `captcha`; `markdown` also pulls
532
+ `markdown-it-py`; `all` is recursive. Extras `cloudscraper` and `undetected-sync` dropped.
533
+ Extra-gated imports are lazy and fail fast with `requires extra '<name>'`; `transport.PlaywrightDriver/
534
+ RequestsDriver/CloudscraperDriver/RequestsSessionAdapter` are lazy re-exports (ImportError instead of `None`).
535
+ `CrawlerSettings` keeps `GC_*`/`__`/`.env` without pydantic-settings; `CrawlerConfig` is a plain `BaseModel`.
536
+
537
+ - **Transport cleanup**. Deleted: sync drivers
538
+ (`RequestsDriver`, `CloudscraperDriver`, `UndetectedChromeDriver`, `BotasaurusDriver`, `BaseSyncDriver`),
539
+ `AsyncDriverHTTP2` (httpx), `AsyncCloudscraperDriver`, `tls_client.py`, `StealthHTTPDriver`,
540
+ `session_manager/session_adapters/session_protocol`, `shared/utils/captcha_bypass.py`,
541
+ `shared/utils/proxy_manager.py`, packages `transport/http` and `transport/browser`.
542
+ `DriverType` is now `aiohttp | tls | playwright | nodriver | escalating` (+ `asyncio` alias);
543
+ strings `http`/`async`/`asyncio` → `aiohttp` and `stealth`/`curl_cffi` → `tls` are accepted for one
544
+ release with `DeprecationWarning`. One `AsyncDriver` (former `driver_adaptive.py` is now
545
+ `async_http/driver.py`); request hints (`referer`, `extra_headers`, `cookies`) are honoured.
546
+ `requests` is gone from the package: proxy health-check, CAPTCHA solvers/bypass and `SitemapParser`
547
+ use aiohttp (`SitemapParser.parse_sitemap/parse_from_robots` are `asyncio.run` wrappers,
548
+ `http_client=` is deprecated and ignored). `BloomFilter` is pure-python (xxhash) — `pybloom-live`
549
+ no longer needed.
550
+ `import graph_crawler` no longer loads any extra (guarded by `tests/unit/test_import_surface.py`), 3.8 s → 1.7 s here.
551
+ `shared/constants.py` no longer imports aiohttp — `br`/`zstd` are advertised
552
+ by `transport.compression.runtime_browser_headers()` only when the runtime can decode them;
553
+ one driver registry — `application.services.driver_factory` delegates to
554
+ `transport.factory.DriverFactory` (`register_factory`, `registered_names`); `SitemapParser(driver=)`
555
+ fetches robots.txt/sitemaps through the crawl `IDriver` (same tier chain), `SitemapSpider` passes its
556
+ driver, aiohttp is used directly only for binary `.gz`; `tests/transport/test_all_drivers_report_redirects.py`
557
+ is an `IDriver` acceptance matrix (aiohttp / tls / fake playwright / fake nodriver / escalating).
558
+
559
+ - **Escalation**: `EscalationPolicy.reprobe_interval` (5 min) —
560
+ one request per interval re-probes tier 0 for a host remembered on a higher tier (no herd of browser
561
+ re-escalations after TTL); `EscalationPolicy.max_hosts` (10k) — `HostTierMemory` is an LRU and drops
562
+ per-host locks together with entries. In strict mode (`GC_ENGINE_STRICT`) an `ImportError` raised _during_
563
+ `fetch` (lazy browser start without the SDK) now propagates instead of being turned into a network error.
564
+
565
+ - **`extras` contract persisted.** New
566
+ `FetchResponse.fetch_provenance()` / `FETCH_EXTRAS_KEYS`: a JSON-safe slice
567
+ (`bot_block_status, channel, driver, engine, escalations` + `escalation_reason`; cookies/impersonate dropped).
568
+ `NodeScanner` stores it in `node.metadata["fetch"]` for every response, including blocked/error ones, so it
569
+ survives JSON/SQLite/MongoDB/PostgreSQL storage. Contract test `tests/transport/test_extras_contract.py`.
570
+
571
+ ### Fixed
572
+
573
+ - Transport `extras` (`bot_block_status`, tier, escalations) were dropped after fetch and never reached the
574
+ node or storage; for blocked pages nothing was transferred at all.
575
+ - `SmartPageFinder` read `html_tree.text` as a property; with Lexbor it is a method — page text was
576
+ silently dropped.
577
+ - `AsyncUndetectedDriver` accepts a plain `dict` config (`AsyncUndetectedConfig.from_mapping`) — building
578
+ the `nodriver` tier through `DriverFactory.create(DriverType.NODRIVER, config={...})` (as `EscalatingDriver`
579
+ does) raised `AttributeError`.
580
+
581
+ ## 5.2.0 — 2026-06
582
+
583
+ ### Added
584
+
585
+ - `infrastructure/transport/escalation.py`: `classify_block()` (Cloudflare / Akamai / DataDome /
586
+ PerimeterX / Incapsula markers, 403/429/503, empty 200), `EscalationPolicy`, `EscalationTier`,
587
+ `EscalatingDriver` — chain http → tls → browser → undetected with `max_tier`.
588
+ `driver="escalating"` works in `crawl()` / `async_crawl()` / `Crawler` / CLI.
589
+ - `HostTierMemory`: adaptive per-host tier memory — TTL from the moment of decision
590
+ (`GC_TIER_TTL`, default 1200 s), invalidation when the remembered tier gets blocked,
591
+ one escalation per host at a time (per-host lock). Per process.
592
+ - Strict engine mode: `GC_ENGINE_STRICT=1`, `{"browser_engine": {"strict": True}}` or
593
+ `driver_config={"engine_strict": True}` — `ImportError` instead of a silent fallback
594
+ `cloakbrowser → patchright → playwright` or a skipped escalation tier.
595
+ - Browser engine registry in `playwright/engine.py`: `register_engine()`, `resolve_engine()`,
596
+ `registered_engines()`. Built-in engines `cloakbrowser` → `patchright` → `playwright` with
597
+ automatic fail-open. `GC_BROWSER_ENGINE` env selects the engine when config has none.
598
+ `PlaywrightBrowserEngine.effective_name` / `fallback_reason`.
599
+ - `FetchResponse.extras` contract: `bot_block_status`, `channel`, `driver`, `engine`, `escalations`.
600
+ - `DriverType.TLS`, `NODRIVER`, `UNDETECTED`, `BOTASAURUS`, `ESCALATING`; `DriverFactory.create()`
601
+ accepts extra constructor kwargs (`policy=`).
602
+ - Extras: `tls`, `stealth`, `cloak`, `undetected`, `undetected-sync`, `antidetect`, `cloudscraper`, `system`.
603
+ - `README.md`, `CHANGELOG.md`.
604
+
605
+ ### Changed
606
+
607
+ - `normalize_protocol` defaults to `False`: `http://` and `https://` are different URLs.
608
+ The crawler follows the real redirect instead of rewriting the scheme itself, so
609
+ http-only hosts stay reachable. Enable it explicitly to merge both schemes on dedup.
610
+ - `Graph.handle_redirect()` stores the alias as the real `Location` the server sent
611
+ (provenance); the normalized form is used only to find and merge nodes.
612
+ - Single `DriverType` enum (`transport/base.py`); `factory.py` re-exports it.
613
+ - `cloudscraper` moved from required dependencies to extra `cloudscraper`.
614
+ - `psutil` is optional (`system` extra); `MemoryGuard` imports it lazily.
615
+ - `requires-python = ">=3.11"` (upper bound removed; classifiers 3.11–3.14).
616
+ - `pytest` runs offline by default (`-m 'not manual'`, no coverage in `addopts`).
617
+ Network / real-site tests live in `tests/manual/` and run with `pytest -m manual tests/manual`.
618
+ - Project URLs point to the GitLab repository.
619
+
620
+ ### Fixed
621
+
622
+ - `pooled_driver`: bounded walk over `redirected_from` (infinite loop on objects without a
623
+ `None` terminator); cookies are no longer added twice to a fresh browser.
624
+ - Tests synced with the code: redirect aliases, `html2md-clean` 0.4.0 API, `crawl_coordinator`
625
+ `per_host_max_in_flight`, `.gitignore` lookup at the repository root, `tools/db_cleanup.py`
626
+ CLI allow-listed for `print`. Playwright fake tests skip when `playwright` is not installed.
627
+
628
+ ### Removed
629
+
630
+ - `tests/345.py`, `tests/ДОДАТКОВІ_ТЕСТИ.py`, `tests/manual_4a_games_test.py`, `tests/google_maps_results.json`.
631
+ - `EXTRACTION_AUDIT.md` and other analysis / plan documents that described work, not the code.
632
+ - `graph_crawler/data/ARCHITECTURE.md`, `MIGRATION_50_STEPS.md`, `REFACTORING_PLAN.md`, `mkdocs.yml`
633
+ (finished plans / config without content).
634
+
635
+ ## 5.1.0
636
+
637
+ Previous release (sync-first API, Playwright pool, adaptive HTTP drivers, storage backends).