graph-crawler 5.2.0__tar.gz → 5.3.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 (492) hide show
  1. graph_crawler-5.3.2/CHANGELOG.md +353 -0
  2. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/MANIFEST.in +0 -3
  3. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/PKG-INFO +214 -49
  4. graph_crawler-5.3.2/README.md +410 -0
  5. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/__init__.py +30 -16
  6. graph_crawler-5.3.2/graph_crawler/__version__.py +8 -0
  7. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/ai/__init__.py +5 -4
  8. graph_crawler-5.3.2/graph_crawler/api/__init__.py +28 -0
  9. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/_core.py +36 -34
  10. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/_distributed.py +14 -16
  11. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/_shared.py +11 -21
  12. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/_sitemap_distributed.py +20 -26
  13. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/async_.py +38 -40
  14. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/cli.py +1 -1
  15. graph_crawler-5.3.2/graph_crawler/api/client/__init__.py +8 -0
  16. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/client/client.py +48 -76
  17. graph_crawler-5.3.2/graph_crawler/api/history_manager.py +55 -0
  18. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/project_init.py +2 -0
  19. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/stats_collector.py +12 -39
  20. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/sync.py +101 -56
  21. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/bootstrap.py +42 -40
  22. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/dto/mappers/edge_mapper.py +1 -1
  23. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/dto/mappers/graph_mapper.py +15 -7
  24. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/dto/mappers/node_mapper.py +34 -4
  25. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/driver_factory.py +2 -2
  26. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/exporters/node_exporter.py +2 -1
  27. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/finalize.py +4 -4
  28. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/low_memory_graph_saver.py +3 -5
  29. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/scheduling/rule_based_scheduler.py +8 -8
  30. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/adaptive_throttler.py +2 -3
  31. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/async_bridge.py +1 -1
  32. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/base_spider.py +4 -5
  33. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/celery_batch_spider.py +9 -11
  34. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/celery_spider.py +8 -11
  35. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/checkpoint.py +5 -5
  36. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/crawl_coordinator.py +23 -18
  37. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/filters/domain_filter.py +1 -1
  38. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/filters/domain_patterns.py +2 -2
  39. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/link_processor.py +29 -5
  40. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/multiprocess_spider.py +4 -4
  41. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/node_scanner.py +69 -11
  42. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/scheduler.py +3 -3
  43. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/sitemap_parser.py +6 -11
  44. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/sitemap_spider.py +6 -6
  45. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/spider.py +6 -4
  46. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/spider_lifecycle.py +2 -2
  47. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/worker_pool.py +1 -1
  48. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/graph_merge/graph_operations.py +3 -3
  49. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/data/backends/memory.py +1 -1
  50. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/data/backends/sqlite.py +2 -2
  51. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/data/interfaces.py +6 -18
  52. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/context/crawl_context.py +6 -6
  53. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/entities/graph.py +109 -60
  54. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/entities/graph_operations.py +2 -5
  55. graph_crawler-5.3.2/graph_crawler/domain/entities/graph_statistics.py +10 -0
  56. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/entities/merge_strategies.py +2 -5
  57. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/entities/node.py +109 -21
  58. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/entities/registries.py +6 -20
  59. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/events/__init__.py +1 -2
  60. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/events/base.py +4 -2
  61. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/events/crawl.py +13 -24
  62. graph_crawler-5.3.2/graph_crawler/domain/events/edge.py +43 -0
  63. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/events/event_bus.py +5 -6
  64. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/events/events.py +10 -10
  65. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/events/fetch.py +13 -16
  66. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/events/legacy_type_map.py +3 -3
  67. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/events/llm.py +10 -20
  68. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/events/node.py +12 -25
  69. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/events/plugin.py +7 -11
  70. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/events/scheduler.py +8 -12
  71. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/events/storage.py +10 -17
  72. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/__init__.py +1 -1
  73. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/distributed_spider.py +4 -5
  74. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/driver.py +1 -2
  75. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/event_bus.py +4 -4
  76. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/exporters.py +9 -23
  77. graph_crawler-5.3.2/graph_crawler/domain/interfaces/merge_context.py +52 -0
  78. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/node_interfaces.py +39 -26
  79. graph_crawler-5.3.2/graph_crawler/domain/interfaces/parser.py +88 -0
  80. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/storage/base.py +0 -2
  81. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/storage/eviction.py +8 -12
  82. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/eviction_policy.py +1 -1
  83. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/minimap_builder.py +4 -4
  84. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/node_view_builder.py +7 -7
  85. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/pagination_guards.py +1 -1
  86. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/url_normalizer.py +12 -3
  87. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/url_rule_matcher.py +1 -1
  88. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/value_objects/__init__.py +1 -1
  89. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/value_objects/configs.py +9 -9
  90. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/value_objects/html_document.py +1 -1
  91. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/value_objects/lifecycle.py +1 -1
  92. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/value_objects/models.py +11 -12
  93. graph_crawler-5.3.2/graph_crawler/domain/value_objects/page_actions.py +72 -0
  94. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/value_objects/url_normalization.py +3 -2
  95. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/base.py +14 -25
  96. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/chain.py +6 -10
  97. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/proxy_health.py +6 -9
  98. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/proxy_middleware.py +6 -5
  99. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/proxy_models.py +11 -15
  100. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/proxy_selection.py +5 -5
  101. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/retry_middleware.py +2 -2
  102. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/user_agent_middleware.py +1 -1
  103. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/builtin/stats_export_plugin.py +9 -8
  104. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/crawl_engine/smart_crawl.py +11 -21
  105. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/engine/__init__.py +1 -1
  106. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/engine/anti_bot_detection.py +4 -1
  107. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/engine/anti_bot_playwright.py +4 -1
  108. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/engine/anti_bot_scripts.py +7 -4
  109. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/engine/anti_bot_stealth.py +4 -4
  110. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/__init__.py +6 -6
  111. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/base.py +100 -12
  112. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/links.py +1 -1
  113. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/metadata.py +7 -14
  114. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/smart_page_finder.py +1 -1
  115. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/adapters/selectolax_adapter.py +1 -2
  116. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/messaging/celery_app.py +1 -1
  117. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/messaging/celery_job_task.py +1 -1
  118. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/messaging/celery_unified.py +1 -1
  119. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/messaging/easy_crawler.py +1 -1
  120. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/messaging/worker_api.py +2 -2
  121. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/auto_storage.py +1 -1
  122. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/graph_repository.py +1 -1
  123. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/lmdb_eviction_storage.py +1 -3
  124. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/memory_dedup_store.py +2 -3
  125. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/memory_politeness_store.py +1 -1
  126. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/async_http/driver.py +5 -5
  127. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/async_http/driver_curl_cffi.py +1 -1
  128. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle_adaptive.py +7 -7
  129. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/async_http/plugins/http_cache.py +2 -2
  130. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/async_http/plugins/retry.py +2 -2
  131. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/base_plugin.py +1 -1
  132. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/core/body_decoder.py +1 -1
  133. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/core/mixins.py +2 -2
  134. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/escalation.py +16 -3
  135. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/batch_scheduler.py +2 -2
  136. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/browser_factory.py +5 -1
  137. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/browser_profile.py +1 -1
  138. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/engine.py +15 -0
  139. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/fetch_metrics.py +1 -1
  140. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/net/__init__.py +1 -1
  141. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/net/proxy_pool.py +1 -1
  142. graph_crawler-5.3.2/graph_crawler/infrastructure/transport/playwright/page_actions.py +186 -0
  143. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/page_fetch.py +106 -2
  144. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/plugins/__init__.py +4 -0
  145. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/plugins/_network_record.py +8 -1
  146. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_solver.py +1 -1
  147. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/plugins/cookie_consent.py +6 -6
  148. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/plugins/human_bezier.py +1 -1
  149. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/plugins/network_log.py +108 -50
  150. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/pool_lifecycle.py +1 -1
  151. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/pooled_driver.py +11 -5
  152. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/plugin_manager.py +4 -4
  153. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/native/__init__.py +7 -8
  154. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/native/setup.py +12 -10
  155. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/error_tracing.py +25 -27
  156. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/metrics/prometheus_metrics.py +1 -1
  157. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/metrics_core.py +21 -27
  158. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/structured_logging.py +31 -55
  159. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/optimizations/simhash_numba.py +1 -1
  160. graph_crawler-5.3.2/graph_crawler/py.typed +0 -0
  161. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/constants.py +2 -2
  162. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/platform/adaptive_resolver.py +3 -3
  163. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/platform/config.py +2 -2
  164. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/platform/detector.py +1 -1
  165. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/bloom_filter.py +1 -1
  166. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/event_publisher_mixin.py +1 -2
  167. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/fast_json.py +1 -1
  168. graph_crawler-5.3.2/graph_crawler/shared/utils/shadow_dom.py +36 -0
  169. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler.egg-info/PKG-INFO +214 -49
  170. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler.egg-info/SOURCES.txt +4 -1
  171. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/pyproject.toml +2 -2
  172. graph_crawler-5.2.0/CHANGELOG.md +0 -3
  173. graph_crawler-5.2.0/README.md +0 -245
  174. graph_crawler-5.2.0/graph_crawler/__version__.py +0 -7
  175. graph_crawler-5.2.0/graph_crawler/api/__init__.py +0 -24
  176. graph_crawler-5.2.0/graph_crawler/api/client/__init__.py +0 -8
  177. graph_crawler-5.2.0/graph_crawler/api/history_manager.py +0 -82
  178. graph_crawler-5.2.0/graph_crawler/domain/entities/graph_statistics.py +0 -13
  179. graph_crawler-5.2.0/graph_crawler/domain/events/edge.py +0 -46
  180. graph_crawler-5.2.0/graph_crawler/domain/interfaces/merge_context.py +0 -58
  181. graph_crawler-5.2.0/graph_crawler/domain/interfaces/parser.py +0 -110
  182. graph_crawler-5.2.0/graph_crawler/native/README.md +0 -110
  183. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/CODE_STYLE.md +0 -0
  184. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/LICENSE +0 -0
  185. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/server/__init__.py +0 -0
  186. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/server/crawl_monitor.py +0 -0
  187. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/server/dashboard.py +0 -0
  188. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/server/rest_api.py +0 -0
  189. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/server/webhooks.py +0 -0
  190. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/api/server/websocket_manager.py +0 -0
  191. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/__init__.py +0 -0
  192. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/context/__init__.py +0 -0
  193. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/context/dependency_registry.py +0 -0
  194. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/context/graph_context.py +0 -0
  195. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/context/merge_context.py +0 -0
  196. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/dto/__init__.py +0 -0
  197. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/dto/edge_dto.py +0 -0
  198. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/dto/graph_dto.py +0 -0
  199. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/dto/mappers/__init__.py +0 -0
  200. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/dto/mappers/node_data_codec.py +0 -0
  201. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/dto/node_dto.py +0 -0
  202. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/dto/utils.py +0 -0
  203. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/__init__.py +0 -0
  204. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/application_container.py +0 -0
  205. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/exporters/__init__.py +0 -0
  206. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/exporters/base_exporter.py +0 -0
  207. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/exporters/csv_exporter.py +0 -0
  208. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/exporters/edge_exporter.py +0 -0
  209. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/exporters/excel_exporter.py +0 -0
  210. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/exporters/jsonl_exporter.py +0 -0
  211. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/exporters/parquet_exporter.py +0 -0
  212. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/exporters/sql_exporter.py +0 -0
  213. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/resource_budget.py +0 -0
  214. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/scheduling/__init__.py +0 -0
  215. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/services/storage_factory.py +0 -0
  216. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/__init__.py +0 -0
  217. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/__init__.py +0 -0
  218. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/dead_letter_queue.py +0 -0
  219. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/domain_rate_limiter.py +0 -0
  220. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/filters/__init__.py +0 -0
  221. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/filters/base.py +0 -0
  222. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/filters/path_filter.py +0 -0
  223. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/incremental_strategy.py +0 -0
  224. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/parsers/__init__.py +0 -0
  225. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/parsers/base.py +0 -0
  226. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/parsers/html_parser.py +0 -0
  227. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/progress_tracker.py +0 -0
  228. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/serialization_mixin.py +0 -0
  229. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/crawling/sitemap_processor.py +0 -0
  230. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/graph_export.py +0 -0
  231. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/graph_merge/__init__.py +0 -0
  232. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/application/use_cases/graph_merge/merge_strategies.py +0 -0
  233. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/data/__init__.py +0 -0
  234. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/data/backends/__init__.py +0 -0
  235. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/__init__.py +0 -0
  236. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/context/__init__.py +0 -0
  237. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/entities/__init__.py +0 -0
  238. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/entities/edge.py +0 -0
  239. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/entities/edge_analysis.py +0 -0
  240. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/entities/redirect_mode.py +0 -0
  241. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/entities/sitemap_node.py +0 -0
  242. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/entities/strategies.py +0 -0
  243. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/events/idempotent_subscriber.py +0 -0
  244. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/adapter.py +0 -0
  245. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/control_channel.py +0 -0
  246. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/crawl_context.py +0 -0
  247. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/filter.py +0 -0
  248. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/plugin_manager.py +0 -0
  249. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/processor.py +0 -0
  250. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/scanner.py +0 -0
  251. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/scheduler.py +0 -0
  252. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/scheduling/__init__.py +0 -0
  253. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/scheduling/frontier.py +0 -0
  254. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/scheduling/scheduler.py +0 -0
  255. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/spider.py +0 -0
  256. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/stop_condition.py +0 -0
  257. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/storage/__init__.py +0 -0
  258. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/interfaces/storage/queue.py +0 -0
  259. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/__init__.py +0 -0
  260. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/adjacency_maintenance.py +0 -0
  261. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/cycle_detection.py +0 -0
  262. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/degree_ranking.py +0 -0
  263. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/depth_context.py +0 -0
  264. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/display_title.py +0 -0
  265. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/duplicate_detection.py +0 -0
  266. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/duplicate_selector.py +0 -0
  267. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/duplicate_stats.py +0 -0
  268. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/edge_filter.py +0 -0
  269. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/edge_statistics.py +0 -0
  270. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/graph_query_cascade.py +0 -0
  271. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/graph_statistics.py +0 -0
  272. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/graph_traversal.py +0 -0
  273. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/language_versions.py +0 -0
  274. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/lsh_index.py +0 -0
  275. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/node_content_resolver.py +0 -0
  276. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/predicate_ops.py +0 -0
  277. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/schema_filter.py +0 -0
  278. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/search_haystack.py +0 -0
  279. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/simhash_clustering.py +0 -0
  280. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/simhash_fallback.py +0 -0
  281. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/similarity_ranking.py +0 -0
  282. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/text_search.py +0 -0
  283. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/url_clustering.py +0 -0
  284. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/url_deduplication.py +0 -0
  285. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/services/url_tree_builder.py +0 -0
  286. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/value_objects/domain_patterns.py +0 -0
  287. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/value_objects/fetch_options.py +0 -0
  288. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/value_objects/redirect_hop.py +0 -0
  289. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/value_objects/scheduling/__init__.py +0 -0
  290. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/value_objects/scheduling/crawl_request.py +0 -0
  291. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/value_objects/scheduling/retry_policy.py +0 -0
  292. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/value_objects/settings.py +0 -0
  293. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/domain/value_objects/views.py +0 -0
  294. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/__init__.py +0 -0
  295. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/__init__.py +0 -0
  296. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/cache_middleware.py +0 -0
  297. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/error_recovery_middleware.py +0 -0
  298. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/logging_middleware.py +0 -0
  299. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/rate_limit_middleware.py +0 -0
  300. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/request_response_middleware.py +0 -0
  301. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/robots_cache.py +0 -0
  302. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/robots_middleware.py +0 -0
  303. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/middleware/robots_validator.py +0 -0
  304. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/__init__.py +0 -0
  305. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/builtin/__init__.py +0 -0
  306. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/crawl_engine/__init__.py +0 -0
  307. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/crawl_engine/base.py +0 -0
  308. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/crawl_engine/priority_provider.py +0 -0
  309. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/engine/base.py +0 -0
  310. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/engine/captcha/__init__.py +0 -0
  311. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/engine/captcha/detector.py +0 -0
  312. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/engine/captcha/models.py +0 -0
  313. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/engine/captcha/plugin.py +0 -0
  314. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/engine/captcha/services.py +0 -0
  315. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/content_extractors/__init__.py +0 -0
  316. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/content_extractors/base.py +0 -0
  317. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/content_extractors/goose3_extractor.py +0 -0
  318. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/content_extractors/newspaper_extractor.py +0 -0
  319. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/content_extractors/plugin.py +0 -0
  320. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/content_extractors/readability_extractor.py +0 -0
  321. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/defaults.py +0 -0
  322. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/extractors/__init__.py +0 -0
  323. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/extractors/email_extractor.py +0 -0
  324. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/extractors/phone_extractor.py +0 -0
  325. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/extractors/price_extractor.py +0 -0
  326. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/markdown_plugin.py +0 -0
  327. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/structured_data/__init__.py +0 -0
  328. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/structured_data/constants.py +0 -0
  329. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/structured_data/exceptions.py +0 -0
  330. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/structured_data/extractor.py +0 -0
  331. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/structured_data/options.py +0 -0
  332. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/__init__.py +0 -0
  333. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/base.py +0 -0
  334. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/jsonld.py +0 -0
  335. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/microdata.py +0 -0
  336. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/opengraph.py +0 -0
  337. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/rdfa.py +0 -0
  338. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/structured_data/parsers/twitter.py +0 -0
  339. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/structured_data/plugin.py +0 -0
  340. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/structured_data/result.py +0 -0
  341. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/text.py +0 -0
  342. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/node/url_normalizer.py +0 -0
  343. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/extensions/plugins/registry.py +0 -0
  344. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/__init__.py +0 -0
  345. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/adapters/__init__.py +0 -0
  346. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/adapters/base.py +0 -0
  347. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/messaging/__init__.py +0 -0
  348. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/messaging/celery_batch.py +0 -0
  349. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/messaging/config.py +0 -0
  350. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/__init__.py +0 -0
  351. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/base.py +0 -0
  352. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/eviction.py +0 -0
  353. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/json/__init__.py +0 -0
  354. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/json_storage.py +0 -0
  355. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/memory/__init__.py +0 -0
  356. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/memory_frontier.py +0 -0
  357. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/memory_storage.py +0 -0
  358. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/mongodb_storage.py +0 -0
  359. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/naming_strategy.py +0 -0
  360. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/postgresql_storage.py +0 -0
  361. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/protocols/__init__.py +0 -0
  362. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/redis_dedup_store.py +0 -0
  363. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/redis_politeness_store.py +0 -0
  364. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/repository.py +0 -0
  365. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/sqlite/__init__.py +0 -0
  366. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/sqlite_eviction_storage.py +0 -0
  367. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/sqlite_storage.py +0 -0
  368. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/unified/__init__.py +0 -0
  369. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/unified/file_job_storage.py +0 -0
  370. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/unified/file_queue_storage.py +0 -0
  371. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/unified/memory_job_storage.py +0 -0
  372. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/unified/memory_queue_storage.py +0 -0
  373. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/unified/postgresql_job_storage.py +0 -0
  374. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/unified/postgresql_queue_storage.py +0 -0
  375. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/persistence/unified/unified_storage.py +0 -0
  376. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/__init__.py +0 -0
  377. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/async_http/__init__.py +0 -0
  378. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/async_http/config.py +0 -0
  379. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/async_http/context.py +0 -0
  380. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/async_http/plugins/__init__.py +0 -0
  381. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/async_http/plugins/autothrottle.py +0 -0
  382. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/async_http/plugins/headers.py +0 -0
  383. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/async_http/plugins/rate_limiter.py +0 -0
  384. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/async_http/stages.py +0 -0
  385. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/base.py +0 -0
  386. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/compression/__init__.py +0 -0
  387. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/compression/strategy.py +0 -0
  388. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/connection_pool.py +0 -0
  389. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/context.py +0 -0
  390. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/core/__init__.py +0 -0
  391. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/core/base_async.py +0 -0
  392. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/core/redirect_utils.py +0 -0
  393. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/factory.py +0 -0
  394. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/__init__.py +0 -0
  395. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/adaptive_semaphore.py +0 -0
  396. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/config.py +0 -0
  397. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/context.py +0 -0
  398. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/driver.py +0 -0
  399. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/net/geoip.py +0 -0
  400. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/net/rate_limiter.py +0 -0
  401. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/plugins/captcha_detector.py +0 -0
  402. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/plugins/form_filler.py +0 -0
  403. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/plugins/ram_adaptive.py +0 -0
  404. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/plugins/screenshot.py +0 -0
  405. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/plugins/user_profile.py +0 -0
  406. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/stages.py +0 -0
  407. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/playwright/stealth_hints.py +0 -0
  408. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/protocols.py +0 -0
  409. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/undetected/__init__.py +0 -0
  410. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/infrastructure/transport/undetected/async_driver.py +0 -0
  411. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/native/bloom_filter.pyx +0 -0
  412. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/native/html_parser.pyx +0 -0
  413. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/native/url_utils.pyx +0 -0
  414. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/__init__.py +0 -0
  415. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/decorators/__init__.py +0 -0
  416. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/decorators/cache.py +0 -0
  417. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/decorators/log.py +0 -0
  418. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/decorators/retry.py +0 -0
  419. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/decorators/timing.py +0 -0
  420. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/extensions/__init__.py +0 -0
  421. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/extensions/memory_guard.py +0 -0
  422. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/extensions/state_manager.py +0 -0
  423. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/listeners/__init__.py +0 -0
  424. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/listeners/base.py +0 -0
  425. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/listeners/base_metrics_listener.py +0 -0
  426. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/listeners/crawl_listener.py +0 -0
  427. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/listeners/dlq_listener.py +0 -0
  428. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/listeners/error_listener.py +0 -0
  429. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/listeners/logging_listener.py +0 -0
  430. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/listeners/metrics_listener.py +0 -0
  431. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/listeners/node_listener.py +0 -0
  432. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/listeners/plugin_listener.py +0 -0
  433. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/listeners/storage_listener.py +0 -0
  434. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/listeners/url_listener.py +0 -0
  435. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/metrics/__init__.py +0 -0
  436. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/metrics/memory_profiler.py +0 -0
  437. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/metrics/metrics_collector.py +0 -0
  438. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/observability/tracing.py +0 -0
  439. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/optimizations/__init__.py +0 -0
  440. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/optimizations/generators.py +0 -0
  441. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/optimizations/memory_slots.py +0 -0
  442. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/__init__.py +0 -0
  443. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/dto/__init__.py +0 -0
  444. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/dto/edge_dto.py +0 -0
  445. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/dto/graph_dto.py +0 -0
  446. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/dto/node_dto.py +0 -0
  447. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/error_handling/__init__.py +0 -0
  448. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/error_handling/error_handler.py +0 -0
  449. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/exceptions.py +0 -0
  450. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/platform/__init__.py +0 -0
  451. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/platform/diagnostics.py +0 -0
  452. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/platform/metrics.py +0 -0
  453. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/security/__init__.py +0 -0
  454. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/security/url_sanitizer.py +0 -0
  455. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/security/url_validator.py +0 -0
  456. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/__init__.py +0 -0
  457. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/async_compat.py +0 -0
  458. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/captcha/__init__.py +0 -0
  459. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/captcha/base.py +0 -0
  460. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/captcha/manager.py +0 -0
  461. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/celery_config.py +0 -0
  462. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/celery_helpers.py +0 -0
  463. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/cgroup_limits.py +0 -0
  464. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/content_decoder.py +0 -0
  465. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/distributed_rate_limiter.py +0 -0
  466. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/distributed_rate_limiter_backends.py +0 -0
  467. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/dns_cache.py +0 -0
  468. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/enterprise_ua_generator.py +0 -0
  469. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/fingerprint.py +0 -0
  470. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/fingerprint_data.py +0 -0
  471. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/fingerprint_generators.py +0 -0
  472. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/fingerprint_profile.py +0 -0
  473. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/fingerprint_stealth.py +0 -0
  474. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/html_utils.py +0 -0
  475. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/id_generator.py +0 -0
  476. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/memory_optimizer.py +0 -0
  477. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/rate_limiter.py +0 -0
  478. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/regex_validator.py +0 -0
  479. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/text_utils.py +0 -0
  480. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/url_patterns.py +0 -0
  481. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/url_utils.py +0 -0
  482. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/user_agent_rotator.py +0 -0
  483. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/validation_helpers.py +0 -0
  484. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/visualization.py +0 -0
  485. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/shared/utils/visualization_core.py +0 -0
  486. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/tools/__init__.py +0 -0
  487. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler/tools/db_cleanup.py +0 -0
  488. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler.egg-info/dependency_links.txt +0 -0
  489. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler.egg-info/entry_points.txt +0 -0
  490. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler.egg-info/requires.txt +0 -0
  491. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/graph_crawler.egg-info/top_level.txt +0 -0
  492. {graph_crawler-5.2.0 → graph_crawler-5.3.2}/setup.cfg +0 -0
