crawlo 1.4.1__py3-none-any.whl → 1.4.3__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 +93 -93
- crawlo/__version__.py +1 -1
- crawlo/cli.py +75 -75
- crawlo/commands/__init__.py +14 -14
- crawlo/commands/check.py +594 -594
- crawlo/commands/genspider.py +151 -151
- crawlo/commands/help.py +138 -138
- crawlo/commands/list.py +155 -155
- crawlo/commands/run.py +341 -341
- crawlo/commands/startproject.py +436 -436
- crawlo/commands/stats.py +187 -187
- crawlo/commands/utils.py +196 -196
- crawlo/config.py +312 -312
- crawlo/config_validator.py +277 -277
- crawlo/core/__init__.py +52 -52
- crawlo/core/engine.py +438 -439
- crawlo/core/processor.py +47 -47
- crawlo/core/scheduler.py +291 -257
- crawlo/crawler.py +650 -650
- crawlo/data/__init__.py +5 -5
- crawlo/data/user_agents.py +194 -194
- crawlo/downloader/__init__.py +273 -273
- crawlo/downloader/aiohttp_downloader.py +233 -228
- crawlo/downloader/cffi_downloader.py +245 -245
- crawlo/downloader/httpx_downloader.py +259 -259
- crawlo/downloader/hybrid_downloader.py +212 -212
- 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 +63 -63
- crawlo/extension/health_check.py +141 -141
- crawlo/extension/log_interval.py +94 -94
- crawlo/extension/log_stats.py +70 -70
- crawlo/extension/logging_extension.py +61 -61
- crawlo/extension/memory_monitor.py +104 -104
- crawlo/extension/performance_profiler.py +133 -133
- crawlo/extension/request_recorder.py +107 -107
- crawlo/factories/__init__.py +27 -27
- crawlo/factories/base.py +68 -68
- crawlo/factories/crawler.py +103 -103
- crawlo/factories/registry.py +84 -84
- crawlo/filters/__init__.py +154 -154
- crawlo/filters/aioredis_filter.py +257 -257
- crawlo/filters/memory_filter.py +269 -269
- crawlo/framework.py +292 -292
- crawlo/initialization/__init__.py +44 -44
- crawlo/initialization/built_in.py +425 -425
- crawlo/initialization/context.py +141 -141
- crawlo/initialization/core.py +193 -193
- crawlo/initialization/phases.py +148 -148
- crawlo/initialization/registry.py +145 -145
- crawlo/items/__init__.py +23 -23
- crawlo/items/base.py +23 -23
- crawlo/items/fields.py +52 -52
- crawlo/items/items.py +104 -104
- crawlo/logging/__init__.py +45 -37
- crawlo/logging/async_handler.py +181 -0
- crawlo/logging/config.py +196 -96
- crawlo/logging/factory.py +171 -128
- crawlo/logging/manager.py +111 -111
- crawlo/logging/monitor.py +153 -0
- crawlo/logging/sampler.py +167 -0
- crawlo/middleware/__init__.py +21 -21
- crawlo/middleware/default_header.py +132 -132
- crawlo/middleware/download_delay.py +104 -104
- crawlo/middleware/middleware_manager.py +135 -135
- crawlo/middleware/offsite.py +123 -123
- crawlo/middleware/proxy.py +386 -386
- crawlo/middleware/request_ignore.py +86 -86
- crawlo/middleware/response_code.py +150 -150
- crawlo/middleware/response_filter.py +136 -136
- crawlo/middleware/retry.py +124 -124
- crawlo/middleware/simple_proxy.py +65 -65
- crawlo/mode_manager.py +219 -219
- crawlo/network/__init__.py +21 -21
- crawlo/network/request.py +379 -379
- crawlo/network/response.py +359 -359
- crawlo/pipelines/__init__.py +21 -21
- crawlo/pipelines/bloom_dedup_pipeline.py +146 -146
- crawlo/pipelines/console_pipeline.py +39 -39
- crawlo/pipelines/csv_pipeline.py +316 -316
- crawlo/pipelines/database_dedup_pipeline.py +197 -197
- crawlo/pipelines/json_pipeline.py +218 -218
- crawlo/pipelines/memory_dedup_pipeline.py +105 -105
- crawlo/pipelines/mongo_pipeline.py +131 -131
- crawlo/pipelines/mysql_pipeline.py +325 -325
- crawlo/pipelines/pipeline_manager.py +100 -84
- crawlo/pipelines/redis_dedup_pipeline.py +156 -156
- crawlo/project.py +349 -338
- crawlo/queue/pqueue.py +42 -42
- crawlo/queue/queue_manager.py +526 -522
- crawlo/queue/redis_priority_queue.py +370 -367
- crawlo/settings/__init__.py +7 -7
- crawlo/settings/default_settings.py +284 -284
- crawlo/settings/setting_manager.py +219 -219
- crawlo/spider/__init__.py +657 -657
- crawlo/stats_collector.py +73 -73
- crawlo/subscriber.py +129 -129
- crawlo/task_manager.py +138 -138
- 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 -118
- crawlo/templates/project/pipelines.py.tmpl +96 -96
- crawlo/templates/project/settings.py.tmpl +170 -170
- crawlo/templates/project/settings_distributed.py.tmpl +169 -169
- crawlo/templates/project/settings_gentle.py.tmpl +166 -166
- crawlo/templates/project/settings_high_performance.py.tmpl +167 -167
- crawlo/templates/project/settings_minimal.py.tmpl +65 -65
- crawlo/templates/project/settings_simple.py.tmpl +164 -164
- crawlo/templates/project/spiders/__init__.py.tmpl +9 -9
- crawlo/templates/run.py.tmpl +34 -34
- crawlo/templates/spider/spider.py.tmpl +143 -143
- crawlo/templates/spiders_init.py.tmpl +9 -9
- crawlo/tools/__init__.py +200 -200
- crawlo/tools/anti_crawler.py +268 -268
- crawlo/tools/authenticated_proxy.py +240 -240
- crawlo/tools/data_formatter.py +225 -225
- crawlo/tools/data_validator.py +180 -180
- crawlo/tools/date_tools.py +289 -289
- crawlo/tools/distributed_coordinator.py +384 -384
- crawlo/tools/encoding_converter.py +127 -127
- crawlo/tools/network_diagnostic.py +364 -364
- crawlo/tools/request_tools.py +82 -82
- crawlo/tools/retry_mechanism.py +224 -224
- crawlo/tools/scenario_adapter.py +262 -262
- crawlo/tools/text_cleaner.py +232 -232
- crawlo/utils/__init__.py +34 -34
- crawlo/utils/batch_processor.py +259 -259
- crawlo/utils/class_loader.py +25 -25
- crawlo/utils/controlled_spider_mixin.py +439 -439
- crawlo/utils/db_helper.py +343 -343
- crawlo/utils/enhanced_error_handler.py +356 -356
- crawlo/utils/env_config.py +142 -142
- crawlo/utils/error_handler.py +165 -165
- crawlo/utils/fingerprint.py +122 -122
- crawlo/utils/func_tools.py +82 -82
- crawlo/utils/large_scale_config.py +286 -286
- crawlo/utils/large_scale_helper.py +344 -344
- crawlo/utils/log.py +79 -79
- crawlo/utils/performance_monitor.py +285 -285
- crawlo/utils/queue_helper.py +175 -175
- crawlo/utils/redis_connection_pool.py +388 -388
- crawlo/utils/redis_key_validator.py +198 -198
- crawlo/utils/request.py +267 -267
- crawlo/utils/request_serializer.py +225 -225
- crawlo/utils/spider_loader.py +61 -61
- crawlo/utils/system.py +11 -11
- crawlo/utils/tools.py +4 -4
- crawlo/utils/url.py +39 -39
- crawlo-1.4.3.dist-info/METADATA +190 -0
- crawlo-1.4.3.dist-info/RECORD +326 -0
- examples/__init__.py +7 -7
- examples/test_project/__init__.py +7 -7
- examples/test_project/run.py +34 -34
- examples/test_project/test_project/__init__.py +3 -3
- examples/test_project/test_project/items.py +17 -17
- examples/test_project/test_project/middlewares.py +118 -118
- examples/test_project/test_project/pipelines.py +96 -96
- examples/test_project/test_project/settings.py +169 -169
- examples/test_project/test_project/spiders/__init__.py +9 -9
- examples/test_project/test_project/spiders/of_week_dis.py +143 -143
- tests/__init__.py +7 -7
- tests/advanced_tools_example.py +275 -275
- tests/authenticated_proxy_example.py +106 -106
- tests/baidu_performance_test.py +108 -108
- tests/baidu_test.py +59 -59
- tests/cleaners_example.py +160 -160
- tests/comprehensive_framework_test.py +212 -212
- tests/comprehensive_test.py +81 -81
- tests/comprehensive_testing_summary.md +186 -186
- tests/config_validation_demo.py +142 -142
- tests/controlled_spider_example.py +205 -205
- tests/date_tools_example.py +180 -180
- tests/debug_configure.py +69 -69
- tests/debug_framework_logger.py +84 -84
- tests/debug_log_config.py +126 -126
- tests/debug_log_levels.py +63 -63
- tests/debug_pipelines.py +66 -66
- tests/detailed_log_test.py +233 -233
- tests/distributed_test.py +66 -66
- tests/distributed_test_debug.py +76 -76
- 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/final_comprehensive_test.py +151 -151
- tests/final_log_test.py +260 -260
- tests/final_validation_test.py +182 -182
- tests/fix_log_test.py +142 -142
- tests/framework_performance_test.py +202 -202
- tests/log_buffering_test.py +111 -111
- tests/log_generation_timing_test.py +153 -153
- tests/optimized_performance_test.py +211 -211
- tests/performance_comparison.py +245 -245
- tests/queue_blocking_test.py +113 -113
- tests/queue_test.py +89 -89
- tests/redis_key_validation_demo.py +130 -130
- tests/request_params_example.py +150 -150
- tests/response_improvements_example.py +144 -144
- tests/scrapy_comparison/ofweek_scrapy.py +138 -138
- tests/scrapy_comparison/scrapy_test.py +133 -133
- tests/simple_command_test.py +119 -119
- tests/simple_crawlo_test.py +127 -127
- tests/simple_log_test.py +57 -57
- tests/simple_log_test2.py +137 -137
- tests/simple_optimization_test.py +128 -128
- tests/simple_queue_type_test.py +41 -41
- tests/simple_spider_test.py +49 -49
- tests/simple_test.py +47 -47
- tests/spider_log_timing_test.py +177 -177
- tests/test_advanced_tools.py +148 -148
- tests/test_all_commands.py +230 -230
- tests/test_all_pipeline_fingerprints.py +133 -133
- tests/test_all_redis_key_configs.py +145 -145
- tests/test_authenticated_proxy.py +141 -141
- tests/test_batch_processor.py +178 -178
- tests/test_cleaners.py +54 -54
- tests/test_component_factory.py +174 -174
- tests/test_comprehensive.py +146 -146
- tests/test_config_consistency.py +80 -80
- tests/test_config_merge.py +152 -152
- tests/test_config_validator.py +182 -182
- tests/test_controlled_spider_mixin.py +79 -79
- tests/test_crawlo_proxy_integration.py +108 -108
- tests/test_date_tools.py +123 -123
- tests/test_dedup_fix.py +220 -220
- tests/test_dedup_pipeline_consistency.py +125 -0
- tests/test_default_header_middleware.py +313 -313
- tests/test_distributed.py +65 -65
- tests/test_double_crawlo_fix.py +204 -204
- tests/test_double_crawlo_fix_simple.py +124 -124
- tests/test_download_delay_middleware.py +221 -221
- tests/test_downloader_proxy_compatibility.py +268 -268
- 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_enhanced_error_handler_comprehensive.py +245 -245
- tests/test_env_config.py +121 -121
- tests/test_error_handler_compatibility.py +112 -112
- tests/test_factories.py +252 -252
- tests/test_final_validation.py +153 -153
- tests/test_fingerprint_consistency.py +135 -135
- tests/test_fingerprint_simple.py +51 -51
- tests/test_framework_env_usage.py +103 -103
- tests/test_framework_logger.py +66 -66
- tests/test_framework_startup.py +64 -64
- tests/test_get_component_logger.py +83 -83
- tests/test_hash_performance.py +99 -99
- tests/test_integration.py +169 -169
- tests/test_item_dedup_redis_key.py +122 -122
- tests/test_large_scale_config.py +112 -112
- tests/test_large_scale_helper.py +235 -235
- tests/test_logging_enhancements.py +375 -0
- tests/test_logging_final.py +185 -0
- tests/test_logging_integration.py +313 -0
- tests/test_logging_system.py +282 -282
- tests/test_middleware_debug.py +142 -0
- tests/test_mode_change.py +72 -72
- tests/test_mode_consistency.py +51 -51
- tests/test_offsite_middleware.py +244 -244
- tests/test_offsite_middleware_simple.py +203 -203
- tests/test_parsel.py +29 -29
- tests/test_performance.py +327 -327
- tests/test_performance_monitor.py +115 -115
- tests/test_pipeline_fingerprint_consistency.py +86 -86
- tests/test_priority_behavior.py +212 -0
- tests/test_priority_consistency.py +152 -0
- tests/test_priority_consistency_fixed.py +250 -0
- tests/test_proxy_api.py +264 -264
- tests/test_proxy_health_check.py +32 -32
- tests/test_proxy_middleware.py +121 -121
- tests/test_proxy_middleware_enhanced.py +216 -216
- tests/test_proxy_middleware_integration.py +136 -136
- tests/test_proxy_middleware_refactored.py +184 -184
- tests/test_proxy_providers.py +56 -56
- tests/test_proxy_stats.py +19 -19
- tests/test_proxy_strategies.py +59 -59
- tests/test_queue_empty_check.py +41 -41
- tests/test_queue_manager_double_crawlo.py +173 -173
- tests/test_queue_manager_redis_key.py +179 -179
- tests/test_queue_naming.py +154 -154
- tests/test_queue_type.py +106 -106
- tests/test_queue_type_redis_config_consistency.py +131 -0
- tests/test_random_headers_default.py +323 -0
- tests/test_random_headers_necessity.py +309 -0
- tests/test_random_user_agent.py +72 -72
- tests/test_real_scenario_proxy.py +195 -195
- 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_redis_queue_name_fix.py +175 -175
- tests/test_redis_queue_type_fallback.py +130 -0
- tests/test_request_ignore_middleware.py +182 -182
- tests/test_request_params.py +111 -111
- tests/test_request_serialization.py +70 -70
- tests/test_response_code_middleware.py +349 -349
- tests/test_response_filter_middleware.py +427 -427
- tests/test_response_improvements.py +152 -152
- tests/test_retry_middleware.py +334 -242
- tests/test_retry_middleware_realistic.py +274 -0
- tests/test_scheduler.py +252 -252
- tests/test_scheduler_config_update.py +133 -133
- 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 +159 -159
- tests/test_user_agent_randomness.py +177 -0
- tests/test_user_agents.py +96 -96
- tests/tools_example.py +260 -260
- tests/untested_features_report.md +138 -138
- tests/verify_debug.py +51 -51
- tests/verify_distributed.py +117 -117
- tests/verify_log_fix.py +111 -111
- crawlo-1.4.1.dist-info/METADATA +0 -1199
- crawlo-1.4.1.dist-info/RECORD +0 -309
- {crawlo-1.4.1.dist-info → crawlo-1.4.3.dist-info}/WHEEL +0 -0
- {crawlo-1.4.1.dist-info → crawlo-1.4.3.dist-info}/entry_points.txt +0 -0
- {crawlo-1.4.1.dist-info → crawlo-1.4.3.dist-info}/top_level.txt +0 -0
crawlo/event.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#!/usr/bin/python
|
|
2
|
-
# -*- coding:UTF-8 -*-
|
|
3
|
-
|
|
4
|
-
spider_error = "spider_error"
|
|
5
|
-
spider_opened = "spider_open"
|
|
6
|
-
spider_closed = "spider_closed"
|
|
7
|
-
ignore_request = "ignore_request"
|
|
8
|
-
request_scheduled = "request_scheduled"
|
|
9
|
-
response_received = "request_received"
|
|
10
|
-
item_successful = "item_successful"
|
|
11
|
-
item_discard = "item_discard"
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding:UTF-8 -*-
|
|
3
|
+
|
|
4
|
+
spider_error = "spider_error"
|
|
5
|
+
spider_opened = "spider_open"
|
|
6
|
+
spider_closed = "spider_closed"
|
|
7
|
+
ignore_request = "ignore_request"
|
|
8
|
+
request_scheduled = "request_scheduled"
|
|
9
|
+
response_received = "request_received"
|
|
10
|
+
item_successful = "item_successful"
|
|
11
|
+
item_discard = "item_discard"
|
crawlo/exceptions.py
CHANGED
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
#!/usr/bin/python
|
|
2
|
-
# -*- coding:UTF-8 -*-
|
|
3
|
-
class TransformTypeError(TypeError):
|
|
4
|
-
pass
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class OutputError(Exception):
|
|
8
|
-
pass
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class SpiderTypeError(TypeError):
|
|
12
|
-
pass
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class ItemInitError(Exception):
|
|
16
|
-
pass
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ItemAttributeError(Exception):
|
|
20
|
-
pass
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class DecodeError(Exception):
|
|
24
|
-
pass
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class MiddlewareInitError(Exception):
|
|
28
|
-
pass
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class PipelineInitError(Exception):
|
|
32
|
-
pass
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class InvalidOutputError(Exception):
|
|
36
|
-
pass
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class RequestMethodError(Exception):
|
|
40
|
-
pass
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class IgnoreRequestError(Exception):
|
|
44
|
-
def __init__(self, msg):
|
|
45
|
-
self.msg = msg
|
|
46
|
-
super(IgnoreRequestError, self).__init__(msg)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class ItemDiscard(Exception):
|
|
50
|
-
def __init__(self, msg):
|
|
51
|
-
self.msg = msg
|
|
52
|
-
super(ItemDiscard, self).__init__(msg)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
class NotConfigured(Exception):
|
|
56
|
-
pass
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class NotConfiguredError(Exception):
|
|
60
|
-
pass
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class ExtensionInitError(Exception):
|
|
64
|
-
pass
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class ReceiverTypeError(Exception):
|
|
68
|
-
pass
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
class SpiderCreationError(Exception):
|
|
72
|
-
"""爬虫实例化失败异常"""
|
|
73
|
-
pass
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
class ItemValidationError(Exception):
|
|
77
|
-
"""Item 字段验证错误"""
|
|
78
|
-
pass
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
class DropItem(Exception):
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding:UTF-8 -*-
|
|
3
|
+
class TransformTypeError(TypeError):
|
|
4
|
+
pass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OutputError(Exception):
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SpiderTypeError(TypeError):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ItemInitError(Exception):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ItemAttributeError(Exception):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DecodeError(Exception):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MiddlewareInitError(Exception):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PipelineInitError(Exception):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class InvalidOutputError(Exception):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RequestMethodError(Exception):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class IgnoreRequestError(Exception):
|
|
44
|
+
def __init__(self, msg):
|
|
45
|
+
self.msg = msg
|
|
46
|
+
super(IgnoreRequestError, self).__init__(msg)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ItemDiscard(Exception):
|
|
50
|
+
def __init__(self, msg):
|
|
51
|
+
self.msg = msg
|
|
52
|
+
super(ItemDiscard, self).__init__(msg)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class NotConfigured(Exception):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class NotConfiguredError(Exception):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ExtensionInitError(Exception):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ReceiverTypeError(Exception):
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class SpiderCreationError(Exception):
|
|
72
|
+
"""爬虫实例化失败异常"""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ItemValidationError(Exception):
|
|
77
|
+
"""Item 字段验证错误"""
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class DropItem(Exception):
|
|
82
82
|
pass
|
crawlo/extension/__init__.py
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
#!/usr/bin/python
|
|
2
|
-
# -*- coding:UTF-8 -*-
|
|
3
|
-
from typing import List, Any
|
|
4
|
-
from pprint import pformat
|
|
5
|
-
|
|
6
|
-
from crawlo.utils.log import get_logger
|
|
7
|
-
from crawlo.utils.class_loader import load_class
|
|
8
|
-
from crawlo.exceptions import ExtensionInitError
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class ExtensionManager(object):
|
|
12
|
-
|
|
13
|
-
def __init__(self, crawler: Any):
|
|
14
|
-
self.crawler = crawler
|
|
15
|
-
self.extensions: List = []
|
|
16
|
-
extensions = self.crawler.settings.get_list('EXTENSIONS')
|
|
17
|
-
self.logger = get_logger(self.__class__.__name__, crawler.settings.get('LOG_LEVEL'))
|
|
18
|
-
self._add_extensions(extensions)
|
|
19
|
-
self._subscribe_extensions()
|
|
20
|
-
|
|
21
|
-
@classmethod
|
|
22
|
-
def create_instance(cls, *args: Any, **kwargs: Any) -> 'ExtensionManager':
|
|
23
|
-
return cls(*args, **kwargs)
|
|
24
|
-
|
|
25
|
-
def _add_extensions(self, extensions: List[str]) -> None:
|
|
26
|
-
for extension_path in extensions:
|
|
27
|
-
try:
|
|
28
|
-
extension_cls = load_class(extension_path)
|
|
29
|
-
if not hasattr(extension_cls, 'create_instance'):
|
|
30
|
-
raise ExtensionInitError(
|
|
31
|
-
f"Extension '{extension_path}' init failed: Must have method 'create_instance()'"
|
|
32
|
-
)
|
|
33
|
-
self.extensions.append(extension_cls.create_instance(self.crawler))
|
|
34
|
-
except Exception as e:
|
|
35
|
-
self.logger.error(f"Failed to load extension '{extension_path}': {e}")
|
|
36
|
-
raise ExtensionInitError(f"Failed to load extension '{extension_path}': {e}")
|
|
37
|
-
|
|
38
|
-
if extensions:
|
|
39
|
-
# 恢复INFO级别日志,保留关键的启用信息
|
|
40
|
-
self.logger.info(f"Enabled extensions: \n{pformat(extensions)}")
|
|
41
|
-
|
|
42
|
-
def _subscribe_extensions(self) -> None:
|
|
43
|
-
"""订阅扩展方法到相应的事件"""
|
|
44
|
-
for extension in self.extensions:
|
|
45
|
-
# 订阅 spider_closed 方法
|
|
46
|
-
if hasattr(extension, 'spider_closed'):
|
|
47
|
-
self.crawler.subscriber.subscribe(extension.spider_closed, event="spider_closed")
|
|
48
|
-
|
|
49
|
-
# 订阅 item_successful 方法
|
|
50
|
-
if hasattr(extension, 'item_successful'):
|
|
51
|
-
self.crawler.subscriber.subscribe(extension.item_successful, event="item_successful")
|
|
52
|
-
|
|
53
|
-
# 订阅 item_discard 方法
|
|
54
|
-
if hasattr(extension, 'item_discard'):
|
|
55
|
-
self.crawler.subscriber.subscribe(extension.item_discard, event="item_discard")
|
|
56
|
-
|
|
57
|
-
# 订阅 response_received 方法
|
|
58
|
-
if hasattr(extension, 'response_received'):
|
|
59
|
-
# 修复:将事件名称从 "request_received" 更正为 "response_received"
|
|
60
|
-
self.crawler.subscriber.subscribe(extension.response_received, event="response_received")
|
|
61
|
-
|
|
62
|
-
# 订阅 request_scheduled 方法
|
|
63
|
-
if hasattr(extension, 'request_scheduled'):
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding:UTF-8 -*-
|
|
3
|
+
from typing import List, Any
|
|
4
|
+
from pprint import pformat
|
|
5
|
+
|
|
6
|
+
from crawlo.utils.log import get_logger
|
|
7
|
+
from crawlo.utils.class_loader import load_class
|
|
8
|
+
from crawlo.exceptions import ExtensionInitError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ExtensionManager(object):
|
|
12
|
+
|
|
13
|
+
def __init__(self, crawler: Any):
|
|
14
|
+
self.crawler = crawler
|
|
15
|
+
self.extensions: List = []
|
|
16
|
+
extensions = self.crawler.settings.get_list('EXTENSIONS')
|
|
17
|
+
self.logger = get_logger(self.__class__.__name__, crawler.settings.get('LOG_LEVEL'))
|
|
18
|
+
self._add_extensions(extensions)
|
|
19
|
+
self._subscribe_extensions()
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def create_instance(cls, *args: Any, **kwargs: Any) -> 'ExtensionManager':
|
|
23
|
+
return cls(*args, **kwargs)
|
|
24
|
+
|
|
25
|
+
def _add_extensions(self, extensions: List[str]) -> None:
|
|
26
|
+
for extension_path in extensions:
|
|
27
|
+
try:
|
|
28
|
+
extension_cls = load_class(extension_path)
|
|
29
|
+
if not hasattr(extension_cls, 'create_instance'):
|
|
30
|
+
raise ExtensionInitError(
|
|
31
|
+
f"Extension '{extension_path}' init failed: Must have method 'create_instance()'"
|
|
32
|
+
)
|
|
33
|
+
self.extensions.append(extension_cls.create_instance(self.crawler))
|
|
34
|
+
except Exception as e:
|
|
35
|
+
self.logger.error(f"Failed to load extension '{extension_path}': {e}")
|
|
36
|
+
raise ExtensionInitError(f"Failed to load extension '{extension_path}': {e}")
|
|
37
|
+
|
|
38
|
+
if extensions:
|
|
39
|
+
# 恢复INFO级别日志,保留关键的启用信息
|
|
40
|
+
self.logger.info(f"Enabled extensions: \n{pformat(extensions)}")
|
|
41
|
+
|
|
42
|
+
def _subscribe_extensions(self) -> None:
|
|
43
|
+
"""订阅扩展方法到相应的事件"""
|
|
44
|
+
for extension in self.extensions:
|
|
45
|
+
# 订阅 spider_closed 方法
|
|
46
|
+
if hasattr(extension, 'spider_closed'):
|
|
47
|
+
self.crawler.subscriber.subscribe(extension.spider_closed, event="spider_closed")
|
|
48
|
+
|
|
49
|
+
# 订阅 item_successful 方法
|
|
50
|
+
if hasattr(extension, 'item_successful'):
|
|
51
|
+
self.crawler.subscriber.subscribe(extension.item_successful, event="item_successful")
|
|
52
|
+
|
|
53
|
+
# 订阅 item_discard 方法
|
|
54
|
+
if hasattr(extension, 'item_discard'):
|
|
55
|
+
self.crawler.subscriber.subscribe(extension.item_discard, event="item_discard")
|
|
56
|
+
|
|
57
|
+
# 订阅 response_received 方法
|
|
58
|
+
if hasattr(extension, 'response_received'):
|
|
59
|
+
# 修复:将事件名称从 "request_received" 更正为 "response_received"
|
|
60
|
+
self.crawler.subscriber.subscribe(extension.response_received, event="response_received")
|
|
61
|
+
|
|
62
|
+
# 订阅 request_scheduled 方法
|
|
63
|
+
if hasattr(extension, 'request_scheduled'):
|
|
64
64
|
self.crawler.subscriber.subscribe(extension.request_scheduled, event="request_scheduled")
|
crawlo/extension/health_check.py
CHANGED
|
@@ -1,142 +1,142 @@
|
|
|
1
|
-
#!/usr/bin/python
|
|
2
|
-
# -*- coding:UTF-8 -*-
|
|
3
|
-
import asyncio
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
from typing import Any, Optional, Dict
|
|
6
|
-
|
|
7
|
-
from crawlo.event import spider_opened, spider_closed, response_received, request_scheduled
|
|
8
|
-
from crawlo.utils.log import get_logger
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class HealthCheckExtension:
|
|
12
|
-
"""
|
|
13
|
-
健康检查扩展
|
|
14
|
-
监控爬虫的健康状态,包括响应时间、错误率等指标
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
def __init__(self, crawler: Any):
|
|
18
|
-
self.settings = crawler.settings
|
|
19
|
-
self.logger = get_logger(self.__class__.__name__, crawler.settings.get('LOG_LEVEL'))
|
|
20
|
-
|
|
21
|
-
# 获取配置参数
|
|
22
|
-
self.enabled = self.settings.get_bool('HEALTH_CHECK_ENABLED', True)
|
|
23
|
-
self.check_interval = self.settings.get_int('HEALTH_CHECK_INTERVAL', 60) # 默认60秒
|
|
24
|
-
|
|
25
|
-
# 健康状态统计
|
|
26
|
-
self.stats: Dict[str, Any] = {
|
|
27
|
-
'start_time': None,
|
|
28
|
-
'total_requests': 0,
|
|
29
|
-
'total_responses': 0,
|
|
30
|
-
'error_responses': 0,
|
|
31
|
-
'last_check_time': None,
|
|
32
|
-
'response_times': [], # 存储最近的响应时间
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
self.task: Optional[asyncio.Task] = None
|
|
36
|
-
|
|
37
|
-
@classmethod
|
|
38
|
-
def create_instance(cls, crawler: Any) -> 'HealthCheckExtension':
|
|
39
|
-
# 只有当配置启用时才创建实例
|
|
40
|
-
if not crawler.settings.get_bool('HEALTH_CHECK_ENABLED', True):
|
|
41
|
-
from crawlo.exceptions import NotConfigured
|
|
42
|
-
raise NotConfigured("HealthCheckExtension: HEALTH_CHECK_ENABLED is False")
|
|
43
|
-
|
|
44
|
-
o = cls(crawler)
|
|
45
|
-
if o.enabled:
|
|
46
|
-
crawler.subscriber.subscribe(o.spider_opened, event=spider_opened)
|
|
47
|
-
crawler.subscriber.subscribe(o.spider_closed, event=spider_closed)
|
|
48
|
-
crawler.subscriber.subscribe(o.response_received, event=response_received)
|
|
49
|
-
crawler.subscriber.subscribe(o.request_scheduled, event=request_scheduled)
|
|
50
|
-
return o
|
|
51
|
-
|
|
52
|
-
async def spider_opened(self) -> None:
|
|
53
|
-
"""爬虫启动时初始化健康检查"""
|
|
54
|
-
if not self.enabled:
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
self.stats['start_time'] = datetime.now()
|
|
58
|
-
self.task = asyncio.create_task(self._health_check_loop())
|
|
59
|
-
self.logger.info("Health check extension started.")
|
|
60
|
-
|
|
61
|
-
async def spider_closed(self) -> None:
|
|
62
|
-
"""爬虫关闭时停止健康检查"""
|
|
63
|
-
if not self.enabled:
|
|
64
|
-
return
|
|
65
|
-
|
|
66
|
-
if self.task:
|
|
67
|
-
self.task.cancel()
|
|
68
|
-
try:
|
|
69
|
-
await self.task
|
|
70
|
-
except asyncio.CancelledError:
|
|
71
|
-
pass
|
|
72
|
-
|
|
73
|
-
# 输出最终健康状态
|
|
74
|
-
await self._check_health()
|
|
75
|
-
self.logger.info("Health check extension stopped.")
|
|
76
|
-
|
|
77
|
-
async def request_scheduled(self, request: Any, spider: Any) -> None:
|
|
78
|
-
"""记录调度的请求"""
|
|
79
|
-
if not self.enabled:
|
|
80
|
-
return
|
|
81
|
-
self.stats['total_requests'] += 1
|
|
82
|
-
|
|
83
|
-
async def response_received(self, response: Any, spider: Any) -> None:
|
|
84
|
-
"""记录接收到的响应"""
|
|
85
|
-
if not self.enabled:
|
|
86
|
-
return
|
|
87
|
-
|
|
88
|
-
self.stats['total_responses'] += 1
|
|
89
|
-
|
|
90
|
-
# 记录错误响应
|
|
91
|
-
if hasattr(response, 'status_code') and response.status_code >= 400:
|
|
92
|
-
self.stats['error_responses'] += 1
|
|
93
|
-
|
|
94
|
-
async def _health_check_loop(self) -> None:
|
|
95
|
-
"""健康检查循环"""
|
|
96
|
-
while True:
|
|
97
|
-
try:
|
|
98
|
-
await asyncio.sleep(self.check_interval)
|
|
99
|
-
await self._check_health()
|
|
100
|
-
except asyncio.CancelledError:
|
|
101
|
-
break
|
|
102
|
-
except Exception as e:
|
|
103
|
-
self.logger.error(f"Error in health check loop: {e}")
|
|
104
|
-
|
|
105
|
-
async def _check_health(self) -> None:
|
|
106
|
-
"""执行健康检查并输出报告"""
|
|
107
|
-
try:
|
|
108
|
-
now_time = datetime.now()
|
|
109
|
-
self.stats['last_check_time'] = now_time
|
|
110
|
-
|
|
111
|
-
# 计算基本统计信息
|
|
112
|
-
runtime = (now_time - self.stats['start_time']).total_seconds() if self.stats['start_time'] else 0
|
|
113
|
-
requests_per_second = self.stats['total_requests'] / runtime if runtime > 0 else 0
|
|
114
|
-
responses_per_second = self.stats['total_responses'] / runtime if runtime > 0 else 0
|
|
115
|
-
|
|
116
|
-
# 计算错误率
|
|
117
|
-
error_rate = (
|
|
118
|
-
self.stats['error_responses'] / self.stats['total_responses']
|
|
119
|
-
if self.stats['total_responses'] > 0 else 0
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
# 输出健康报告
|
|
123
|
-
health_report = {
|
|
124
|
-
'runtime_seconds': round(runtime, 2),
|
|
125
|
-
'total_requests': self.stats['total_requests'],
|
|
126
|
-
'total_responses': self.stats['total_responses'],
|
|
127
|
-
'requests_per_second': round(requests_per_second, 2),
|
|
128
|
-
'responses_per_second': round(responses_per_second, 2),
|
|
129
|
-
'error_responses': self.stats['error_responses'],
|
|
130
|
-
'error_rate': f"{error_rate:.2%}",
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
# 根据错误率判断健康状态
|
|
134
|
-
if error_rate > 0.1: # 错误率超过10%
|
|
135
|
-
self.logger.warning(f"Health check report: {health_report}")
|
|
136
|
-
elif error_rate > 0.05: # 错误率超过5%
|
|
137
|
-
self.logger.info(f"Health check report: {health_report}")
|
|
138
|
-
else:
|
|
139
|
-
self.logger.debug(f"Health check report: {health_report}")
|
|
140
|
-
|
|
141
|
-
except Exception as e:
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding:UTF-8 -*-
|
|
3
|
+
import asyncio
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any, Optional, Dict
|
|
6
|
+
|
|
7
|
+
from crawlo.event import spider_opened, spider_closed, response_received, request_scheduled
|
|
8
|
+
from crawlo.utils.log import get_logger
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HealthCheckExtension:
|
|
12
|
+
"""
|
|
13
|
+
健康检查扩展
|
|
14
|
+
监控爬虫的健康状态,包括响应时间、错误率等指标
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, crawler: Any):
|
|
18
|
+
self.settings = crawler.settings
|
|
19
|
+
self.logger = get_logger(self.__class__.__name__, crawler.settings.get('LOG_LEVEL'))
|
|
20
|
+
|
|
21
|
+
# 获取配置参数
|
|
22
|
+
self.enabled = self.settings.get_bool('HEALTH_CHECK_ENABLED', True)
|
|
23
|
+
self.check_interval = self.settings.get_int('HEALTH_CHECK_INTERVAL', 60) # 默认60秒
|
|
24
|
+
|
|
25
|
+
# 健康状态统计
|
|
26
|
+
self.stats: Dict[str, Any] = {
|
|
27
|
+
'start_time': None,
|
|
28
|
+
'total_requests': 0,
|
|
29
|
+
'total_responses': 0,
|
|
30
|
+
'error_responses': 0,
|
|
31
|
+
'last_check_time': None,
|
|
32
|
+
'response_times': [], # 存储最近的响应时间
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
self.task: Optional[asyncio.Task] = None
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def create_instance(cls, crawler: Any) -> 'HealthCheckExtension':
|
|
39
|
+
# 只有当配置启用时才创建实例
|
|
40
|
+
if not crawler.settings.get_bool('HEALTH_CHECK_ENABLED', True):
|
|
41
|
+
from crawlo.exceptions import NotConfigured
|
|
42
|
+
raise NotConfigured("HealthCheckExtension: HEALTH_CHECK_ENABLED is False")
|
|
43
|
+
|
|
44
|
+
o = cls(crawler)
|
|
45
|
+
if o.enabled:
|
|
46
|
+
crawler.subscriber.subscribe(o.spider_opened, event=spider_opened)
|
|
47
|
+
crawler.subscriber.subscribe(o.spider_closed, event=spider_closed)
|
|
48
|
+
crawler.subscriber.subscribe(o.response_received, event=response_received)
|
|
49
|
+
crawler.subscriber.subscribe(o.request_scheduled, event=request_scheduled)
|
|
50
|
+
return o
|
|
51
|
+
|
|
52
|
+
async def spider_opened(self) -> None:
|
|
53
|
+
"""爬虫启动时初始化健康检查"""
|
|
54
|
+
if not self.enabled:
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
self.stats['start_time'] = datetime.now()
|
|
58
|
+
self.task = asyncio.create_task(self._health_check_loop())
|
|
59
|
+
self.logger.info("Health check extension started.")
|
|
60
|
+
|
|
61
|
+
async def spider_closed(self) -> None:
|
|
62
|
+
"""爬虫关闭时停止健康检查"""
|
|
63
|
+
if not self.enabled:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
if self.task:
|
|
67
|
+
self.task.cancel()
|
|
68
|
+
try:
|
|
69
|
+
await self.task
|
|
70
|
+
except asyncio.CancelledError:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
# 输出最终健康状态
|
|
74
|
+
await self._check_health()
|
|
75
|
+
self.logger.info("Health check extension stopped.")
|
|
76
|
+
|
|
77
|
+
async def request_scheduled(self, request: Any, spider: Any) -> None:
|
|
78
|
+
"""记录调度的请求"""
|
|
79
|
+
if not self.enabled:
|
|
80
|
+
return
|
|
81
|
+
self.stats['total_requests'] += 1
|
|
82
|
+
|
|
83
|
+
async def response_received(self, response: Any, spider: Any) -> None:
|
|
84
|
+
"""记录接收到的响应"""
|
|
85
|
+
if not self.enabled:
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
self.stats['total_responses'] += 1
|
|
89
|
+
|
|
90
|
+
# 记录错误响应
|
|
91
|
+
if hasattr(response, 'status_code') and response.status_code >= 400:
|
|
92
|
+
self.stats['error_responses'] += 1
|
|
93
|
+
|
|
94
|
+
async def _health_check_loop(self) -> None:
|
|
95
|
+
"""健康检查循环"""
|
|
96
|
+
while True:
|
|
97
|
+
try:
|
|
98
|
+
await asyncio.sleep(self.check_interval)
|
|
99
|
+
await self._check_health()
|
|
100
|
+
except asyncio.CancelledError:
|
|
101
|
+
break
|
|
102
|
+
except Exception as e:
|
|
103
|
+
self.logger.error(f"Error in health check loop: {e}")
|
|
104
|
+
|
|
105
|
+
async def _check_health(self) -> None:
|
|
106
|
+
"""执行健康检查并输出报告"""
|
|
107
|
+
try:
|
|
108
|
+
now_time = datetime.now()
|
|
109
|
+
self.stats['last_check_time'] = now_time
|
|
110
|
+
|
|
111
|
+
# 计算基本统计信息
|
|
112
|
+
runtime = (now_time - self.stats['start_time']).total_seconds() if self.stats['start_time'] else 0
|
|
113
|
+
requests_per_second = self.stats['total_requests'] / runtime if runtime > 0 else 0
|
|
114
|
+
responses_per_second = self.stats['total_responses'] / runtime if runtime > 0 else 0
|
|
115
|
+
|
|
116
|
+
# 计算错误率
|
|
117
|
+
error_rate = (
|
|
118
|
+
self.stats['error_responses'] / self.stats['total_responses']
|
|
119
|
+
if self.stats['total_responses'] > 0 else 0
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# 输出健康报告
|
|
123
|
+
health_report = {
|
|
124
|
+
'runtime_seconds': round(runtime, 2),
|
|
125
|
+
'total_requests': self.stats['total_requests'],
|
|
126
|
+
'total_responses': self.stats['total_responses'],
|
|
127
|
+
'requests_per_second': round(requests_per_second, 2),
|
|
128
|
+
'responses_per_second': round(responses_per_second, 2),
|
|
129
|
+
'error_responses': self.stats['error_responses'],
|
|
130
|
+
'error_rate': f"{error_rate:.2%}",
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# 根据错误率判断健康状态
|
|
134
|
+
if error_rate > 0.1: # 错误率超过10%
|
|
135
|
+
self.logger.warning(f"Health check report: {health_report}")
|
|
136
|
+
elif error_rate > 0.05: # 错误率超过5%
|
|
137
|
+
self.logger.info(f"Health check report: {health_report}")
|
|
138
|
+
else:
|
|
139
|
+
self.logger.debug(f"Health check report: {health_report}")
|
|
140
|
+
|
|
141
|
+
except Exception as e:
|
|
142
142
|
self.logger.error(f"Error in health check: {e}")
|