crawlo 1.1.9__py3-none-any.whl → 1.2.1__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.
- crawlo/__init__.py +61 -61
- crawlo/__version__.py +1 -1
- crawlo/cleaners/__init__.py +60 -60
- crawlo/cleaners/data_formatter.py +225 -225
- crawlo/cleaners/encoding_converter.py +125 -125
- crawlo/cleaners/text_cleaner.py +232 -232
- crawlo/cli.py +65 -65
- crawlo/commands/__init__.py +14 -14
- crawlo/commands/check.py +594 -594
- crawlo/commands/genspider.py +151 -151
- crawlo/commands/help.py +142 -132
- crawlo/commands/list.py +155 -155
- crawlo/commands/run.py +292 -292
- crawlo/commands/startproject.py +418 -418
- crawlo/commands/stats.py +188 -188
- crawlo/commands/utils.py +186 -186
- crawlo/config.py +312 -312
- crawlo/config_validator.py +252 -252
- crawlo/core/__init__.py +2 -2
- crawlo/core/engine.py +354 -345
- crawlo/core/processor.py +40 -40
- crawlo/core/scheduler.py +143 -136
- crawlo/crawler.py +1027 -1027
- crawlo/downloader/__init__.py +266 -266
- crawlo/downloader/aiohttp_downloader.py +220 -220
- crawlo/downloader/cffi_downloader.py +256 -256
- crawlo/downloader/httpx_downloader.py +259 -259
- crawlo/downloader/hybrid_downloader.py +213 -213
- crawlo/downloader/playwright_downloader.py +402 -402
- crawlo/downloader/selenium_downloader.py +472 -472
- crawlo/event.py +11 -11
- crawlo/exceptions.py +81 -81
- crawlo/extension/__init__.py +37 -37
- crawlo/extension/health_check.py +141 -141
- crawlo/extension/log_interval.py +57 -57
- crawlo/extension/log_stats.py +81 -81
- crawlo/extension/logging_extension.py +43 -43
- crawlo/extension/memory_monitor.py +104 -104
- crawlo/extension/performance_profiler.py +133 -133
- crawlo/extension/request_recorder.py +107 -107
- crawlo/filters/__init__.py +154 -154
- crawlo/filters/aioredis_filter.py +280 -280
- crawlo/filters/memory_filter.py +269 -269
- crawlo/items/__init__.py +23 -23
- crawlo/items/base.py +21 -21
- crawlo/items/fields.py +53 -53
- crawlo/items/items.py +104 -104
- crawlo/middleware/__init__.py +21 -21
- crawlo/middleware/default_header.py +132 -32
- crawlo/middleware/download_delay.py +105 -28
- crawlo/middleware/middleware_manager.py +135 -135
- crawlo/middleware/offsite.py +116 -0
- crawlo/middleware/proxy.py +366 -272
- crawlo/middleware/request_ignore.py +88 -30
- crawlo/middleware/response_code.py +164 -18
- crawlo/middleware/response_filter.py +138 -26
- crawlo/middleware/retry.py +124 -124
- crawlo/mode_manager.py +211 -211
- crawlo/network/__init__.py +21 -21
- crawlo/network/request.py +338 -338
- crawlo/network/response.py +359 -359
- crawlo/pipelines/__init__.py +21 -21
- crawlo/pipelines/bloom_dedup_pipeline.py +156 -156
- crawlo/pipelines/console_pipeline.py +39 -39
- crawlo/pipelines/csv_pipeline.py +316 -316
- crawlo/pipelines/database_dedup_pipeline.py +224 -224
- crawlo/pipelines/json_pipeline.py +218 -218
- crawlo/pipelines/memory_dedup_pipeline.py +115 -115
- crawlo/pipelines/mongo_pipeline.py +131 -131
- crawlo/pipelines/mysql_pipeline.py +316 -316
- crawlo/pipelines/pipeline_manager.py +61 -61
- crawlo/pipelines/redis_dedup_pipeline.py +167 -167
- crawlo/project.py +187 -187
- crawlo/queue/pqueue.py +37 -37
- crawlo/queue/queue_manager.py +337 -334
- crawlo/queue/redis_priority_queue.py +298 -298
- crawlo/settings/__init__.py +7 -7
- crawlo/settings/default_settings.py +226 -219
- crawlo/settings/setting_manager.py +122 -122
- crawlo/spider/__init__.py +639 -639
- crawlo/stats_collector.py +59 -59
- crawlo/subscriber.py +130 -130
- crawlo/task_manager.py +30 -30
- crawlo/templates/crawlo.cfg.tmpl +10 -10
- crawlo/templates/project/__init__.py.tmpl +3 -3
- crawlo/templates/project/items.py.tmpl +17 -17
- crawlo/templates/project/middlewares.py.tmpl +118 -109
- crawlo/templates/project/pipelines.py.tmpl +96 -96
- crawlo/templates/project/run.py.tmpl +45 -45
- crawlo/templates/project/settings.py.tmpl +327 -326
- crawlo/templates/project/settings_distributed.py.tmpl +119 -119
- crawlo/templates/project/settings_gentle.py.tmpl +94 -94
- crawlo/templates/project/settings_high_performance.py.tmpl +151 -151
- crawlo/templates/project/settings_simple.py.tmpl +68 -68
- crawlo/templates/project/spiders/__init__.py.tmpl +5 -5
- crawlo/templates/spider/spider.py.tmpl +143 -141
- crawlo/tools/__init__.py +182 -182
- crawlo/tools/anti_crawler.py +268 -268
- crawlo/tools/authenticated_proxy.py +240 -240
- crawlo/tools/data_validator.py +180 -180
- crawlo/tools/date_tools.py +35 -35
- crawlo/tools/distributed_coordinator.py +386 -386
- crawlo/tools/retry_mechanism.py +220 -220
- crawlo/tools/scenario_adapter.py +262 -262
- crawlo/utils/__init__.py +35 -35
- crawlo/utils/batch_processor.py +260 -260
- crawlo/utils/controlled_spider_mixin.py +439 -439
- crawlo/utils/date_tools.py +290 -290
- crawlo/utils/db_helper.py +343 -343
- crawlo/utils/enhanced_error_handler.py +359 -359
- crawlo/utils/env_config.py +105 -105
- crawlo/utils/error_handler.py +125 -125
- crawlo/utils/func_tools.py +82 -82
- crawlo/utils/large_scale_config.py +286 -286
- crawlo/utils/large_scale_helper.py +343 -343
- crawlo/utils/log.py +128 -128
- crawlo/utils/performance_monitor.py +284 -284
- crawlo/utils/queue_helper.py +175 -175
- crawlo/utils/redis_connection_pool.py +334 -334
- crawlo/utils/redis_key_validator.py +199 -199
- crawlo/utils/request.py +267 -267
- crawlo/utils/request_serializer.py +219 -219
- crawlo/utils/spider_loader.py +62 -62
- crawlo/utils/system.py +11 -11
- crawlo/utils/tools.py +4 -4
- crawlo/utils/url.py +39 -39
- crawlo-1.2.1.dist-info/METADATA +692 -0
- crawlo-1.2.1.dist-info/RECORD +220 -0
- examples/__init__.py +7 -7
- examples/aiohttp_settings.py +42 -0
- examples/curl_cffi_settings.py +41 -0
- examples/default_header_middleware_example.py +107 -0
- examples/default_header_spider_example.py +129 -0
- examples/download_delay_middleware_example.py +160 -0
- examples/httpx_settings.py +42 -0
- examples/multi_downloader_proxy_example.py +81 -0
- examples/offsite_middleware_example.py +55 -0
- examples/offsite_spider_example.py +107 -0
- examples/proxy_spider_example.py +166 -0
- examples/request_ignore_middleware_example.py +51 -0
- examples/request_ignore_spider_example.py +99 -0
- examples/response_code_middleware_example.py +52 -0
- examples/response_filter_middleware_example.py +67 -0
- examples/tong_hua_shun_settings.py +62 -0
- examples/tong_hua_shun_spider.py +170 -0
- tests/DOUBLE_CRAWLO_PREFIX_FIX_REPORT.md +81 -81
- tests/__init__.py +7 -7
- tests/advanced_tools_example.py +275 -275
- tests/authenticated_proxy_example.py +236 -236
- tests/cleaners_example.py +160 -160
- tests/config_validation_demo.py +102 -102
- tests/controlled_spider_example.py +205 -205
- tests/date_tools_example.py +180 -180
- tests/dynamic_loading_example.py +523 -523
- tests/dynamic_loading_test.py +104 -104
- tests/env_config_example.py +133 -133
- tests/error_handling_example.py +171 -171
- tests/redis_key_validation_demo.py +130 -130
- tests/response_improvements_example.py +144 -144
- tests/test_advanced_tools.py +148 -148
- tests/test_all_redis_key_configs.py +145 -145
- tests/test_authenticated_proxy.py +141 -141
- tests/test_cleaners.py +54 -54
- tests/test_comprehensive.py +146 -146
- tests/test_config_validator.py +193 -193
- tests/test_crawlo_proxy_integration.py +173 -0
- tests/test_date_tools.py +123 -123
- tests/test_default_header_middleware.py +159 -0
- tests/test_double_crawlo_fix.py +207 -207
- tests/test_double_crawlo_fix_simple.py +124 -124
- tests/test_download_delay_middleware.py +222 -0
- tests/test_downloader_proxy_compatibility.py +269 -0
- tests/test_dynamic_downloaders_proxy.py +124 -124
- tests/test_dynamic_proxy.py +92 -92
- tests/test_dynamic_proxy_config.py +146 -146
- tests/test_dynamic_proxy_real.py +109 -109
- tests/test_edge_cases.py +303 -303
- tests/test_enhanced_error_handler.py +270 -270
- tests/test_env_config.py +121 -121
- tests/test_error_handler_compatibility.py +112 -112
- tests/test_final_validation.py +153 -153
- tests/test_framework_env_usage.py +103 -103
- tests/test_integration.py +356 -356
- tests/test_item_dedup_redis_key.py +122 -122
- tests/test_offsite_middleware.py +222 -0
- tests/test_parsel.py +29 -29
- tests/test_performance.py +327 -327
- tests/test_proxy_api.py +265 -0
- tests/test_proxy_health_check.py +32 -32
- tests/test_proxy_middleware.py +122 -0
- tests/test_proxy_middleware_enhanced.py +217 -0
- tests/test_proxy_middleware_integration.py +136 -136
- tests/test_proxy_providers.py +56 -56
- tests/test_proxy_stats.py +19 -19
- tests/test_proxy_strategies.py +59 -59
- tests/test_queue_manager_double_crawlo.py +174 -231
- tests/test_queue_manager_redis_key.py +176 -176
- tests/test_real_scenario_proxy.py +196 -0
- tests/test_redis_config.py +28 -28
- tests/test_redis_connection_pool.py +294 -294
- tests/test_redis_key_naming.py +181 -181
- tests/test_redis_key_validator.py +123 -123
- tests/test_redis_queue.py +224 -224
- tests/test_request_ignore_middleware.py +183 -0
- tests/test_request_serialization.py +70 -70
- tests/test_response_code_middleware.py +350 -0
- tests/test_response_filter_middleware.py +428 -0
- tests/test_response_improvements.py +152 -152
- tests/test_retry_middleware.py +242 -0
- tests/test_scheduler.py +241 -241
- tests/test_simple_response.py +61 -61
- tests/test_telecom_spider_redis_key.py +205 -205
- tests/test_template_content.py +87 -87
- tests/test_template_redis_key.py +134 -134
- tests/test_tools.py +153 -153
- tests/tools_example.py +257 -257
- crawlo-1.1.9.dist-info/METADATA +0 -626
- crawlo-1.1.9.dist-info/RECORD +0 -190
- {crawlo-1.1.9.dist-info → crawlo-1.2.1.dist-info}/WHEEL +0 -0
- {crawlo-1.1.9.dist-info → crawlo-1.2.1.dist-info}/entry_points.txt +0 -0
- {crawlo-1.1.9.dist-info → crawlo-1.2.1.dist-info}/top_level.txt +0 -0
tests/test_redis_queue.py
CHANGED
|
@@ -1,225 +1,225 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
Redis 分布式队列测试脚本
|
|
5
|
-
用于诊断和修复分布式队列问题
|
|
6
|
-
"""
|
|
7
|
-
import asyncio
|
|
8
|
-
import sys
|
|
9
|
-
import traceback
|
|
10
|
-
import time
|
|
11
|
-
from crawlo.queue.redis_priority_queue import RedisPriorityQueue
|
|
12
|
-
from crawlo.network.request import Request
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
async def test_redis_connection():
|
|
16
|
-
"""测试 Redis 连接"""
|
|
17
|
-
print("🔍 1. 测试 Redis 连接...")
|
|
18
|
-
|
|
19
|
-
# 测试不同的 Redis URL 格式
|
|
20
|
-
test_urls = [
|
|
21
|
-
"redis://localhost:6379/0",
|
|
22
|
-
"redis://:oscar&0503@127.0.0.1:6379/0", # 带密码
|
|
23
|
-
"redis://127.0.0.1:6379/0", # 无密码
|
|
24
|
-
]
|
|
25
|
-
|
|
26
|
-
for redis_url in test_urls:
|
|
27
|
-
try:
|
|
28
|
-
print(f" 尝试连接: {redis_url}")
|
|
29
|
-
queue = RedisPriorityQueue(redis_url=redis_url)
|
|
30
|
-
await queue.connect()
|
|
31
|
-
print(f" ✅ 连接成功: {redis_url}")
|
|
32
|
-
await queue.close()
|
|
33
|
-
return redis_url
|
|
34
|
-
except Exception as e:
|
|
35
|
-
print(f" ❌ 连接失败: {redis_url} - {e}")
|
|
36
|
-
|
|
37
|
-
raise ConnectionError("所有 Redis URL 都连接失败")
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
async def test_queue_operations(redis_url):
|
|
41
|
-
"""测试队列基本操作"""
|
|
42
|
-
print("🔍 2. 测试队列基本操作...")
|
|
43
|
-
|
|
44
|
-
queue = RedisPriorityQueue(
|
|
45
|
-
redis_url=redis_url,
|
|
46
|
-
queue_name="test:crawlo:requests",
|
|
47
|
-
max_retries=2
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
try:
|
|
51
|
-
await queue.connect()
|
|
52
|
-
|
|
53
|
-
# 测试 put 操作
|
|
54
|
-
test_request = Request(url="https://example.com", priority=5)
|
|
55
|
-
print(f" 📤 插入请求: {test_request.url}")
|
|
56
|
-
|
|
57
|
-
success = await queue.put(test_request, priority=5)
|
|
58
|
-
if success:
|
|
59
|
-
print(" ✅ 插入成功")
|
|
60
|
-
else:
|
|
61
|
-
print(" ❌ 插入失败")
|
|
62
|
-
return False
|
|
63
|
-
|
|
64
|
-
# 测试队列大小
|
|
65
|
-
size = await queue.qsize()
|
|
66
|
-
print(f" 📊 队列大小: {size}")
|
|
67
|
-
|
|
68
|
-
# 测试 get 操作
|
|
69
|
-
print(" 📥 获取请求...")
|
|
70
|
-
retrieved_request = await queue.get(timeout=2.0)
|
|
71
|
-
|
|
72
|
-
if retrieved_request:
|
|
73
|
-
print(f" ✅ 获取成功: {retrieved_request.url}")
|
|
74
|
-
# 测试 ack
|
|
75
|
-
await queue.ack(retrieved_request)
|
|
76
|
-
print(" ✅ ACK 成功")
|
|
77
|
-
else:
|
|
78
|
-
print(" ❌ 获取失败(超时)")
|
|
79
|
-
return False
|
|
80
|
-
|
|
81
|
-
return True
|
|
82
|
-
|
|
83
|
-
except Exception as e:
|
|
84
|
-
print(f" ❌ 队列操作失败: {e}")
|
|
85
|
-
traceback.print_exc()
|
|
86
|
-
return False
|
|
87
|
-
finally:
|
|
88
|
-
await queue.close()
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
async def test_serialization():
|
|
92
|
-
"""测试序列化问题"""
|
|
93
|
-
print("🔍 3. 测试 Request 序列化...")
|
|
94
|
-
|
|
95
|
-
try:
|
|
96
|
-
import pickle
|
|
97
|
-
from crawlo.network.request import Request
|
|
98
|
-
|
|
99
|
-
# 创建测试请求
|
|
100
|
-
request = Request(
|
|
101
|
-
url="https://example.com",
|
|
102
|
-
method="GET",
|
|
103
|
-
headers={"User-Agent": "Test"},
|
|
104
|
-
meta={"test": "data"},
|
|
105
|
-
priority=5
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
# 测试序列化
|
|
109
|
-
serialized = pickle.dumps(request)
|
|
110
|
-
print(f" ✅ 序列化成功,大小: {len(serialized)} bytes")
|
|
111
|
-
|
|
112
|
-
# 测试反序列化
|
|
113
|
-
deserialized = pickle.loads(serialized)
|
|
114
|
-
print(f" ✅ 反序列化成功: {deserialized.url}")
|
|
115
|
-
|
|
116
|
-
return True
|
|
117
|
-
|
|
118
|
-
except Exception as e:
|
|
119
|
-
print(f" ❌ 序列化失败: {e}")
|
|
120
|
-
traceback.print_exc()
|
|
121
|
-
return False
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
async def test_concurrent_operations(redis_url):
|
|
125
|
-
"""测试并发操作"""
|
|
126
|
-
print("🔍 4. 测试并发操作...")
|
|
127
|
-
|
|
128
|
-
async def producer(queue, start_id):
|
|
129
|
-
"""生产者"""
|
|
130
|
-
try:
|
|
131
|
-
for i in range(5):
|
|
132
|
-
request = Request(url=f"https://example{start_id + i}.com", priority=i)
|
|
133
|
-
await queue.put(request, priority=i)
|
|
134
|
-
await asyncio.sleep(0.1)
|
|
135
|
-
print(f" ✅ 生产者 {start_id} 完成")
|
|
136
|
-
except Exception as e:
|
|
137
|
-
print(f" ❌ 生产者 {start_id} 失败: {e}")
|
|
138
|
-
|
|
139
|
-
async def consumer(queue, consumer_id):
|
|
140
|
-
"""消费者"""
|
|
141
|
-
consumed = 0
|
|
142
|
-
try:
|
|
143
|
-
for _ in range(3): # 每个消费者处理3个请求
|
|
144
|
-
request = await queue.get(timeout=5.0)
|
|
145
|
-
if request:
|
|
146
|
-
await queue.ack(request)
|
|
147
|
-
consumed += 1
|
|
148
|
-
await asyncio.sleep(0.05)
|
|
149
|
-
else:
|
|
150
|
-
break
|
|
151
|
-
print(f" ✅ 消费者 {consumer_id} 处理了 {consumed} 个请求")
|
|
152
|
-
except Exception as e:
|
|
153
|
-
print(f" ❌ 消费者 {consumer_id} 失败: {e}")
|
|
154
|
-
|
|
155
|
-
queue = RedisPriorityQueue(
|
|
156
|
-
redis_url=redis_url,
|
|
157
|
-
queue_name="test:concurrent:requests"
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
try:
|
|
161
|
-
await queue.connect()
|
|
162
|
-
|
|
163
|
-
# 并发运行生产者和消费者
|
|
164
|
-
tasks = [
|
|
165
|
-
producer(queue, 0),
|
|
166
|
-
producer(queue, 10),
|
|
167
|
-
consumer(queue, 1),
|
|
168
|
-
consumer(queue, 2),
|
|
169
|
-
]
|
|
170
|
-
|
|
171
|
-
await asyncio.gather(*tasks, return_exceptions=True)
|
|
172
|
-
|
|
173
|
-
# 检查剩余队列大小
|
|
174
|
-
final_size = await queue.qsize()
|
|
175
|
-
print(f" 📊 最终队列大小: {final_size}")
|
|
176
|
-
|
|
177
|
-
return True
|
|
178
|
-
|
|
179
|
-
except Exception as e:
|
|
180
|
-
print(f" ❌ 并发测试失败: {e}")
|
|
181
|
-
return False
|
|
182
|
-
finally:
|
|
183
|
-
await queue.close()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
async def main():
|
|
187
|
-
"""主测试函数"""
|
|
188
|
-
print("🚀 开始 Redis 分布式队列诊断...")
|
|
189
|
-
print("=" * 50)
|
|
190
|
-
|
|
191
|
-
try:
|
|
192
|
-
# 1. 测试连接
|
|
193
|
-
redis_url = await test_redis_connection()
|
|
194
|
-
|
|
195
|
-
# 2. 测试序列化
|
|
196
|
-
if not await test_serialization():
|
|
197
|
-
return
|
|
198
|
-
|
|
199
|
-
# 3. 测试基本操作
|
|
200
|
-
if not await test_queue_operations(redis_url):
|
|
201
|
-
return
|
|
202
|
-
|
|
203
|
-
# 4. 测试并发操作
|
|
204
|
-
if not await test_concurrent_operations(redis_url):
|
|
205
|
-
return
|
|
206
|
-
|
|
207
|
-
print("=" * 50)
|
|
208
|
-
print("🎉 所有测试通过!Redis 队列工作正常")
|
|
209
|
-
|
|
210
|
-
except Exception as e:
|
|
211
|
-
print("=" * 50)
|
|
212
|
-
print(f"❌ 诊断失败: {e}")
|
|
213
|
-
traceback.print_exc()
|
|
214
|
-
|
|
215
|
-
# 提供解决建议
|
|
216
|
-
print("\n🔧 可能的解决方案:")
|
|
217
|
-
print("1. 检查 Redis 服务是否启动: redis-server")
|
|
218
|
-
print("2. 检查 Redis 密码配置")
|
|
219
|
-
print("3. 检查防火墙和端口 6379")
|
|
220
|
-
print("4. 安装 Redis: pip install redis")
|
|
221
|
-
print("5. 检查 Redis 配置文件中的 bind 设置")
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if __name__ == "__main__":
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Redis 分布式队列测试脚本
|
|
5
|
+
用于诊断和修复分布式队列问题
|
|
6
|
+
"""
|
|
7
|
+
import asyncio
|
|
8
|
+
import sys
|
|
9
|
+
import traceback
|
|
10
|
+
import time
|
|
11
|
+
from crawlo.queue.redis_priority_queue import RedisPriorityQueue
|
|
12
|
+
from crawlo.network.request import Request
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def test_redis_connection():
|
|
16
|
+
"""测试 Redis 连接"""
|
|
17
|
+
print("🔍 1. 测试 Redis 连接...")
|
|
18
|
+
|
|
19
|
+
# 测试不同的 Redis URL 格式
|
|
20
|
+
test_urls = [
|
|
21
|
+
"redis://localhost:6379/0",
|
|
22
|
+
"redis://:oscar&0503@127.0.0.1:6379/0", # 带密码
|
|
23
|
+
"redis://127.0.0.1:6379/0", # 无密码
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
for redis_url in test_urls:
|
|
27
|
+
try:
|
|
28
|
+
print(f" 尝试连接: {redis_url}")
|
|
29
|
+
queue = RedisPriorityQueue(redis_url=redis_url)
|
|
30
|
+
await queue.connect()
|
|
31
|
+
print(f" ✅ 连接成功: {redis_url}")
|
|
32
|
+
await queue.close()
|
|
33
|
+
return redis_url
|
|
34
|
+
except Exception as e:
|
|
35
|
+
print(f" ❌ 连接失败: {redis_url} - {e}")
|
|
36
|
+
|
|
37
|
+
raise ConnectionError("所有 Redis URL 都连接失败")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def test_queue_operations(redis_url):
|
|
41
|
+
"""测试队列基本操作"""
|
|
42
|
+
print("🔍 2. 测试队列基本操作...")
|
|
43
|
+
|
|
44
|
+
queue = RedisPriorityQueue(
|
|
45
|
+
redis_url=redis_url,
|
|
46
|
+
queue_name="test:crawlo:requests",
|
|
47
|
+
max_retries=2
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
await queue.connect()
|
|
52
|
+
|
|
53
|
+
# 测试 put 操作
|
|
54
|
+
test_request = Request(url="https://example.com", priority=5)
|
|
55
|
+
print(f" 📤 插入请求: {test_request.url}")
|
|
56
|
+
|
|
57
|
+
success = await queue.put(test_request, priority=5)
|
|
58
|
+
if success:
|
|
59
|
+
print(" ✅ 插入成功")
|
|
60
|
+
else:
|
|
61
|
+
print(" ❌ 插入失败")
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
# 测试队列大小
|
|
65
|
+
size = await queue.qsize()
|
|
66
|
+
print(f" 📊 队列大小: {size}")
|
|
67
|
+
|
|
68
|
+
# 测试 get 操作
|
|
69
|
+
print(" 📥 获取请求...")
|
|
70
|
+
retrieved_request = await queue.get(timeout=2.0)
|
|
71
|
+
|
|
72
|
+
if retrieved_request:
|
|
73
|
+
print(f" ✅ 获取成功: {retrieved_request.url}")
|
|
74
|
+
# 测试 ack
|
|
75
|
+
await queue.ack(retrieved_request)
|
|
76
|
+
print(" ✅ ACK 成功")
|
|
77
|
+
else:
|
|
78
|
+
print(" ❌ 获取失败(超时)")
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(f" ❌ 队列操作失败: {e}")
|
|
85
|
+
traceback.print_exc()
|
|
86
|
+
return False
|
|
87
|
+
finally:
|
|
88
|
+
await queue.close()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def test_serialization():
|
|
92
|
+
"""测试序列化问题"""
|
|
93
|
+
print("🔍 3. 测试 Request 序列化...")
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
import pickle
|
|
97
|
+
from crawlo.network.request import Request
|
|
98
|
+
|
|
99
|
+
# 创建测试请求
|
|
100
|
+
request = Request(
|
|
101
|
+
url="https://example.com",
|
|
102
|
+
method="GET",
|
|
103
|
+
headers={"User-Agent": "Test"},
|
|
104
|
+
meta={"test": "data"},
|
|
105
|
+
priority=5
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# 测试序列化
|
|
109
|
+
serialized = pickle.dumps(request)
|
|
110
|
+
print(f" ✅ 序列化成功,大小: {len(serialized)} bytes")
|
|
111
|
+
|
|
112
|
+
# 测试反序列化
|
|
113
|
+
deserialized = pickle.loads(serialized)
|
|
114
|
+
print(f" ✅ 反序列化成功: {deserialized.url}")
|
|
115
|
+
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
print(f" ❌ 序列化失败: {e}")
|
|
120
|
+
traceback.print_exc()
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
async def test_concurrent_operations(redis_url):
|
|
125
|
+
"""测试并发操作"""
|
|
126
|
+
print("🔍 4. 测试并发操作...")
|
|
127
|
+
|
|
128
|
+
async def producer(queue, start_id):
|
|
129
|
+
"""生产者"""
|
|
130
|
+
try:
|
|
131
|
+
for i in range(5):
|
|
132
|
+
request = Request(url=f"https://example{start_id + i}.com", priority=i)
|
|
133
|
+
await queue.put(request, priority=i)
|
|
134
|
+
await asyncio.sleep(0.1)
|
|
135
|
+
print(f" ✅ 生产者 {start_id} 完成")
|
|
136
|
+
except Exception as e:
|
|
137
|
+
print(f" ❌ 生产者 {start_id} 失败: {e}")
|
|
138
|
+
|
|
139
|
+
async def consumer(queue, consumer_id):
|
|
140
|
+
"""消费者"""
|
|
141
|
+
consumed = 0
|
|
142
|
+
try:
|
|
143
|
+
for _ in range(3): # 每个消费者处理3个请求
|
|
144
|
+
request = await queue.get(timeout=5.0)
|
|
145
|
+
if request:
|
|
146
|
+
await queue.ack(request)
|
|
147
|
+
consumed += 1
|
|
148
|
+
await asyncio.sleep(0.05)
|
|
149
|
+
else:
|
|
150
|
+
break
|
|
151
|
+
print(f" ✅ 消费者 {consumer_id} 处理了 {consumed} 个请求")
|
|
152
|
+
except Exception as e:
|
|
153
|
+
print(f" ❌ 消费者 {consumer_id} 失败: {e}")
|
|
154
|
+
|
|
155
|
+
queue = RedisPriorityQueue(
|
|
156
|
+
redis_url=redis_url,
|
|
157
|
+
queue_name="test:concurrent:requests"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
await queue.connect()
|
|
162
|
+
|
|
163
|
+
# 并发运行生产者和消费者
|
|
164
|
+
tasks = [
|
|
165
|
+
producer(queue, 0),
|
|
166
|
+
producer(queue, 10),
|
|
167
|
+
consumer(queue, 1),
|
|
168
|
+
consumer(queue, 2),
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
172
|
+
|
|
173
|
+
# 检查剩余队列大小
|
|
174
|
+
final_size = await queue.qsize()
|
|
175
|
+
print(f" 📊 最终队列大小: {final_size}")
|
|
176
|
+
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
print(f" ❌ 并发测试失败: {e}")
|
|
181
|
+
return False
|
|
182
|
+
finally:
|
|
183
|
+
await queue.close()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
async def main():
|
|
187
|
+
"""主测试函数"""
|
|
188
|
+
print("🚀 开始 Redis 分布式队列诊断...")
|
|
189
|
+
print("=" * 50)
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
# 1. 测试连接
|
|
193
|
+
redis_url = await test_redis_connection()
|
|
194
|
+
|
|
195
|
+
# 2. 测试序列化
|
|
196
|
+
if not await test_serialization():
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
# 3. 测试基本操作
|
|
200
|
+
if not await test_queue_operations(redis_url):
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
# 4. 测试并发操作
|
|
204
|
+
if not await test_concurrent_operations(redis_url):
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
print("=" * 50)
|
|
208
|
+
print("🎉 所有测试通过!Redis 队列工作正常")
|
|
209
|
+
|
|
210
|
+
except Exception as e:
|
|
211
|
+
print("=" * 50)
|
|
212
|
+
print(f"❌ 诊断失败: {e}")
|
|
213
|
+
traceback.print_exc()
|
|
214
|
+
|
|
215
|
+
# 提供解决建议
|
|
216
|
+
print("\n🔧 可能的解决方案:")
|
|
217
|
+
print("1. 检查 Redis 服务是否启动: redis-server")
|
|
218
|
+
print("2. 检查 Redis 密码配置")
|
|
219
|
+
print("3. 检查防火墙和端口 6379")
|
|
220
|
+
print("4. 安装 Redis: pip install redis")
|
|
221
|
+
print("5. 检查 Redis 配置文件中的 bind 设置")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
if __name__ == "__main__":
|
|
225
225
|
asyncio.run(main())
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding:UTF-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
RequestIgnoreMiddleware 测试文件
|
|
5
|
+
用于测试请求忽略中间件的功能
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import unittest
|
|
10
|
+
from unittest.mock import Mock, patch
|
|
11
|
+
|
|
12
|
+
from crawlo.middleware.request_ignore import RequestIgnoreMiddleware
|
|
13
|
+
from crawlo.exceptions import IgnoreRequestError
|
|
14
|
+
from crawlo.settings.setting_manager import SettingManager
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MockLogger:
|
|
18
|
+
"""Mock Logger 类,用于测试日志输出"""
|
|
19
|
+
def __init__(self, name, level=None):
|
|
20
|
+
self.name = name
|
|
21
|
+
self.level = level
|
|
22
|
+
self.logs = []
|
|
23
|
+
|
|
24
|
+
def debug(self, msg):
|
|
25
|
+
self.logs.append(('debug', msg))
|
|
26
|
+
|
|
27
|
+
def info(self, msg):
|
|
28
|
+
self.logs.append(('info', msg))
|
|
29
|
+
|
|
30
|
+
def warning(self, msg):
|
|
31
|
+
self.logs.append(('warning', msg))
|
|
32
|
+
|
|
33
|
+
def error(self, msg):
|
|
34
|
+
self.logs.append(('error', msg))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MockStats:
|
|
38
|
+
"""Mock Stats 类,用于测试统计信息"""
|
|
39
|
+
def __init__(self):
|
|
40
|
+
self.stats = {}
|
|
41
|
+
|
|
42
|
+
def inc_value(self, key, value=1):
|
|
43
|
+
if key in self.stats:
|
|
44
|
+
self.stats[key] += value
|
|
45
|
+
else:
|
|
46
|
+
self.stats[key] = value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestRequestIgnoreMiddleware(unittest.TestCase):
|
|
50
|
+
"""RequestIgnoreMiddleware 测试类"""
|
|
51
|
+
|
|
52
|
+
def setUp(self):
|
|
53
|
+
"""测试前准备"""
|
|
54
|
+
# 创建设置管理器
|
|
55
|
+
self.settings = SettingManager()
|
|
56
|
+
|
|
57
|
+
# 创建爬虫模拟对象
|
|
58
|
+
self.crawler = Mock()
|
|
59
|
+
self.crawler.settings = self.settings
|
|
60
|
+
self.crawler.stats = MockStats()
|
|
61
|
+
|
|
62
|
+
@patch('crawlo.utils.log.get_logger')
|
|
63
|
+
def test_middleware_initialization(self, mock_get_logger):
|
|
64
|
+
"""测试中间件初始化"""
|
|
65
|
+
self.settings.set('LOG_LEVEL', 'INFO')
|
|
66
|
+
mock_get_logger.return_value = MockLogger('RequestIgnoreMiddleware')
|
|
67
|
+
|
|
68
|
+
# 应该正常创建实例
|
|
69
|
+
middleware = RequestIgnoreMiddleware.create_instance(self.crawler)
|
|
70
|
+
self.assertIsInstance(middleware, RequestIgnoreMiddleware)
|
|
71
|
+
|
|
72
|
+
@patch('crawlo.utils.log.get_logger')
|
|
73
|
+
def test_request_ignore_event_handling(self, mock_get_logger):
|
|
74
|
+
"""测试请求忽略事件处理"""
|
|
75
|
+
self.settings.set('LOG_LEVEL', 'DEBUG')
|
|
76
|
+
mock_logger = MockLogger('RequestIgnoreMiddleware')
|
|
77
|
+
mock_get_logger.return_value = mock_logger
|
|
78
|
+
|
|
79
|
+
# 创建中间件实例
|
|
80
|
+
mock_stats = MockStats()
|
|
81
|
+
middleware = RequestIgnoreMiddleware(
|
|
82
|
+
stats=mock_stats,
|
|
83
|
+
log_level='DEBUG'
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# 创建异常和请求对象
|
|
87
|
+
exc = IgnoreRequestError("test reason")
|
|
88
|
+
request = Mock()
|
|
89
|
+
request.url = 'http://example.com/page'
|
|
90
|
+
|
|
91
|
+
# 处理忽略事件
|
|
92
|
+
asyncio.run(middleware.request_ignore(exc, request, Mock()))
|
|
93
|
+
|
|
94
|
+
# 验证统计信息
|
|
95
|
+
self.assertIn('request_ignore_count', mock_stats.stats)
|
|
96
|
+
self.assertEqual(mock_stats.stats['request_ignore_count'], 1)
|
|
97
|
+
self.assertIn('request_ignore_count/reason/test reason', mock_stats.stats)
|
|
98
|
+
|
|
99
|
+
@patch('crawlo.utils.log.get_logger')
|
|
100
|
+
def test_request_ignore_event_handling_with_domain(self, mock_get_logger):
|
|
101
|
+
"""测试带域名的请求忽略事件处理"""
|
|
102
|
+
self.settings.set('LOG_LEVEL', 'DEBUG')
|
|
103
|
+
mock_logger = MockLogger('RequestIgnoreMiddleware')
|
|
104
|
+
mock_get_logger.return_value = mock_logger
|
|
105
|
+
|
|
106
|
+
# 创建中间件实例
|
|
107
|
+
mock_stats = MockStats()
|
|
108
|
+
middleware = RequestIgnoreMiddleware(
|
|
109
|
+
stats=mock_stats,
|
|
110
|
+
log_level='DEBUG'
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# 创建异常和请求对象
|
|
114
|
+
exc = IgnoreRequestError("test reason")
|
|
115
|
+
request = Mock()
|
|
116
|
+
request.url = 'http://example.com/page'
|
|
117
|
+
|
|
118
|
+
# 处理忽略事件
|
|
119
|
+
asyncio.run(middleware.request_ignore(exc, request, Mock()))
|
|
120
|
+
|
|
121
|
+
# 验证域名统计信息
|
|
122
|
+
self.assertIn('request_ignore_count/domain/example.com', mock_stats.stats)
|
|
123
|
+
|
|
124
|
+
@patch('crawlo.utils.log.get_logger')
|
|
125
|
+
def test_request_ignore_event_handling_with_invalid_url(self, mock_get_logger):
|
|
126
|
+
"""测试带无效URL的请求忽略事件处理"""
|
|
127
|
+
self.settings.set('LOG_LEVEL', 'DEBUG')
|
|
128
|
+
mock_logger = MockLogger('RequestIgnoreMiddleware')
|
|
129
|
+
mock_get_logger.return_value = mock_logger
|
|
130
|
+
|
|
131
|
+
# 创建中间件实例
|
|
132
|
+
mock_stats = MockStats()
|
|
133
|
+
middleware = RequestIgnoreMiddleware(
|
|
134
|
+
stats=mock_stats,
|
|
135
|
+
log_level='DEBUG'
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# 创建异常和请求对象(没有url属性,会触发异常)
|
|
139
|
+
exc = IgnoreRequestError("test reason")
|
|
140
|
+
request = Mock()
|
|
141
|
+
# 不设置request.url属性,这样在访问时会抛出AttributeError
|
|
142
|
+
|
|
143
|
+
# 处理忽略事件
|
|
144
|
+
asyncio.run(middleware.request_ignore(exc, request, Mock()))
|
|
145
|
+
|
|
146
|
+
# 验证无效URL统计信息
|
|
147
|
+
self.assertIn('request_ignore_count/domain/invalid_url', mock_stats.stats)
|
|
148
|
+
|
|
149
|
+
def test_process_exception_with_ignore_request_error(self):
|
|
150
|
+
"""测试处理IgnoreRequestError异常"""
|
|
151
|
+
# 创建中间件实例
|
|
152
|
+
middleware = RequestIgnoreMiddleware(
|
|
153
|
+
stats=MockStats(),
|
|
154
|
+
log_level='INFO'
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# 创建异常和请求对象
|
|
158
|
+
exc = IgnoreRequestError("test reason")
|
|
159
|
+
request = Mock()
|
|
160
|
+
|
|
161
|
+
# 应该返回True表示异常已被处理
|
|
162
|
+
result = middleware.process_exception(request, exc, Mock())
|
|
163
|
+
self.assertTrue(result)
|
|
164
|
+
|
|
165
|
+
def test_process_exception_with_other_exception(self):
|
|
166
|
+
"""测试处理其他异常"""
|
|
167
|
+
# 创建中间件实例
|
|
168
|
+
middleware = RequestIgnoreMiddleware(
|
|
169
|
+
stats=MockStats(),
|
|
170
|
+
log_level='INFO'
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# 创建异常和请求对象
|
|
174
|
+
exc = ValueError("test error")
|
|
175
|
+
request = Mock()
|
|
176
|
+
|
|
177
|
+
# 应该返回None表示异常未被处理
|
|
178
|
+
result = middleware.process_exception(request, exc, Mock())
|
|
179
|
+
self.assertIsNone(result)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
if __name__ == '__main__':
|
|
183
|
+
unittest.main()
|