@@ -0,0 +1,353 @@
1
+ # Changelog
2
+
3
+ ## 5.3.2 — 2026-06
4
+
5
+ ### Added
6
+
7
+ - **Fetch provenance on the node.** `Node.channel`, `bot_block_status`, `served_by_driver`, `served_by_engine`,
8
+ `escalations` and `fetch_info` read `metadata["fetch"]`; `Node.ok` (scanned, 2xx/3xx, not blocked, no error) and
9
+ `Node.is_blocked` separate useful pages from block and error pages.
10
+ - **Plugin routing.** `BaseNodePlugin.domains` (domain and subdomains) and `url_pattern` (regex), on the class or via
11
+ `config={"domains": [...], "url_pattern": ...}`, plus an overridable `applies_to(url)`. An invalid `url_pattern`
12
+ raises `ConfigurationError` on `register()`. Several instances of one plugin class with different routes run
13
+ side by side (they used to replace each other).
14
+ - **Typed plugin output.** `NodePluginContext.set_node_field(name, value)` writes a field declared by a `Node`
15
+ subclass, validated against its annotation (`"100"` → `int`); base fields are read-only. Fields are rolled back
16
+ together with `metadata`/`user_data` when the plugin fails.
17
+ - **Empty extraction warning.** `BaseNodePlugin.expects_nonempty` + `extraction_key`: a WARNING when the plugin left
18
+ `user_data[extraction_key]` empty.
19
+ - **`Graph.search(..., user_data_keys=("jobs",))`** searches the listed `user_data` keys as well.
20
+ - **`Node.public_user_data`** and `RESERVED_USER_DATA_KEYS` (`_link_anchors`, `_custom_fields`, `_node_class`).
21
+
22
+ ### Changed
23
+
24
+ - A response with status 400 or higher gets `metadata["error"] = "HTTP <code>"` and is logged as a WARNING
25
+ (`HTTP 404 for <url> — page not scanned`).
26
+ - A bare 403/401/405 without vendor markers is still classified as `none`; the decision is now logged at INFO.
27
+ - Quieter logs: import writes nothing at INFO, platform detection runs once, the start of a crawl is one line,
28
+ and each page is one INFO line with `[channel=…]`, `[block=…]`, `[error=…]`.
29
+ - The temporary `_link_anchors` map is removed from the node once its edges are created (the anchor text stays in
30
+ `edge.metadata["anchor_text"]`). The default JSON export writes `public_user_data`.
31
+ - A third-party plugin that overwrites a reserved `user_data` key gets a WARNING.
32
+
33
+ ### Fixed
34
+
35
+ - `load_graph()` without `node_class=` restores a custom `Node` subclass when its module is already imported (the
36
+ class name is stored with the node; nothing is imported from the file). `Graph.copy()`, `union`, `intersection`,
37
+ `difference` and nodes created in a loaded graph keep the node class.
38
+ - `metadata` written by a plugin on `ON_NODE_CREATED` or `ON_AFTER_SCAN` is no longer lost.
39
+ - **Placeholder lists are detected as a JavaScript shell.** A 2xx body with five or more `placeholder` /
40
+ `skeleton` / `shimmer` elements makes `needs_browser` true, so `escalating` goes straight to the browser tier.
41
+ With `driver="http"` the node gets `metadata["js_shell_suspected"]` and a WARNING (once per host) suggests
42
+ `driver="escalating"` or `"playwright"`.
43
+ - **No duplicate edges.** A link repeated on a page (menu + footer + body) created a new `Edge` each time
44
+ (17.5 % of edges on uk.wikipedia); `LinkProcessor` now skips existing `(source, target)` pairs. `Graph.has_edge`
45
+ also sees the backend edge index.
46
+ - **`h1` children are no longer joined with spaces** (`Портал : Поточні події` → `Портал:Поточні події`).
47
+ - **Default ports are normalized** (`http://x:80`, `https://x:443`, empty port).
48
+ - `Graph.normalize_url()` is public; `LinkProcessor` checks the scheduler with the canonical form as well.
49
+ - **`NetworkLogPlugin` waits for network quiet** before publishing the log (`settle_ms`=2000, `quiet_ms`=300;
50
+ `settle_ms=0` restores the old behaviour) — late XHR (search/listing after hydration) was lost.
51
+ - `create_trailing_slash_rule` keeps the root path; an empty path is `/` for PATH-scope rules.
52
+
53
+ ## 5.3.1 — 2026-06
54
+
55
+ ### Added
56
+
57
+ - **Hydration state globals snapshot.** The Playwright driver serialises `window.__NAME__` objects (`__NUXT__`,
58
+ `__APOLLO_STATE__`, `__INITIAL_STATE__`, `__PINIA__`, …) with `JSON.stringify` after the page has run and embeds
59
+ them into the HTML snapshot as `<script type="application/json" data-agc-state="__NAME__">`. Nuxt 2 ships its
60
+ state as an IIFE (`window.__NUXT__=(function(a,b){return {…}}(…))`) which is JavaScript, not JSON, and was
61
+ invisible to any static state parser. Globals that already exist as a JSON `<script id="__NAME__">`
62
+ (`__NEXT_DATA__`) are not duplicated; `PageFetcher.state_globals_max_chars` (5 MB) caps the snapshot.
63
+ `inject_state_globals(html, states)` is exported for tests and other drivers.
64
+
65
+ ## 5.3.0 — 2026-06
66
+
67
+ ### Added
68
+
69
+ - **Page actions as public API.** `driver_config={"actions": [PageAction(...) | dict, ...]}` for the Playwright driver:
70
+ `click` / `fill` / `select` / `press` / `wait_for(response=…|selector=…)` / `scroll`, target by `selector`, `text`,
71
+ `placeholder` or `label` (`nth` among visible matches). Actions are validated when the driver is created and run
72
+ after content is ready and before the HTML snapshot and `CONTENT_READY` hooks — the network log is published after
73
+ the actions. Results land in `node.metadata["actions_log"]`, `["requests_after_actions"]` and `["actions_error"]`
74
+ (the first failing action stops the chain with a reason). `from graph_crawler import PageAction`.
75
+ - `engine.page_error_types()` — page error classes of all installed browser SDKs.
76
+ - `PageAction(type="back")`; action errors name the element that intercepts pointer events.
77
+
78
+ ### Fixed
79
+
80
+ - Binary request bodies (gzip beacons) no longer raise `UnicodeDecodeError` inside Playwright event listeners
81
+ (`NetworkLogPlugin` and the action recorder read `post_data_buffer` with `errors="replace"`); the error used to break
82
+ the next Playwright call with `TypeError: function takes exactly 5 arguments`.
83
+
84
+ ### Fixed
85
+
86
+ - `graph.get_node_by_url(seed)` finds the node after a redirect in the graph returned by `async_crawl`: `add_node`
87
+ restores the alias from `metadata["was_redirected_from"]`.
88
+
89
+ ## 5.2.2 — 2026-06
90
+
91
+ ### Fixed (test infrastructure)
92
+
93
+ - **The library no longer configures logging.** `GraphCrawlerClient.__init__` called `logging.basicConfig(INFO)`
94
+ when its logger had no handlers, so every crawl printed to stderr and reconfigured the application's root
95
+ logger. Removed; `graph_crawler` gets a `NullHandler`.
96
+ - **Noise demoted:** `HTML executor shutdown` and `lmdb not installed … Falling back to SQLiteEvictionStorage` are
97
+ DEBUG; `channel='chrome' not installed, falling back to bundled Chromium` is a WARNING once per process.
98
+ - **One version source.** `graph_crawler.__version__` (and the default User-Agent) come from the installed
99
+ package metadata (`pyproject.toml`); `__version__.py` no longer hard-codes a number that drifted (5.1.0 vs 5.2.0).
100
+ - **Test suite collects without optional extras.** Tests that need `psutil`, `rapidfuzz`, `stop-words`,
101
+ `html2md-clean` or `playwright` are skipped via `importorskip` instead of failing collection under `-x`.
102
+ `test_div_inline_fix.py` / `test_phase1_fixes.py` tested `html2md_clean` itself (no `graph_crawler` code) and were
103
+ removed from the core suite.
104
+ - GitLab CI (`.gitlab-ci.yml`): offline suite with `[dev]` and a minimal job without extras.
105
+
106
+ ## 5.2.1 — 2026-06
107
+
108
+ ### Fixed (freelance cases)
109
+
110
+ - **Captured XHR/fetch traffic could not be replayed outside the browser.** `NetworkLogPlugin` recorded the
111
+ URL and method of a request but not its body, so every POST/GraphQL endpoint (robota.ua lists vacancies via
112
+ `POST dracula.robota.ua/?q=getPublishedVacanciesList`) was unusable: a GET of the same URL returns 400.
113
+ Records now carry `post_data` and (on `response`) `request_headers`.
114
+ - **GraphQL responses overwrote each other.** `body_ref` was `sha1(url|status|content-type)` — all operations
115
+ POSTed to one `/graphql` URL shared a key and only the last body survived. The key now includes method and
116
+ request body.
117
+ - **JSON family by default.** The capture allow-list matched exact MIME types only; `application/graphql-
118
+ response+json`, `problem+json`, `vnd.api+json`, `text/json` were dropped. Without an explicit
119
+ `capture_content_types` every `*/json` and `*+json` type is captured (RFC 6839). Default `max_body_kb` 64 → 256
120
+ (listing APIs routinely exceed 64 KB).
121
+ - **Shadow DOM was invisible.** `page.content()` serializes only light DOM: content of open shadow roots (web
122
+ components, embedded price widgets) never reached the node. When a page has open shadow roots, the browser
123
+ driver serializes it with `Element.getHTML({shadowRoots})` and expands the declarative shadow roots into their
124
+ hosts (`shared/utils/shadow_dom.expose_shadow_roots`) — Lexbor, like the HTML spec, does not descend into
125
+ `<template>` content, so a plain declarative-shadow-DOM serialization would still hide it from selectors and link
126
+ extraction. Pages without shadow roots are untouched.
127
+ - `tests/unit/domain/entities/test_graph_new_features.py` still expected the `DeprecationWarning` that
128
+ `export_nodes()` stopped emitting in 4aab7525; it now asserts the opposite.
129
+
130
+ ### Fixed (real-site tests)
131
+
132
+ - **Ctrl+C during `crawl()` hung until the crawl-level timeout** (28 s with `timeout=25`; forever without
133
+ one). `asyncio.wait_for` in Python 3.11 swallows the outer cancellation when the awaited coroutine finishes
134
+ at the same moment, so some workers survived `KeyboardInterrupt` and kept crawling. `NodeScanner` and the
135
+ escalation tier budget now use `asyncio.timeout()`; `CrawlCoordinator` cancels its workers on
136
+ `CancelledError`. Ctrl+C now returns in ~0.3 s.
137
+ - **`same_domain=False` was ignored** — without `allowed_domains` the `DomainFilter` default
138
+ (`domain+subdomains`) still applied and external links never entered the graph. Now maps to `allowed_domains=["*"]`.
139
+ - **`graph.metadata["stop_reason"]` was `None` after `timeout=` / `max_pages`** — only stop conditions were
140
+ recorded. Now `"timeout"`, `"max_pages"` or `"limit"`.
141
+ - **Network errors were invisible on the node**: a DNS/connection failure left `response_status=None`,
142
+ `scanned=True` and no message; `get_failed_nodes()` returned `[]`. The error text is now in
143
+ `node.metadata["error"]`, the HTTP status (if any) is kept, and `get_failed_nodes()` with default
144
+ arguments includes network-error nodes.
145
+ - **`storage="json", storage_config={"path": "site.json"}` was silently ignored** (file written to
146
+ `crawler_data/json/graph.json`). `path` is now honoured, like for sqlite (`JSONStorage(file_name=)`).
147
+ - `Graph.export_nodes()` / `Graph.export_edges()` are no longer deprecated: they are the public one-liners the
148
+ README shows and now delegate to `GraphExportUseCase` silently (no `DeprecationWarning`).
149
+ - `tests/manual/real_sites/run_all.py -j N` runs scenarios in parallel; `tests/manual/real_sites/test_user_cases.py`
150
+ — 23 pytest user scenarios on Wikipedia / robota.ua / GitHub / example.com, each hard-capped at 40 s.
151
+
152
+ ### Changed
153
+
154
+ - **Engine contract verified end-to-end**. `tests/transport/
155
+ test_engine_contract.py` drives the real `PlaywrightDriver.fetch()` through three engines registered via the
156
+ public `register_engine()` on a recording fake SDK: the call sequence is byte-for-byte identical across
157
+ engines, stealth is injected iff `provides_stealth is False`, and `executable_path`/`ignore_default_args`/
158
+ launch args come only from the engine. No runtime code changed.
159
+ - **`LexborDocument.clean_text()` no longer re-parses the page**. To strip `script/style/noscript/svg/head` without mutating the shared tree it
160
+ serialised the document and built a second `LexborHTMLParser` — i.e. every page with the default
161
+ `TextExtractorPlugin` was parsed twice (`count_parses.py`: 2.0 → **1.0** per page; with `MarkdownPlugin`
162
+ 1 parse + 1 clone). It now uses `tree.clone()`.
163
+ - **One `ContentDecoder` for every HTTP tier**. `AsyncDriver` (aiohttp) and `AsyncCurlCffiDriver` (curl_cffi) no longer rely on the client's own
164
+ `response.text`/`.text()`: they read the raw body and decode it through `ContentDecoder`
165
+ (`Content-Type` charset → `<meta charset>` → BOM → chardet → strict utf-8). Pages whose charset is only
166
+ declared in `<meta>` (e.g. windows-1251 without a header) were previously decoded as utf-8 by aiohttp,
167
+ failed, and silently became `html=None` with a "successful" 200. An undecodable body is now reported as
168
+ `FetchResponse.error` (`"ContentDecoder: cannot decode body …"`) with the status code preserved;
169
+ `ConnectionPoolManager.fetch_text()` goes through the same decoder. `FetchResponse.html` is a strict
170
+ `str` field — passing `bytes` raises `ValidationError` instead of being coerced. Browser tiers
171
+ (Playwright `page.content()`, nodriver `tab.get_content()`) already return `str`. Contract test:
172
+ `tests/transport/test_html_is_str_contract.py` (driver matrix returns `str`; cp1251 via meta/header;
173
+ binary → error).
174
+ - **Lazy public facade**. `graph_crawler/__init__.py` now
175
+ eagerly imports only `crawl`, `async_crawl`, `Graph`, `Node`, `save_graph`, `load_graph`,
176
+ `BaseStopCondition`; every other name in `__all__` (drivers, storages, exceptions, settings, views,
177
+ celery tasks, …) resolves on first access via module `__getattr__` and is cached. `aiohttp` is no longer
178
+ loaded by `import graph_crawler`. `bootstrap()` no longer calls `collect_system_metrics()`
179
+ (`psutil.cpu_percent(interval=0.1)` blocked import for 100 ms for a DEBUG line). Import time here:
180
+ 470 ms → 228 ms. Optional names (`PlaywrightDriver`, `celery`, `EasyDistributedCrawler`, …) still return
181
+ `None` without their extra.
182
+ - **Escalation re-probe replays the solving tier's User-Agent**.
183
+ Playwright and nodriver now export `navigator.userAgent` as `extras["user_agent"]`;
184
+ `SessionState.absorb` records it (pinned once the session is solved, like `impersonate`/`proxy`) and
185
+ `EscalatingDriver._fetch_tier` sends it as `extra_headers={"User-Agent": …}` to HTTP/TLS tiers together
186
+ with the stored cookies. `cf_clearance` is bound to UA+IP+JA4, so a re-probe on Tier 1 with aiohttp's
187
+ default UA was rejected and the host never descended from the browser tier.
188
+
189
+ ### Removed
190
+
191
+ - **`NodePluginContext.parser`**. Node plugins now read the
192
+ single Lexbor tree through the `HTMLDocument`/`HTMLNode` protocols: `LinkExtractorPlugin`
193
+ (`doc.css("a[href]")`, `doc.base_href()`), `MetadataExtractorPlugin`, `TextExtractorPlugin`
194
+ (`doc.clean_text()`, new in the protocol), `StructuredDataPlugin` (OG/Twitter/Microdata/RDFa parsers take
195
+ an `HTMLDocument`; `StructuredDataExtractor.extract(html=, doc=)`), `Email`/`PhoneExtractorPlugin`.
196
+ Migration: `context.parser.find_all(sel)` → `context.html_tree.css(sel)`, `.find()` → `.css_first()`,
197
+ `el.get_attribute()` → `el.attr()`. Fixed along the way in `LexborNode`: `attr()` returns `""` (not
198
+ `None`) for valueless boolean attributes such as `itemscope`, so nested Microdata/RDFa items are now
199
+ detected; `css()`/`css_first()` return descendants only (selectolax included the node itself, which made
200
+ Microdata recurse to `max_nesting_depth`).
201
+ - **`PooledPlaywrightDriver` legacy batch path**: `batch_legacy.py`
202
+ (`LegacyBatch._fetch_batch_legacy`, `_fetch_chain`) is gone. `persistent_browser=False` keeps its
203
+ "launch/stop per batch" semantics through the pool path: after a batch, once no fetch is in flight, the
204
+ pool and the engine session are closed (`_close_pool()` + `_stop_persistent_session()`, shared with
205
+ `close()`). Dead `self.playwright`/`self.browsers` cleanup removed from `close()`.
206
+ - **Escalation.** `classify_block()` rewritten to the
207
+ 6.0 decision table: challenge/captcha vendor headers first, then unambiguous body captcha/challenge
208
+ markers (ahead of vendor hard-block headers), then short/blocking-page widgets, status-based verdicts
209
+ and empty-2xx. A bare `401/403/405` with no vendor markers is no longer a block. On the classifier corpus
210
+ it goes 25/37 → **37/37**.
211
+
212
+ - `needs_browser` heuristic (SPA shell: little visible text + SPA root / `<noscript>` JS hint / no
213
+ `<a href>`) is now the default `EscalationPolicy.needs_browser`; on a clean HTTP/TLS response it jumps
214
+ straight to the first browser tier (`escalation_reason="needs_browser"`).
215
+ - Per-tier `TierTimeouts` + `escalation_deadline_s` (60s) budget: a tier is skipped (last response
216
+ returned, browser never started) when the remaining budget is below its `min_budget`; each tier fetch
217
+ is wrapped in `asyncio.wait_for`. New `PER_NODE_FETCH_TIMEOUT` constant with a validator warning.
218
+ - Cross-tier session: `SessionState` / `HostSessionStore` carry solved cookies (`cf_clearance`, `__cf_bm`,
219
+ `datadome`, …) forward and back down to cheaper tiers on re-probe; `impersonate`/`proxy` are pinned once
220
+ a session is solved. curl_cffi pins the impersonate fingerprint; Playwright/nodriver export context
221
+ cookies via `extras["cookies"]`.
222
+ - `extras` contract: every driver stamps `channel`/`driver`/`engine` (via `BaseDriver`/`BaseAsyncDriver`
223
+ `_stamp_extras` + `_finalize_response`) even outside escalation.
224
+ - New Prometheus metrics `crawl_fetch_duration_seconds{channel,engine}`, `crawl_escalations_total`,
225
+ `crawl_escalation_deadline_hits_total`.
226
+
227
+ - **HTML parsing: Lexbor (selectolax) is the only core parser**.
228
+ `beautifulsoup4`, `lxml`, `lxml_html_clean` removed from `[project].dependencies`;
229
+ `BeautifulSoupAdapter`, `LxmlAdapter`, `ScrapyAdapter` deleted. New `HTMLDocument` / `HTMLNode`
230
+ protocols in `domain/value_objects/html_document.py`, implemented by `LexborDocument` in
231
+ `infrastructure/adapters/selectolax_adapter.py`. `<template>` is renamed to `gc-template` before
232
+ parsing so Vue/React SSR links inside templates are not lost. `IParser.parse()` returns `HTMLDocument`;
233
+ `NodePluginContext.html_tree` is now a `LexborDocument`. `get_parser()` accepts `auto|selectolax|lexbor`.
234
+ - `HTMLUtils.extract_links/extract_text/extract_metadata` accept an `HTMLDocument` (string input still
235
+ works but is deprecated).
236
+ - `SitemapParser.parse_large_sitemap_streaming()` uses stdlib `iterparse` by default; lxml is an
237
+ optional fast-path via the new extra `sitemap-fast`.
238
+
239
+ - **`html2md-clean` moved to the optional extra `markdown`** (`pip install "graph-crawler[markdown]"`);
240
+ `MarkdownGenerator/MarkdownOptions/MarkdownResult` re-exports in `graph_crawler.shared.utils` are lazy.
241
+ `MarkdownPlugin` feeds the already-parsed Lexbor tree to html2md-clean 0.5 (`generate_from_tree`) —
242
+ one parse per page instead of 2–4. `import graph_crawler` no longer loads bs4/lxml.
243
+
244
+ - **Dependency map**. Core is now 10 packages: `requests`,
245
+ `httpx`, `pydantic-settings`, `pybloom-live`, `fastapi`, `tenacity`, `stop-words`, `tiktoken`,
246
+ `markdown-it-py` removed from `[project].dependencies`. New extras: `browser` (`playwright` kept as alias),
247
+ `server` (fastapi/uvicorn/websockets), `text` (tiktoken/stop-words), `captcha`; `markdown` also pulls
248
+ `markdown-it-py`; `all` is recursive. Extras `cloudscraper` and `undetected-sync` dropped.
249
+ Extra-gated imports are lazy and fail fast with `requires extra '<name>'`; `transport.PlaywrightDriver/
250
+ RequestsDriver/CloudscraperDriver/RequestsSessionAdapter` are lazy re-exports (ImportError instead of `None`).
251
+ `CrawlerSettings` keeps `GC_*`/`__`/`.env` without pydantic-settings; `CrawlerConfig` is a plain `BaseModel`.
252
+
253
+ - **Transport cleanup**. Deleted: sync drivers
254
+ (`RequestsDriver`, `CloudscraperDriver`, `UndetectedChromeDriver`, `BotasaurusDriver`, `BaseSyncDriver`),
255
+ `AsyncDriverHTTP2` (httpx), `AsyncCloudscraperDriver`, `tls_client.py`, `StealthHTTPDriver`,
256
+ `session_manager/session_adapters/session_protocol`, `shared/utils/captcha_bypass.py`,
257
+ `shared/utils/proxy_manager.py`, packages `transport/http` and `transport/browser`.
258
+ `DriverType` is now `aiohttp | tls | playwright | nodriver | escalating` (+ `asyncio` alias);
259
+ strings `http`/`async`/`asyncio` → `aiohttp` and `stealth`/`curl_cffi` → `tls` are accepted for one
260
+ release with `DeprecationWarning`. One `AsyncDriver` (former `driver_adaptive.py` is now
261
+ `async_http/driver.py`); request hints (`referer`, `extra_headers`, `cookies`) are honoured.
262
+ `requests` is gone from the package: proxy health-check, CAPTCHA solvers/bypass and `SitemapParser`
263
+ use aiohttp (`SitemapParser.parse_sitemap/parse_from_robots` are `asyncio.run` wrappers,
264
+ `http_client=` is deprecated and ignored). `BloomFilter` is pure-python (xxhash) — `pybloom-live`
265
+ no longer needed.
266
+ `import graph_crawler` no longer loads any extra (guarded by `tests/unit/test_import_surface.py`), 3.8 s → 1.7 s here.
267
+ `shared/constants.py` no longer imports aiohttp — `br`/`zstd` are advertised
268
+ by `transport.compression.runtime_browser_headers()` only when the runtime can decode them;
269
+ one driver registry — `application.services.driver_factory` delegates to
270
+ `transport.factory.DriverFactory` (`register_factory`, `registered_names`); `SitemapParser(driver=)`
271
+ fetches robots.txt/sitemaps through the crawl `IDriver` (same tier chain), `SitemapSpider` passes its
272
+ driver, aiohttp is used directly only for binary `.gz`; `tests/transport/test_all_drivers_report_redirects.py`
273
+ is an `IDriver` acceptance matrix (aiohttp / tls / fake playwright / fake nodriver / escalating).
274
+
275
+ - **Escalation**: `EscalationPolicy.reprobe_interval` (5 min) —
276
+ one request per interval re-probes tier 0 for a host remembered on a higher tier (no herd of browser
277
+ re-escalations after TTL); `EscalationPolicy.max_hosts` (10k) — `HostTierMemory` is an LRU and drops
278
+ per-host locks together with entries. In strict mode (`GC_ENGINE_STRICT`) an `ImportError` raised _during_
279
+ `fetch` (lazy browser start without the SDK) now propagates instead of being turned into a network error.
280
+
281
+ - **`extras` contract persisted.** New
282
+ `FetchResponse.fetch_provenance()` / `FETCH_EXTRAS_KEYS`: a JSON-safe slice
283
+ (`bot_block_status, channel, driver, engine, escalations` + `escalation_reason`; cookies/impersonate dropped).
284
+ `NodeScanner` stores it in `node.metadata["fetch"]` for every response, including blocked/error ones, so it
285
+ survives JSON/SQLite/MongoDB/PostgreSQL storage. Contract test `tests/transport/test_extras_contract.py`.
286
+
287
+ ### Fixed
288
+
289
+ - Transport `extras` (`bot_block_status`, tier, escalations) were dropped after fetch and never reached the
290
+ node or storage; for blocked pages nothing was transferred at all.
291
+ - `SmartPageFinder` read `html_tree.text` as a property; with Lexbor it is a method — page text was
292
+ silently dropped.
293
+ - `AsyncUndetectedDriver` accepts a plain `dict` config (`AsyncUndetectedConfig.from_mapping`) — building
294
+ the `nodriver` tier through `DriverFactory.create(DriverType.NODRIVER, config={...})` (as `EscalatingDriver`
295
+ does) raised `AttributeError`.
296
+
297
+ ## 5.2.0 — 2026-06
298
+
299
+ ### Added
300
+
301
+ - `infrastructure/transport/escalation.py`: `classify_block()` (Cloudflare / Akamai / DataDome /
302
+ PerimeterX / Incapsula markers, 403/429/503, empty 200), `EscalationPolicy`, `EscalationTier`,
303
+ `EscalatingDriver` — chain http → tls → browser → undetected with `max_tier`.
304
+ `driver="escalating"` works in `crawl()` / `async_crawl()` / `Crawler` / CLI.
305
+ - `HostTierMemory`: adaptive per-host tier memory — TTL from the moment of decision
306
+ (`GC_TIER_TTL`, default 1200 s), invalidation when the remembered tier gets blocked,
307
+ one escalation per host at a time (per-host lock). Per process.
308
+ - Strict engine mode: `GC_ENGINE_STRICT=1`, `{"browser_engine": {"strict": True}}` or
309
+ `driver_config={"engine_strict": True}` — `ImportError` instead of a silent fallback
310
+ `cloakbrowser → patchright → playwright` or a skipped escalation tier.
311
+ - Browser engine registry in `playwright/engine.py`: `register_engine()`, `resolve_engine()`,
312
+ `registered_engines()`. Built-in engines `cloakbrowser` → `patchright` → `playwright` with
313
+ automatic fail-open. `GC_BROWSER_ENGINE` env selects the engine when config has none.
314
+ `PlaywrightBrowserEngine.effective_name` / `fallback_reason`.
315
+ - `FetchResponse.extras` contract: `bot_block_status`, `channel`, `driver`, `engine`, `escalations`.
316
+ - `DriverType.TLS`, `NODRIVER`, `UNDETECTED`, `BOTASAURUS`, `ESCALATING`; `DriverFactory.create()`
317
+ accepts extra constructor kwargs (`policy=`).
318
+ - Extras: `tls`, `stealth`, `cloak`, `undetected`, `undetected-sync`, `antidetect`, `cloudscraper`, `system`.
319
+ - `README.md`, `CHANGELOG.md`.
320
+
321
+ ### Changed
322
+
323
+ - `normalize_protocol` defaults to `False`: `http://` and `https://` are different URLs.
324
+ The crawler follows the real redirect instead of rewriting the scheme itself, so
325
+ http-only hosts stay reachable. Enable it explicitly to merge both schemes on dedup.
326
+ - `Graph.handle_redirect()` stores the alias as the real `Location` the server sent
327
+ (provenance); the normalized form is used only to find and merge nodes.
328
+ - Single `DriverType` enum (`transport/base.py`); `factory.py` re-exports it.
329
+ - `cloudscraper` moved from required dependencies to extra `cloudscraper`.
330
+ - `psutil` is optional (`system` extra); `MemoryGuard` imports it lazily.
331
+ - `requires-python = ">=3.11"` (upper bound removed; classifiers 3.11–3.14).
332
+ - `pytest` runs offline by default (`-m 'not manual'`, no coverage in `addopts`).
333
+ Network / real-site tests live in `tests/manual/` and run with `pytest -m manual tests/manual`.
334
+ - Project URLs point to the GitLab repository.
335
+
336
+ ### Fixed
337
+
338
+ - `pooled_driver`: bounded walk over `redirected_from` (infinite loop on objects without a
339
+ `None` terminator); cookies are no longer added twice to a fresh browser.
340
+ - Tests synced with the code: redirect aliases, `html2md-clean` 0.4.0 API, `crawl_coordinator`
341
+ `per_host_max_in_flight`, `.gitignore` lookup at the repository root, `tools/db_cleanup.py`
342
+ CLI allow-listed for `print`. Playwright fake tests skip when `playwright` is not installed.
343
+
344
+ ### Removed
345
+
346
+ - `tests/345.py`, `tests/ДОДАТКОВІ_ТЕСТИ.py`, `tests/manual_4a_games_test.py`, `tests/google_maps_results.json`.
347
+ - `EXTRACTION_AUDIT.md` and other analysis / plan documents that described work, not the code.
348
+ - `graph_crawler/data/ARCHITECTURE.md`, `MIGRATION_50_STEPS.md`, `REFACTORING_PLAN.md`, `mkdocs.yml`
349
+ (finished plans / config without content).
350
+
351
+ ## 5.1.0
352
+
353
+ Previous release (sync-first API, Playwright pool, adaptive HTTP drivers, storage backends).
@@ -1,14 +1,11 @@
1
1
  include README.md
2
2
  include LICENSE
3
3
  include CHANGELOG.md
4
- include CODE_STYLE.md
5
4
  include pyproject.toml
6
5
  include graph_crawler/py.typed
7
6
 
8
7
  recursive-include graph_crawler/native *.pyx
9
- recursive-include graph_crawler/native *.pxd
10
8
  include graph_crawler/native/setup.py
11
- include graph_crawler/native/README.md
12
9
 
13
10
  prune tests
14
11
  global-exclude __pycache__