crawlo 1.3.3__py3-none-any.whl → 1.3.5__py3-none-any.whl

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.

Potentially problematic release.


This version of crawlo might be problematic. Click here for more details.

Files changed (289) hide show
  1. crawlo/__init__.py +87 -63
  2. crawlo/__version__.py +1 -1
  3. crawlo/cli.py +75 -75
  4. crawlo/commands/__init__.py +14 -14
  5. crawlo/commands/check.py +594 -594
  6. crawlo/commands/genspider.py +151 -151
  7. crawlo/commands/help.py +138 -138
  8. crawlo/commands/list.py +155 -155
  9. crawlo/commands/run.py +341 -323
  10. crawlo/commands/startproject.py +436 -436
  11. crawlo/commands/stats.py +187 -187
  12. crawlo/commands/utils.py +196 -196
  13. crawlo/config.py +312 -312
  14. crawlo/config_validator.py +277 -277
  15. crawlo/core/__init__.py +46 -2
  16. crawlo/core/engine.py +439 -365
  17. crawlo/core/processor.py +40 -40
  18. crawlo/core/scheduler.py +257 -256
  19. crawlo/crawler.py +639 -1167
  20. crawlo/data/__init__.py +5 -5
  21. crawlo/data/user_agents.py +194 -194
  22. crawlo/downloader/__init__.py +273 -273
  23. crawlo/downloader/aiohttp_downloader.py +228 -226
  24. crawlo/downloader/cffi_downloader.py +245 -245
  25. crawlo/downloader/httpx_downloader.py +259 -259
  26. crawlo/downloader/hybrid_downloader.py +212 -212
  27. crawlo/downloader/playwright_downloader.py +402 -402
  28. crawlo/downloader/selenium_downloader.py +472 -472
  29. crawlo/event.py +11 -11
  30. crawlo/exceptions.py +81 -81
  31. crawlo/extension/__init__.py +39 -39
  32. crawlo/extension/health_check.py +141 -141
  33. crawlo/extension/log_interval.py +57 -57
  34. crawlo/extension/log_stats.py +81 -81
  35. crawlo/extension/logging_extension.py +61 -52
  36. crawlo/extension/memory_monitor.py +104 -104
  37. crawlo/extension/performance_profiler.py +133 -133
  38. crawlo/extension/request_recorder.py +107 -107
  39. crawlo/factories/__init__.py +28 -0
  40. crawlo/factories/base.py +69 -0
  41. crawlo/factories/crawler.py +104 -0
  42. crawlo/factories/registry.py +85 -0
  43. crawlo/filters/__init__.py +154 -154
  44. crawlo/filters/aioredis_filter.py +257 -234
  45. crawlo/filters/memory_filter.py +269 -269
  46. crawlo/framework.py +292 -0
  47. crawlo/initialization/__init__.py +40 -0
  48. crawlo/initialization/built_in.py +426 -0
  49. crawlo/initialization/context.py +142 -0
  50. crawlo/initialization/core.py +194 -0
  51. crawlo/initialization/phases.py +149 -0
  52. crawlo/initialization/registry.py +146 -0
  53. crawlo/items/__init__.py +23 -23
  54. crawlo/items/base.py +23 -22
  55. crawlo/items/fields.py +52 -52
  56. crawlo/items/items.py +104 -104
  57. crawlo/logging/__init__.py +38 -0
  58. crawlo/logging/config.py +97 -0
  59. crawlo/logging/factory.py +129 -0
  60. crawlo/logging/manager.py +112 -0
  61. crawlo/middleware/__init__.py +21 -21
  62. crawlo/middleware/default_header.py +132 -132
  63. crawlo/middleware/download_delay.py +104 -104
  64. crawlo/middleware/middleware_manager.py +135 -135
  65. crawlo/middleware/offsite.py +123 -123
  66. crawlo/middleware/proxy.py +386 -386
  67. crawlo/middleware/request_ignore.py +86 -86
  68. crawlo/middleware/response_code.py +163 -163
  69. crawlo/middleware/response_filter.py +136 -136
  70. crawlo/middleware/retry.py +124 -124
  71. crawlo/middleware/simple_proxy.py +65 -65
  72. crawlo/mode_manager.py +212 -187
  73. crawlo/network/__init__.py +21 -21
  74. crawlo/network/request.py +379 -379
  75. crawlo/network/response.py +359 -359
  76. crawlo/pipelines/__init__.py +21 -21
  77. crawlo/pipelines/bloom_dedup_pipeline.py +156 -156
  78. crawlo/pipelines/console_pipeline.py +39 -39
  79. crawlo/pipelines/csv_pipeline.py +316 -316
  80. crawlo/pipelines/database_dedup_pipeline.py +222 -222
  81. crawlo/pipelines/json_pipeline.py +218 -218
  82. crawlo/pipelines/memory_dedup_pipeline.py +115 -115
  83. crawlo/pipelines/mongo_pipeline.py +131 -131
  84. crawlo/pipelines/mysql_pipeline.py +325 -318
  85. crawlo/pipelines/pipeline_manager.py +76 -75
  86. crawlo/pipelines/redis_dedup_pipeline.py +166 -166
  87. crawlo/project.py +327 -325
  88. crawlo/queue/pqueue.py +43 -37
  89. crawlo/queue/queue_manager.py +503 -379
  90. crawlo/queue/redis_priority_queue.py +326 -306
  91. crawlo/settings/__init__.py +7 -7
  92. crawlo/settings/default_settings.py +321 -225
  93. crawlo/settings/setting_manager.py +214 -198
  94. crawlo/spider/__init__.py +657 -639
  95. crawlo/stats_collector.py +73 -59
  96. crawlo/subscriber.py +129 -129
  97. crawlo/task_manager.py +139 -30
  98. crawlo/templates/crawlo.cfg.tmpl +10 -10
  99. crawlo/templates/project/__init__.py.tmpl +3 -3
  100. crawlo/templates/project/items.py.tmpl +17 -17
  101. crawlo/templates/project/middlewares.py.tmpl +118 -118
  102. crawlo/templates/project/pipelines.py.tmpl +96 -96
  103. crawlo/templates/project/settings.py.tmpl +168 -267
  104. crawlo/templates/project/settings_distributed.py.tmpl +167 -180
  105. crawlo/templates/project/settings_gentle.py.tmpl +167 -61
  106. crawlo/templates/project/settings_high_performance.py.tmpl +168 -131
  107. crawlo/templates/project/settings_minimal.py.tmpl +66 -35
  108. crawlo/templates/project/settings_simple.py.tmpl +165 -102
  109. crawlo/templates/project/spiders/__init__.py.tmpl +10 -6
  110. crawlo/templates/run.py.tmpl +34 -38
  111. crawlo/templates/spider/spider.py.tmpl +143 -143
  112. crawlo/templates/spiders_init.py.tmpl +10 -0
  113. crawlo/tools/__init__.py +200 -200
  114. crawlo/tools/anti_crawler.py +268 -268
  115. crawlo/tools/authenticated_proxy.py +240 -240
  116. crawlo/tools/data_formatter.py +225 -225
  117. crawlo/tools/data_validator.py +180 -180
  118. crawlo/tools/date_tools.py +289 -289
  119. crawlo/tools/distributed_coordinator.py +388 -388
  120. crawlo/tools/encoding_converter.py +127 -127
  121. crawlo/tools/network_diagnostic.py +365 -0
  122. crawlo/tools/request_tools.py +82 -82
  123. crawlo/tools/retry_mechanism.py +224 -224
  124. crawlo/tools/scenario_adapter.py +262 -262
  125. crawlo/tools/text_cleaner.py +232 -232
  126. crawlo/utils/__init__.py +34 -34
  127. crawlo/utils/batch_processor.py +259 -259
  128. crawlo/utils/class_loader.py +26 -0
  129. crawlo/utils/controlled_spider_mixin.py +439 -439
  130. crawlo/utils/db_helper.py +343 -343
  131. crawlo/utils/enhanced_error_handler.py +356 -356
  132. crawlo/utils/env_config.py +142 -142
  133. crawlo/utils/error_handler.py +165 -124
  134. crawlo/utils/func_tools.py +82 -82
  135. crawlo/utils/large_scale_config.py +286 -286
  136. crawlo/utils/large_scale_helper.py +344 -344
  137. crawlo/utils/log.py +80 -200
  138. crawlo/utils/performance_monitor.py +285 -285
  139. crawlo/utils/queue_helper.py +175 -175
  140. crawlo/utils/redis_connection_pool.py +388 -351
  141. crawlo/utils/redis_key_validator.py +198 -198
  142. crawlo/utils/request.py +267 -267
  143. crawlo/utils/request_serializer.py +225 -218
  144. crawlo/utils/spider_loader.py +61 -61
  145. crawlo/utils/system.py +11 -11
  146. crawlo/utils/tools.py +4 -4
  147. crawlo/utils/url.py +39 -39
  148. {crawlo-1.3.3.dist-info → crawlo-1.3.5.dist-info}/METADATA +1126 -1020
  149. crawlo-1.3.5.dist-info/RECORD +288 -0
  150. examples/__init__.py +7 -7
  151. tests/__init__.py +7 -7
  152. tests/advanced_tools_example.py +275 -275
  153. tests/authenticated_proxy_example.py +107 -107
  154. tests/baidu_performance_test.py +109 -0
  155. tests/baidu_test.py +60 -0
  156. tests/cleaners_example.py +160 -160
  157. tests/comprehensive_framework_test.py +213 -0
  158. tests/comprehensive_test.py +82 -0
  159. tests/comprehensive_testing_summary.md +187 -0
  160. tests/config_validation_demo.py +142 -142
  161. tests/controlled_spider_example.py +205 -205
  162. tests/date_tools_example.py +180 -180
  163. tests/debug_configure.py +70 -0
  164. tests/debug_framework_logger.py +85 -0
  165. tests/debug_log_config.py +127 -0
  166. tests/debug_log_levels.py +64 -0
  167. tests/debug_pipelines.py +66 -66
  168. tests/detailed_log_test.py +234 -0
  169. tests/distributed_test.py +67 -0
  170. tests/distributed_test_debug.py +77 -0
  171. tests/dynamic_loading_example.py +523 -523
  172. tests/dynamic_loading_test.py +104 -104
  173. tests/env_config_example.py +133 -133
  174. tests/error_handling_example.py +171 -171
  175. tests/final_command_test_report.md +0 -0
  176. tests/final_comprehensive_test.py +152 -0
  177. tests/final_log_test.py +261 -0
  178. tests/final_validation_test.py +183 -0
  179. tests/fix_log_test.py +143 -0
  180. tests/framework_performance_test.py +203 -0
  181. tests/log_buffering_test.py +112 -0
  182. tests/log_generation_timing_test.py +154 -0
  183. tests/optimized_performance_test.py +212 -0
  184. tests/performance_comparison.py +246 -0
  185. tests/queue_blocking_test.py +114 -0
  186. tests/queue_test.py +90 -0
  187. tests/redis_key_validation_demo.py +130 -130
  188. tests/request_params_example.py +150 -150
  189. tests/response_improvements_example.py +144 -144
  190. tests/scrapy_comparison/ofweek_scrapy.py +139 -0
  191. tests/scrapy_comparison/scrapy_test.py +134 -0
  192. tests/simple_command_test.py +120 -0
  193. tests/simple_crawlo_test.py +128 -0
  194. tests/simple_log_test.py +58 -0
  195. tests/simple_log_test2.py +138 -0
  196. tests/simple_optimization_test.py +129 -0
  197. tests/simple_spider_test.py +50 -0
  198. tests/simple_test.py +48 -0
  199. tests/spider_log_timing_test.py +178 -0
  200. tests/test_advanced_tools.py +148 -148
  201. tests/test_all_commands.py +231 -0
  202. tests/test_all_redis_key_configs.py +145 -145
  203. tests/test_authenticated_proxy.py +141 -141
  204. tests/test_batch_processor.py +179 -0
  205. tests/test_cleaners.py +54 -54
  206. tests/test_component_factory.py +175 -0
  207. tests/test_comprehensive.py +146 -146
  208. tests/test_config_consistency.py +80 -80
  209. tests/test_config_merge.py +152 -152
  210. tests/test_config_validator.py +182 -182
  211. tests/test_controlled_spider_mixin.py +80 -0
  212. tests/test_crawlo_proxy_integration.py +108 -108
  213. tests/test_date_tools.py +123 -123
  214. tests/test_default_header_middleware.py +158 -158
  215. tests/test_distributed.py +65 -65
  216. tests/test_double_crawlo_fix.py +207 -207
  217. tests/test_double_crawlo_fix_simple.py +124 -124
  218. tests/test_download_delay_middleware.py +221 -221
  219. tests/test_downloader_proxy_compatibility.py +268 -268
  220. tests/test_dynamic_downloaders_proxy.py +124 -124
  221. tests/test_dynamic_proxy.py +92 -92
  222. tests/test_dynamic_proxy_config.py +146 -146
  223. tests/test_dynamic_proxy_real.py +109 -109
  224. tests/test_edge_cases.py +303 -303
  225. tests/test_enhanced_error_handler.py +270 -270
  226. tests/test_enhanced_error_handler_comprehensive.py +246 -0
  227. tests/test_env_config.py +121 -121
  228. tests/test_error_handler_compatibility.py +112 -112
  229. tests/test_factories.py +253 -0
  230. tests/test_final_validation.py +153 -153
  231. tests/test_framework_env_usage.py +103 -103
  232. tests/test_framework_logger.py +67 -0
  233. tests/test_framework_startup.py +65 -0
  234. tests/test_get_component_logger.py +84 -0
  235. tests/test_integration.py +169 -169
  236. tests/test_item_dedup_redis_key.py +122 -122
  237. tests/test_large_scale_config.py +113 -0
  238. tests/test_large_scale_helper.py +236 -0
  239. tests/test_logging_system.py +283 -0
  240. tests/test_mode_change.py +73 -0
  241. tests/test_mode_consistency.py +51 -51
  242. tests/test_offsite_middleware.py +221 -221
  243. tests/test_parsel.py +29 -29
  244. tests/test_performance.py +327 -327
  245. tests/test_performance_monitor.py +116 -0
  246. tests/test_proxy_api.py +264 -264
  247. tests/test_proxy_health_check.py +32 -32
  248. tests/test_proxy_middleware.py +121 -121
  249. tests/test_proxy_middleware_enhanced.py +216 -216
  250. tests/test_proxy_middleware_integration.py +136 -136
  251. tests/test_proxy_middleware_refactored.py +184 -184
  252. tests/test_proxy_providers.py +56 -56
  253. tests/test_proxy_stats.py +19 -19
  254. tests/test_proxy_strategies.py +59 -59
  255. tests/test_queue_empty_check.py +42 -0
  256. tests/test_queue_manager_double_crawlo.py +173 -173
  257. tests/test_queue_manager_redis_key.py +176 -176
  258. tests/test_random_user_agent.py +72 -72
  259. tests/test_real_scenario_proxy.py +195 -195
  260. tests/test_redis_config.py +28 -28
  261. tests/test_redis_connection_pool.py +294 -294
  262. tests/test_redis_key_naming.py +181 -181
  263. tests/test_redis_key_validator.py +123 -123
  264. tests/test_redis_queue.py +224 -224
  265. tests/test_request_ignore_middleware.py +182 -182
  266. tests/test_request_params.py +111 -111
  267. tests/test_request_serialization.py +70 -70
  268. tests/test_response_code_middleware.py +349 -349
  269. tests/test_response_filter_middleware.py +427 -427
  270. tests/test_response_improvements.py +152 -152
  271. tests/test_retry_middleware.py +241 -241
  272. tests/test_scheduler.py +252 -252
  273. tests/test_scheduler_config_update.py +133 -133
  274. tests/test_simple_response.py +61 -61
  275. tests/test_telecom_spider_redis_key.py +205 -205
  276. tests/test_template_content.py +87 -87
  277. tests/test_template_redis_key.py +134 -134
  278. tests/test_tools.py +159 -159
  279. tests/test_user_agents.py +96 -96
  280. tests/tools_example.py +260 -260
  281. tests/untested_features_report.md +139 -0
  282. tests/verify_debug.py +52 -0
  283. tests/verify_distributed.py +117 -117
  284. tests/verify_log_fix.py +112 -0
  285. crawlo-1.3.3.dist-info/RECORD +0 -219
  286. tests/DOUBLE_CRAWLO_PREFIX_FIX_REPORT.md +0 -82
  287. {crawlo-1.3.3.dist-info → crawlo-1.3.5.dist-info}/WHEEL +0 -0
  288. {crawlo-1.3.3.dist-info → crawlo-1.3.5.dist-info}/entry_points.txt +0 -0
  289. {crawlo-1.3.3.dist-info → crawlo-1.3.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ """
4
+ 模拟爬虫场景测试日志文件生成时机
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import time
10
+ import asyncio
11
+ from pathlib import Path
12
+
13
+ # 添加项目根目录到Python路径
14
+ project_root = Path(__file__).parent.parent
15
+ sys.path.insert(0, str(project_root))
16
+
17
+ from crawlo.logging import configure_logging as configure, get_logger
18
+
19
+
20
+ class MockSpider:
21
+ """模拟爬虫类"""
22
+
23
+ def __init__(self, name):
24
+ self.name = name
25
+ self.logger = get_logger(f'spider.{name}')
26
+
27
+ async def start_requests(self):
28
+ """开始请求"""
29
+ self.logger.info(f"Spider {self.name} 开始请求")
30
+ # 模拟一些网络请求
31
+ for i in range(3):
32
+ self.logger.info(f"Spider {self.name} 发送请求 {i+1}")
33
+ await asyncio.sleep(0.1)
34
+
35
+ async def parse(self, response):
36
+ """解析响应"""
37
+ self.logger.info(f"Spider {self.name} 解析响应")
38
+ # 模拟解析过程
39
+ await asyncio.sleep(0.05)
40
+ self.logger.info(f"Spider {self.name} 提取数据")
41
+ return {"data": f"item from {self.name}"}
42
+
43
+ async def crawl(self):
44
+ """执行爬取"""
45
+ self.logger.info(f"Spider {self.name} 开始爬取")
46
+ await self.start_requests()
47
+ await self.parse("mock_response")
48
+ self.logger.info(f"Spider {self.name} 爬取完成")
49
+
50
+
51
+ async def test_spider_logging_timing():
52
+ """测试爬虫日志记录时机"""
53
+ print("=== 测试爬虫日志记录时机 ===")
54
+
55
+ # 设置日志文件路径
56
+ log_file = "logs/spider_timing_test.log"
57
+
58
+ # 删除可能存在的旧日志文件
59
+ if os.path.exists(log_file):
60
+ os.remove(log_file)
61
+
62
+ # 配置日志系统
63
+ print("1. 配置日志系统...")
64
+ configure(
65
+ level='INFO',
66
+ file_path=log_file,
67
+ console_enabled=True,
68
+ file_enabled=True
69
+ )
70
+
71
+ print(f" 配置后日志文件是否存在: {os.path.exists(log_file)}")
72
+
73
+ # 创建爬虫实例
74
+ print("\n2. 创建爬虫实例...")
75
+ spider = MockSpider("test_spider")
76
+ print(f" 创建爬虫后日志文件是否存在: {os.path.exists(log_file)}")
77
+
78
+ # 检查文件内容
79
+ if os.path.exists(log_file):
80
+ with open(log_file, 'r', encoding='utf-8') as f:
81
+ content = f.read()
82
+ print(f" 日志文件行数: {len(content.splitlines())}")
83
+
84
+ # 执行爬取
85
+ print("\n3. 执行爬取...")
86
+ await spider.crawl()
87
+
88
+ print(f" 爬取完成后日志文件是否存在: {os.path.exists(log_file)}")
89
+
90
+ # 检查最终文件内容
91
+ if os.path.exists(log_file):
92
+ with open(log_file, 'r', encoding='utf-8') as f:
93
+ lines = f.readlines()
94
+ print(f"\n最终日志文件内容:")
95
+ print(f" 总行数: {len(lines)}")
96
+ print(f" 文件大小: {os.path.getsize(log_file)} 字节")
97
+ if lines:
98
+ print(f" 第一行: {lines[0].strip()}")
99
+ print(f" 最后一行: {lines[-1].strip()}")
100
+
101
+ # 等待一段时间后再次检查
102
+ print("\n4. 等待2秒后再次检查...")
103
+ time.sleep(2)
104
+
105
+ if os.path.exists(log_file):
106
+ with open(log_file, 'r', encoding='utf-8') as f:
107
+ lines = f.readlines()
108
+ print(f" 等待后总行数: {len(lines)}")
109
+
110
+
111
+ async def test_multiple_spiders():
112
+ """测试多个爬虫的日志记录"""
113
+ print("\n=== 测试多个爬虫的日志记录 ===")
114
+
115
+ # 设置日志文件路径
116
+ log_file = "logs/multi_spider_test.log"
117
+
118
+ # 删除可能存在的旧日志文件
119
+ if os.path.exists(log_file):
120
+ os.remove(log_file)
121
+
122
+ # 配置日志系统
123
+ configure(
124
+ level='INFO',
125
+ file_path=log_file,
126
+ console_enabled=True,
127
+ file_enabled=True
128
+ )
129
+
130
+ # 创建多个爬虫实例
131
+ spiders = [MockSpider(f"spider_{i}") for i in range(3)]
132
+
133
+ print("1. 顺序执行多个爬虫...")
134
+ for i, spider in enumerate(spiders):
135
+ print(f" 执行爬虫 {i+1}...")
136
+ await spider.crawl()
137
+
138
+ # 检查当前日志文件状态
139
+ if os.path.exists(log_file):
140
+ with open(log_file, 'r', encoding='utf-8') as f:
141
+ lines = f.readlines()
142
+ print(f" 当前日志行数: {len(lines)}")
143
+
144
+ print(f"\n 所有爬虫执行完成后日志文件是否存在: {os.path.exists(log_file)}")
145
+
146
+ # 检查最终文件内容
147
+ if os.path.exists(log_file):
148
+ with open(log_file, 'r', encoding='utf-8') as f:
149
+ lines = f.readlines()
150
+ print(f"\n最终日志文件内容:")
151
+ print(f" 总行数: {len(lines)}")
152
+ print(f" 文件大小: {os.path.getsize(log_file)} 字节")
153
+ if lines:
154
+ print(f" 第一行: {lines[0].strip()}")
155
+ print(f" 最后一行: {lines[-1].strip()}")
156
+
157
+
158
+ async def main():
159
+ """主函数"""
160
+ print("开始测试爬虫日志记录时机...")
161
+
162
+ try:
163
+ await test_spider_logging_timing()
164
+ await test_multiple_spiders()
165
+
166
+ print("\n=== 所有测试完成 ===")
167
+
168
+ except Exception as e:
169
+ print(f"\n测试过程中出现错误: {e}")
170
+ import traceback
171
+ traceback.print_exc()
172
+ return 1
173
+
174
+ return 0
175
+
176
+
177
+ if __name__ == '__main__':
178
+ asyncio.run(main())
@@ -1,149 +1,149 @@
1
- #!/usr/bin/python
2
- # -*- coding: UTF-8 -*-
3
- """
4
- 高级工具测试
5
- """
6
- import unittest
7
- from crawlo.tools import (
8
- # 数据处理工具
9
- clean_text,
10
- format_currency,
11
- validate_email,
12
- validate_url,
13
- check_data_integrity,
14
-
15
- # 重试机制
16
- RetryMechanism,
17
- should_retry,
18
- exponential_backoff,
19
-
20
- # 反爬虫应对工具
21
- AntiCrawler,
22
- rotate_proxy,
23
- handle_captcha,
24
- detect_rate_limiting,
25
-
26
- # 分布式协调工具
27
- generate_pagination_tasks,
28
- distribute_tasks,
29
- DistributedCoordinator,
30
- TaskDistributor,
31
- DeduplicationTool
32
- )
33
-
34
-
35
- class TestAdvancedTools(unittest.TestCase):
36
- """高级工具测试类"""
37
-
38
- def test_data_processing_tools(self):
39
- """测试数据处理工具"""
40
- # 测试数据清洗
41
- dirty_text = "<p>这是一个&nbsp;<b>测试</b>&amp;文本</p>"
42
- clean_result = clean_text(dirty_text)
43
- self.assertEqual(clean_result, "这是一个 测试&文本")
44
-
45
- # 测试数据格式化
46
- price = 1234.567
47
- formatted_price = format_currency(price, "¥", 2)
48
- self.assertEqual(formatted_price, "¥1,234.57")
49
-
50
- # 测试字段验证
51
- self.assertTrue(validate_email("test@example.com"))
52
- self.assertFalse(validate_email("invalid-email"))
53
-
54
- self.assertTrue(validate_url("https://example.com"))
55
- self.assertFalse(validate_url("invalid-url"))
56
-
57
- # 测试数据完整性检查
58
- data = {
59
- "name": "张三",
60
- "email": "test@example.com",
61
- "phone": "13812345678"
62
- }
63
- required_fields = ["name", "email", "phone"]
64
- integrity_result = check_data_integrity(data, required_fields)
65
- self.assertTrue(integrity_result["is_valid"])
66
-
67
- def test_retry_mechanism(self):
68
- """测试重试机制"""
69
- # 测试指数退避
70
- delay = exponential_backoff(0)
71
- self.assertGreater(delay, 0)
72
-
73
- # 测试是否应该重试
74
- self.assertTrue(should_retry(status_code=500))
75
- self.assertTrue(should_retry(exception=ConnectionError()))
76
- self.assertFalse(should_retry(status_code=200))
77
-
78
- def test_anti_crawler_tools(self):
79
- """测试反爬虫应对工具"""
80
- # 测试反爬虫工具
81
- anti_crawler = AntiCrawler()
82
-
83
- # 测试随机User-Agent
84
- user_agent = anti_crawler.get_random_user_agent()
85
- self.assertIsInstance(user_agent, str)
86
- self.assertGreater(len(user_agent), 0)
87
-
88
- # 测试代理轮换
89
- proxy = anti_crawler.rotate_proxy()
90
- self.assertIsInstance(proxy, dict)
91
-
92
- # 测试验证码检测
93
- self.assertTrue(anti_crawler.handle_captcha("请输入验证码进行验证"))
94
- self.assertFalse(anti_crawler.handle_captcha("正常页面内容"))
95
-
96
- # 测试频率限制检测
97
- self.assertTrue(anti_crawler.detect_rate_limiting(429, {}))
98
- self.assertFalse(anti_crawler.detect_rate_limiting(200, {}))
99
-
100
- def test_distributed_coordinator_tools(self):
101
- """测试分布式协调工具"""
102
- # 测试任务分发器
103
- distributor = TaskDistributor()
104
-
105
- # 测试分页任务生成
106
- base_url = "https://example.com/products"
107
- pagination_tasks = distributor.generate_pagination_tasks(base_url, 1, 5)
108
- self.assertEqual(len(pagination_tasks), 5)
109
-
110
- # 测试任务分发
111
- tasks = list(range(1, 21)) # 20个任务
112
- distributed = distributor.distribute_tasks(tasks, 4) # 分发给4个工作节点
113
- self.assertEqual(len(distributed), 4)
114
- self.assertEqual(sum(len(worker_tasks) for worker_tasks in distributed), 20)
115
-
116
- # 测试去重工具
117
- dedup_tool = DeduplicationTool()
118
-
119
- # 测试数据指纹生成
120
- fingerprint1 = dedup_tool.generate_fingerprint({"name": "test", "value": 123})
121
- fingerprint2 = dedup_tool.generate_fingerprint({"name": "test", "value": 123})
122
- self.assertEqual(fingerprint1, fingerprint2)
123
-
124
- # 测试去重功能
125
- self.assertFalse(dedup_tool.is_duplicate({"name": "test", "value": 123}))
126
- self.assertTrue(dedup_tool.add_to_dedup({"name": "test", "value": 123}))
127
- self.assertTrue(dedup_tool.is_duplicate({"name": "test", "value": 123}))
128
- self.assertFalse(dedup_tool.add_to_dedup({"name": "test", "value": 123}))
129
-
130
- # 测试分布式协调器
131
- coordinator = DistributedCoordinator()
132
-
133
- # 测试任务ID生成
134
- task_id = coordinator.generate_task_id("https://example.com", "test_spider")
135
- self.assertIsInstance(task_id, str)
136
- self.assertEqual(len(task_id), 32) # MD5 hash长度
137
-
138
- # 测试分页任务生成
139
- pagination_tasks = coordinator.generate_pagination_tasks("https://example.com/products", 1, 5)
140
- self.assertEqual(len(pagination_tasks), 5)
141
-
142
- # 测试任务分发
143
- tasks = list(range(1, 21)) # 20个任务
144
- distributed = coordinator.distribute_tasks(tasks, 4) # 分发给4个工作节点
145
- self.assertEqual(len(distributed), 4)
146
-
147
-
148
- if __name__ == '__main__':
1
+ #!/usr/bin/python
2
+ # -*- coding: UTF-8 -*-
3
+ """
4
+ 高级工具测试
5
+ """
6
+ import unittest
7
+ from crawlo.tools import (
8
+ # 数据处理工具
9
+ clean_text,
10
+ format_currency,
11
+ validate_email,
12
+ validate_url,
13
+ check_data_integrity,
14
+
15
+ # 重试机制
16
+ RetryMechanism,
17
+ should_retry,
18
+ exponential_backoff,
19
+
20
+ # 反爬虫应对工具
21
+ AntiCrawler,
22
+ rotate_proxy,
23
+ handle_captcha,
24
+ detect_rate_limiting,
25
+
26
+ # 分布式协调工具
27
+ generate_pagination_tasks,
28
+ distribute_tasks,
29
+ DistributedCoordinator,
30
+ TaskDistributor,
31
+ DeduplicationTool
32
+ )
33
+
34
+
35
+ class TestAdvancedTools(unittest.TestCase):
36
+ """高级工具测试类"""
37
+
38
+ def test_data_processing_tools(self):
39
+ """测试数据处理工具"""
40
+ # 测试数据清洗
41
+ dirty_text = "<p>这是一个&nbsp;<b>测试</b>&amp;文本</p>"
42
+ clean_result = clean_text(dirty_text)
43
+ self.assertEqual(clean_result, "这是一个 测试&文本")
44
+
45
+ # 测试数据格式化
46
+ price = 1234.567
47
+ formatted_price = format_currency(price, "¥", 2)
48
+ self.assertEqual(formatted_price, "¥1,234.57")
49
+
50
+ # 测试字段验证
51
+ self.assertTrue(validate_email("test@example.com"))
52
+ self.assertFalse(validate_email("invalid-email"))
53
+
54
+ self.assertTrue(validate_url("https://example.com"))
55
+ self.assertFalse(validate_url("invalid-url"))
56
+
57
+ # 测试数据完整性检查
58
+ data = {
59
+ "name": "张三",
60
+ "email": "test@example.com",
61
+ "phone": "13812345678"
62
+ }
63
+ required_fields = ["name", "email", "phone"]
64
+ integrity_result = check_data_integrity(data, required_fields)
65
+ self.assertTrue(integrity_result["is_valid"])
66
+
67
+ def test_retry_mechanism(self):
68
+ """测试重试机制"""
69
+ # 测试指数退避
70
+ delay = exponential_backoff(0)
71
+ self.assertGreater(delay, 0)
72
+
73
+ # 测试是否应该重试
74
+ self.assertTrue(should_retry(status_code=500))
75
+ self.assertTrue(should_retry(exception=ConnectionError()))
76
+ self.assertFalse(should_retry(status_code=200))
77
+
78
+ def test_anti_crawler_tools(self):
79
+ """测试反爬虫应对工具"""
80
+ # 测试反爬虫工具
81
+ anti_crawler = AntiCrawler()
82
+
83
+ # 测试随机User-Agent
84
+ user_agent = anti_crawler.get_random_user_agent()
85
+ self.assertIsInstance(user_agent, str)
86
+ self.assertGreater(len(user_agent), 0)
87
+
88
+ # 测试代理轮换
89
+ proxy = anti_crawler.rotate_proxy()
90
+ self.assertIsInstance(proxy, dict)
91
+
92
+ # 测试验证码检测
93
+ self.assertTrue(anti_crawler.handle_captcha("请输入验证码进行验证"))
94
+ self.assertFalse(anti_crawler.handle_captcha("正常页面内容"))
95
+
96
+ # 测试频率限制检测
97
+ self.assertTrue(anti_crawler.detect_rate_limiting(429, {}))
98
+ self.assertFalse(anti_crawler.detect_rate_limiting(200, {}))
99
+
100
+ def test_distributed_coordinator_tools(self):
101
+ """测试分布式协调工具"""
102
+ # 测试任务分发器
103
+ distributor = TaskDistributor()
104
+
105
+ # 测试分页任务生成
106
+ base_url = "https://example.com/products"
107
+ pagination_tasks = distributor.generate_pagination_tasks(base_url, 1, 5)
108
+ self.assertEqual(len(pagination_tasks), 5)
109
+
110
+ # 测试任务分发
111
+ tasks = list(range(1, 21)) # 20个任务
112
+ distributed = distributor.distribute_tasks(tasks, 4) # 分发给4个工作节点
113
+ self.assertEqual(len(distributed), 4)
114
+ self.assertEqual(sum(len(worker_tasks) for worker_tasks in distributed), 20)
115
+
116
+ # 测试去重工具
117
+ dedup_tool = DeduplicationTool()
118
+
119
+ # 测试数据指纹生成
120
+ fingerprint1 = dedup_tool.generate_fingerprint({"name": "test", "value": 123})
121
+ fingerprint2 = dedup_tool.generate_fingerprint({"name": "test", "value": 123})
122
+ self.assertEqual(fingerprint1, fingerprint2)
123
+
124
+ # 测试去重功能
125
+ self.assertFalse(dedup_tool.is_duplicate({"name": "test", "value": 123}))
126
+ self.assertTrue(dedup_tool.add_to_dedup({"name": "test", "value": 123}))
127
+ self.assertTrue(dedup_tool.is_duplicate({"name": "test", "value": 123}))
128
+ self.assertFalse(dedup_tool.add_to_dedup({"name": "test", "value": 123}))
129
+
130
+ # 测试分布式协调器
131
+ coordinator = DistributedCoordinator()
132
+
133
+ # 测试任务ID生成
134
+ task_id = coordinator.generate_task_id("https://example.com", "test_spider")
135
+ self.assertIsInstance(task_id, str)
136
+ self.assertEqual(len(task_id), 32) # MD5 hash长度
137
+
138
+ # 测试分页任务生成
139
+ pagination_tasks = coordinator.generate_pagination_tasks("https://example.com/products", 1, 5)
140
+ self.assertEqual(len(pagination_tasks), 5)
141
+
142
+ # 测试任务分发
143
+ tasks = list(range(1, 21)) # 20个任务
144
+ distributed = coordinator.distribute_tasks(tasks, 4) # 分发给4个工作节点
145
+ self.assertEqual(len(distributed), 4)
146
+
147
+
148
+ if __name__ == '__main__':
149
149
  unittest.main()