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

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