crawlo 1.4.5__py3-none-any.whl → 1.4.7__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 +90 -89
- 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 +186 -186
- crawlo/commands/help.py +140 -138
- crawlo/commands/list.py +155 -155
- crawlo/commands/run.py +379 -341
- crawlo/commands/startproject.py +460 -460
- crawlo/commands/stats.py +187 -187
- crawlo/commands/utils.py +196 -196
- crawlo/config.py +320 -312
- crawlo/config_validator.py +277 -277
- crawlo/core/__init__.py +52 -52
- crawlo/core/engine.py +451 -438
- crawlo/core/processor.py +47 -47
- crawlo/core/scheduler.py +290 -291
- crawlo/crawler.py +698 -657
- crawlo/data/__init__.py +5 -5
- crawlo/data/user_agents.py +194 -194
- crawlo/downloader/__init__.py +280 -276
- crawlo/downloader/aiohttp_downloader.py +233 -233
- crawlo/downloader/cffi_downloader.py +250 -245
- crawlo/downloader/httpx_downloader.py +265 -259
- crawlo/downloader/hybrid_downloader.py +212 -212
- crawlo/downloader/playwright_downloader.py +425 -402
- crawlo/downloader/selenium_downloader.py +486 -472
- crawlo/event.py +45 -11
- crawlo/exceptions.py +215 -82
- crawlo/extension/__init__.py +65 -64
- 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 +53 -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 +104 -103
- crawlo/factories/registry.py +84 -84
- crawlo/factories/utils.py +135 -0
- crawlo/filters/__init__.py +170 -153
- crawlo/filters/aioredis_filter.py +348 -264
- crawlo/filters/memory_filter.py +261 -276
- crawlo/framework.py +306 -292
- crawlo/initialization/__init__.py +44 -44
- crawlo/initialization/built_in.py +391 -434
- crawlo/initialization/context.py +141 -141
- crawlo/initialization/core.py +240 -194
- crawlo/initialization/phases.py +230 -149
- crawlo/initialization/registry.py +143 -145
- crawlo/initialization/utils.py +49 -0
- crawlo/interfaces.py +23 -23
- 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 +42 -46
- crawlo/logging/config.py +277 -197
- crawlo/logging/factory.py +175 -171
- crawlo/logging/manager.py +104 -112
- crawlo/middleware/__init__.py +87 -24
- crawlo/middleware/default_header.py +132 -132
- crawlo/middleware/download_delay.py +104 -104
- crawlo/middleware/middleware_manager.py +142 -142
- crawlo/middleware/offsite.py +123 -123
- crawlo/middleware/proxy.py +209 -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/mode_manager.py +287 -253
- crawlo/network/__init__.py +21 -21
- crawlo/network/request.py +375 -379
- crawlo/network/response.py +569 -664
- crawlo/pipelines/__init__.py +53 -22
- crawlo/pipelines/base_pipeline.py +452 -0
- 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 +140 -132
- crawlo/pipelines/mysql_pipeline.py +470 -326
- crawlo/pipelines/pipeline_manager.py +100 -100
- crawlo/pipelines/redis_dedup_pipeline.py +155 -156
- crawlo/project.py +347 -347
- crawlo/queue/__init__.py +10 -0
- crawlo/queue/pqueue.py +38 -38
- crawlo/queue/queue_manager.py +591 -525
- crawlo/queue/redis_priority_queue.py +519 -370
- crawlo/settings/__init__.py +7 -7
- crawlo/settings/default_settings.py +285 -270
- crawlo/settings/setting_manager.py +219 -219
- crawlo/spider/__init__.py +657 -657
- crawlo/stats_collector.py +82 -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 +2 -4
- crawlo/templates/project/items.py.tmpl +13 -17
- crawlo/templates/project/middlewares.py.tmpl +38 -38
- crawlo/templates/project/pipelines.py.tmpl +35 -36
- crawlo/templates/project/settings.py.tmpl +110 -157
- crawlo/templates/project/settings_distributed.py.tmpl +156 -161
- crawlo/templates/project/settings_gentle.py.tmpl +170 -171
- crawlo/templates/project/settings_high_performance.py.tmpl +171 -172
- crawlo/templates/project/settings_minimal.py.tmpl +99 -77
- crawlo/templates/project/settings_simple.py.tmpl +168 -169
- crawlo/templates/project/spiders/__init__.py.tmpl +9 -9
- crawlo/templates/run.py.tmpl +23 -30
- crawlo/templates/spider/spider.py.tmpl +33 -144
- crawlo/templates/spiders_init.py.tmpl +5 -10
- crawlo/tools/__init__.py +86 -189
- crawlo/tools/date_tools.py +289 -289
- crawlo/tools/distributed_coordinator.py +384 -384
- crawlo/tools/scenario_adapter.py +262 -262
- crawlo/tools/text_cleaner.py +232 -232
- crawlo/utils/__init__.py +50 -50
- crawlo/utils/batch_processor.py +276 -259
- crawlo/utils/config_manager.py +442 -0
- crawlo/utils/controlled_spider_mixin.py +439 -439
- crawlo/utils/db_helper.py +250 -244
- crawlo/utils/error_handler.py +410 -410
- crawlo/utils/fingerprint.py +121 -121
- crawlo/utils/func_tools.py +82 -82
- crawlo/utils/large_scale_helper.py +344 -344
- crawlo/utils/leak_detector.py +335 -0
- crawlo/utils/log.py +79 -79
- crawlo/utils/misc.py +81 -81
- crawlo/utils/mongo_connection_pool.py +157 -0
- crawlo/utils/mysql_connection_pool.py +197 -0
- crawlo/utils/performance_monitor.py +285 -285
- crawlo/utils/queue_helper.py +175 -175
- crawlo/utils/redis_checker.py +91 -0
- crawlo/utils/redis_connection_pool.py +578 -388
- crawlo/utils/redis_key_validator.py +198 -198
- crawlo/utils/request.py +278 -256
- crawlo/utils/request_serializer.py +225 -225
- crawlo/utils/resource_manager.py +337 -0
- crawlo/utils/selector_helper.py +137 -137
- crawlo/utils/singleton.py +70 -0
- crawlo/utils/spider_loader.py +201 -201
- crawlo/utils/text_helper.py +94 -94
- crawlo/utils/{url.py → url_utils.py} +39 -39
- crawlo-1.4.7.dist-info/METADATA +689 -0
- crawlo-1.4.7.dist-info/RECORD +347 -0
- examples/__init__.py +7 -7
- tests/__init__.py +7 -7
- tests/advanced_tools_example.py +217 -275
- tests/authenticated_proxy_example.py +110 -106
- tests/baidu_performance_test.py +108 -108
- tests/baidu_test.py +59 -59
- tests/bug_check_test.py +250 -250
- 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/direct_selector_helper_test.py +96 -96
- tests/distributed_dedup_test.py +467 -0
- 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/error_handling_example.py +171 -171
- tests/explain_mysql_update_behavior.py +77 -0
- 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/monitor_redis_dedup.sh +72 -0
- tests/ofweek_scrapy/ofweek_scrapy/items.py +12 -12
- tests/ofweek_scrapy/ofweek_scrapy/middlewares.py +100 -100
- tests/ofweek_scrapy/ofweek_scrapy/pipelines.py +13 -13
- tests/ofweek_scrapy/ofweek_scrapy/settings.py +84 -84
- tests/ofweek_scrapy/scrapy.cfg +11 -11
- tests/optimized_performance_test.py +211 -211
- tests/performance_comparison.py +244 -244
- 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_cli_test.py +55 -0
- tests/simple_command_test.py +119 -119
- tests/simple_crawlo_test.py +126 -126
- tests/simple_follow_test.py +38 -38
- tests/simple_log_test2.py +137 -137
- tests/simple_optimization_test.py +128 -128
- tests/simple_queue_type_test.py +41 -41
- tests/simple_response_selector_test.py +94 -94
- tests/simple_selector_helper_test.py +154 -154
- tests/simple_selector_test.py +207 -207
- tests/simple_spider_test.py +49 -49
- tests/simple_url_test.py +73 -73
- tests/simulate_mysql_update_test.py +140 -0
- 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_asyncmy_usage.py +57 -0
- tests/test_batch_processor.py +178 -178
- tests/test_cleaners.py +54 -54
- tests/test_cli_arguments.py +119 -0
- tests/test_component_factory.py +174 -174
- 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_crawler_process_import.py +38 -38
- tests/test_crawler_process_spider_modules.py +47 -47
- tests/test_crawlo_proxy_integration.py +114 -108
- tests/test_date_tools.py +123 -123
- tests/test_dedup_fix.py +220 -220
- tests/test_dedup_pipeline_consistency.py +124 -124
- 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 +272 -268
- tests/test_edge_cases.py +305 -305
- tests/test_encoding_core.py +56 -56
- tests/test_encoding_detection.py +126 -126
- tests/test_enhanced_error_handler.py +270 -270
- tests/test_enhanced_error_handler_comprehensive.py +245 -245
- tests/test_error_handler_compatibility.py +112 -112
- tests/test_factories.py +252 -252
- tests/test_factory_compatibility.py +196 -196
- tests/test_final_validation.py +153 -153
- tests/test_fingerprint_consistency.py +135 -135
- tests/test_fingerprint_simple.py +51 -51
- 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_helper.py +235 -235
- tests/test_logging_enhancements.py +374 -374
- tests/test_logging_final.py +184 -184
- tests/test_logging_integration.py +312 -312
- tests/test_logging_system.py +282 -282
- tests/test_middleware_debug.py +141 -141
- tests/test_mode_consistency.py +51 -51
- tests/test_multi_directory.py +67 -67
- tests/test_multiple_spider_modules.py +80 -80
- tests/test_mysql_pipeline_config.py +165 -0
- tests/test_mysql_pipeline_error.py +99 -0
- tests/test_mysql_pipeline_init_log.py +83 -0
- tests/test_mysql_pipeline_integration.py +133 -0
- tests/test_mysql_pipeline_refactor.py +144 -0
- tests/test_mysql_pipeline_refactor_simple.py +86 -0
- tests/test_mysql_pipeline_robustness.py +196 -0
- tests/test_mysql_pipeline_types.py +89 -0
- tests/test_mysql_update_columns.py +94 -0
- tests/test_offsite_middleware.py +244 -244
- tests/test_offsite_middleware_simple.py +203 -203
- tests/test_optimized_selector_naming.py +100 -100
- 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 +211 -211
- tests/test_priority_consistency.py +151 -151
- tests/test_priority_consistency_fixed.py +249 -249
- tests/test_proxy_health_check.py +32 -32
- tests/test_proxy_middleware.py +217 -121
- tests/test_proxy_middleware_enhanced.py +212 -216
- tests/test_proxy_middleware_integration.py +142 -137
- tests/test_proxy_middleware_refactored.py +207 -184
- tests/test_proxy_only.py +84 -0
- tests/test_proxy_providers.py +56 -56
- tests/test_proxy_stats.py +19 -19
- tests/test_proxy_strategies.py +59 -59
- tests/test_proxy_with_downloader.py +153 -0
- 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 +130 -130
- tests/test_random_headers_default.py +322 -322
- tests/test_random_headers_necessity.py +308 -308
- tests/test_random_user_agent.py +72 -72
- 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 +129 -129
- 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_follow.py +104 -104
- tests/test_response_improvements.py +152 -152
- tests/test_response_selector_methods.py +92 -92
- tests/test_response_url_methods.py +70 -70
- tests/test_response_urljoin.py +86 -86
- tests/test_retry_middleware.py +333 -333
- tests/test_retry_middleware_realistic.py +273 -273
- tests/test_scheduler.py +252 -252
- tests/test_scheduler_config_update.py +133 -133
- tests/test_scrapy_style_encoding.py +112 -112
- tests/test_selector_helper.py +100 -100
- tests/test_selector_optimizations.py +146 -146
- tests/test_simple_response.py +61 -61
- tests/test_spider_loader.py +49 -49
- tests/test_spider_loader_comprehensive.py +69 -69
- tests/test_spider_modules.py +84 -84
- tests/test_spiders/test_spider.py +9 -9
- 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 +176 -176
- tests/test_user_agents.py +96 -96
- 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
- tests/verify_mysql_warnings.py +110 -0
- crawlo/logging/async_handler.py +0 -181
- crawlo/logging/monitor.py +0 -153
- crawlo/logging/sampler.py +0 -167
- crawlo/middleware/simple_proxy.py +0 -65
- crawlo/tools/authenticated_proxy.py +0 -241
- crawlo/tools/data_formatter.py +0 -226
- crawlo/tools/data_validator.py +0 -181
- crawlo/tools/encoding_converter.py +0 -127
- crawlo/tools/network_diagnostic.py +0 -365
- crawlo/tools/request_tools.py +0 -83
- crawlo/tools/retry_mechanism.py +0 -224
- crawlo/utils/env_config.py +0 -143
- crawlo/utils/large_scale_config.py +0 -287
- crawlo/utils/system.py +0 -11
- crawlo/utils/tools.py +0 -5
- crawlo-1.4.5.dist-info/METADATA +0 -329
- crawlo-1.4.5.dist-info/RECORD +0 -347
- tests/env_config_example.py +0 -134
- tests/ofweek_scrapy/ofweek_scrapy/spiders/ofweek_spider.py +0 -162
- tests/test_authenticated_proxy.py +0 -142
- tests/test_comprehensive.py +0 -147
- tests/test_dynamic_downloaders_proxy.py +0 -125
- tests/test_dynamic_proxy.py +0 -93
- tests/test_dynamic_proxy_config.py +0 -147
- tests/test_dynamic_proxy_real.py +0 -110
- tests/test_env_config.py +0 -122
- tests/test_framework_env_usage.py +0 -104
- tests/test_large_scale_config.py +0 -113
- tests/test_proxy_api.py +0 -265
- tests/test_real_scenario_proxy.py +0 -196
- tests/tools_example.py +0 -261
- {crawlo-1.4.5.dist-info → crawlo-1.4.7.dist-info}/WHEEL +0 -0
- {crawlo-1.4.5.dist-info → crawlo-1.4.7.dist-info}/entry_points.txt +0 -0
- {crawlo-1.4.5.dist-info → crawlo-1.4.7.dist-info}/top_level.txt +0 -0
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
|
|
8
|
-
from crawlo.
|
|
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__
|
|
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=
|
|
47
|
-
crawler.subscriber.subscribe(o.spider_closed, event=
|
|
48
|
-
crawler.subscriber.subscribe(o.response_received, event=
|
|
49
|
-
crawler.subscriber.subscribe(o.request_scheduled, event=
|
|
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 CrawlerEvent
|
|
8
|
+
from crawlo.logging 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__)
|
|
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=CrawlerEvent.SPIDER_OPENED)
|
|
47
|
+
crawler.subscriber.subscribe(o.spider_closed, event=CrawlerEvent.SPIDER_CLOSED)
|
|
48
|
+
crawler.subscriber.subscribe(o.response_received, event=CrawlerEvent.RESPONSE_RECEIVED)
|
|
49
|
+
crawler.subscriber.subscribe(o.request_scheduled, event=CrawlerEvent.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}")
|
crawlo/extension/log_interval.py
CHANGED
|
@@ -1,95 +1,95 @@
|
|
|
1
|
-
#!/usr/bin/python
|
|
2
|
-
# -*- coding:UTF-8 -*-
|
|
3
|
-
import asyncio
|
|
4
|
-
from typing import Any, Optional
|
|
5
|
-
|
|
6
|
-
from crawlo.
|
|
7
|
-
from crawlo.event import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class LogIntervalExtension
|
|
11
|
-
|
|
12
|
-
def __init__(self, crawler: Any):
|
|
13
|
-
self.task: Optional[asyncio.Task] = None
|
|
14
|
-
self.stats = crawler.stats
|
|
15
|
-
self.item_count = 0
|
|
16
|
-
self.response_count = 0
|
|
17
|
-
self.seconds = crawler.settings.get('INTERVAL', 60) # 默认60秒
|
|
18
|
-
|
|
19
|
-
# 修复时间单位计算逻辑
|
|
20
|
-
if self.seconds % 60 == 0:
|
|
21
|
-
self.interval = int(self.seconds / 60)
|
|
22
|
-
self.unit = 'min'
|
|
23
|
-
else:
|
|
24
|
-
self.interval = self.seconds
|
|
25
|
-
self.unit = 's'
|
|
26
|
-
|
|
27
|
-
# 处理单数情况
|
|
28
|
-
if self.interval == 1 and self.unit == 'min':
|
|
29
|
-
self.interval_display = ""
|
|
30
|
-
else:
|
|
31
|
-
self.interval_display = str(self.interval)
|
|
32
|
-
|
|
33
|
-
self.logger = get_logger(self.__class__.__name__
|
|
34
|
-
self.logger.info(f"LogIntervalExtension initialized with interval: {self.seconds} seconds")
|
|
35
|
-
|
|
36
|
-
@classmethod
|
|
37
|
-
def create_instance(cls, crawler: Any) -> 'LogIntervalExtension':
|
|
38
|
-
o = cls(crawler)
|
|
39
|
-
crawler.subscriber.subscribe(o.spider_opened, event=
|
|
40
|
-
crawler.subscriber.subscribe(o.spider_closed, event=
|
|
41
|
-
return o
|
|
42
|
-
|
|
43
|
-
async def spider_opened(self) -> None:
|
|
44
|
-
self.logger.info("Spider opened, starting interval logging task")
|
|
45
|
-
self.task = asyncio.create_task(self.interval_log())
|
|
46
|
-
self.logger.info("Interval logging task started")
|
|
47
|
-
|
|
48
|
-
async def spider_closed(self) -> None:
|
|
49
|
-
self.logger.info("Spider closed, stopping interval logging task")
|
|
50
|
-
if self.task:
|
|
51
|
-
self.task.cancel()
|
|
52
|
-
try:
|
|
53
|
-
await self.task
|
|
54
|
-
except asyncio.CancelledError:
|
|
55
|
-
pass
|
|
56
|
-
self.task = None
|
|
57
|
-
|
|
58
|
-
async def interval_log(self) -> None:
|
|
59
|
-
iteration = 0
|
|
60
|
-
while True:
|
|
61
|
-
try:
|
|
62
|
-
iteration += 1
|
|
63
|
-
self.logger.debug(f"Interval log iteration {iteration} starting")
|
|
64
|
-
last_item_count = self.stats.get_value('item_successful_count', default=0)
|
|
65
|
-
last_response_count = self.stats.get_value('response_received_count', default=0)
|
|
66
|
-
item_rate = last_item_count - self.item_count
|
|
67
|
-
response_rate = last_response_count - self.response_count
|
|
68
|
-
|
|
69
|
-
# 添加调试信息
|
|
70
|
-
self.logger.debug(f"Debug info - Iteration: {iteration}, Last item count: {last_item_count}, Last response count: {last_response_count}")
|
|
71
|
-
self.logger.debug(f"Debug info - Previous item count: {self.item_count}, Previous response count: {self.response_count}")
|
|
72
|
-
self.logger.debug(f"Debug info - Item rate: {item_rate}, Response rate: {response_rate}")
|
|
73
|
-
|
|
74
|
-
self.item_count, self.response_count = last_item_count, last_response_count
|
|
75
|
-
|
|
76
|
-
# 修复效率计算,确保使用正确的单位
|
|
77
|
-
if self.unit == 'min' and self.seconds > 0:
|
|
78
|
-
# 转换为每分钟速率
|
|
79
|
-
pages_per_min = response_rate * 60 / self.seconds if self.seconds > 0 else 0
|
|
80
|
-
items_per_min = item_rate * 60 / self.seconds if self.seconds > 0 else 0
|
|
81
|
-
self.logger.info(
|
|
82
|
-
f'Crawled {last_response_count} pages (at {pages_per_min:.0f} pages/min),'
|
|
83
|
-
f' Got {last_item_count} items (at {items_per_min:.0f} items/min).'
|
|
84
|
-
)
|
|
85
|
-
else:
|
|
86
|
-
# 使用原始单位
|
|
87
|
-
self.logger.info(
|
|
88
|
-
f'Crawled {last_response_count} pages (at {response_rate} pages/{self.interval_display}{self.unit}),'
|
|
89
|
-
f' Got {last_item_count} items (at {item_rate} items/{self.interval_display}{self.unit}).'
|
|
90
|
-
)
|
|
91
|
-
self.logger.debug(f"Interval log iteration {iteration} completed, sleeping for {self.seconds} seconds")
|
|
92
|
-
await asyncio.sleep(self.seconds)
|
|
93
|
-
except Exception as e:
|
|
94
|
-
self.logger.error(f"Error in interval logging: {e}")
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding:UTF-8 -*-
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
from crawlo.logging import get_logger
|
|
7
|
+
from crawlo.event import CrawlerEvent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LogIntervalExtension:
|
|
11
|
+
|
|
12
|
+
def __init__(self, crawler: Any):
|
|
13
|
+
self.task: Optional[asyncio.Task] = None
|
|
14
|
+
self.stats = crawler.stats
|
|
15
|
+
self.item_count = 0
|
|
16
|
+
self.response_count = 0
|
|
17
|
+
self.seconds = crawler.settings.get('INTERVAL', 60) # 默认60秒
|
|
18
|
+
|
|
19
|
+
# 修复时间单位计算逻辑
|
|
20
|
+
if self.seconds % 60 == 0:
|
|
21
|
+
self.interval = int(self.seconds / 60)
|
|
22
|
+
self.unit = 'min'
|
|
23
|
+
else:
|
|
24
|
+
self.interval = self.seconds
|
|
25
|
+
self.unit = 's'
|
|
26
|
+
|
|
27
|
+
# 处理单数情况
|
|
28
|
+
if self.interval == 1 and self.unit == 'min':
|
|
29
|
+
self.interval_display = ""
|
|
30
|
+
else:
|
|
31
|
+
self.interval_display = str(self.interval)
|
|
32
|
+
|
|
33
|
+
self.logger = get_logger(self.__class__.__name__)
|
|
34
|
+
self.logger.info(f"LogIntervalExtension initialized with interval: {self.seconds} seconds")
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def create_instance(cls, crawler: Any) -> 'LogIntervalExtension':
|
|
38
|
+
o = cls(crawler)
|
|
39
|
+
crawler.subscriber.subscribe(o.spider_opened, event=CrawlerEvent.SPIDER_OPENED)
|
|
40
|
+
crawler.subscriber.subscribe(o.spider_closed, event=CrawlerEvent.SPIDER_CLOSED)
|
|
41
|
+
return o
|
|
42
|
+
|
|
43
|
+
async def spider_opened(self) -> None:
|
|
44
|
+
self.logger.info("Spider opened, starting interval logging task")
|
|
45
|
+
self.task = asyncio.create_task(self.interval_log())
|
|
46
|
+
self.logger.info("Interval logging task started")
|
|
47
|
+
|
|
48
|
+
async def spider_closed(self) -> None:
|
|
49
|
+
self.logger.info("Spider closed, stopping interval logging task")
|
|
50
|
+
if self.task:
|
|
51
|
+
self.task.cancel()
|
|
52
|
+
try:
|
|
53
|
+
await self.task
|
|
54
|
+
except asyncio.CancelledError:
|
|
55
|
+
pass
|
|
56
|
+
self.task = None
|
|
57
|
+
|
|
58
|
+
async def interval_log(self) -> None:
|
|
59
|
+
iteration = 0
|
|
60
|
+
while True:
|
|
61
|
+
try:
|
|
62
|
+
iteration += 1
|
|
63
|
+
self.logger.debug(f"Interval log iteration {iteration} starting")
|
|
64
|
+
last_item_count = self.stats.get_value('item_successful_count', default=0)
|
|
65
|
+
last_response_count = self.stats.get_value('response_received_count', default=0)
|
|
66
|
+
item_rate = last_item_count - self.item_count
|
|
67
|
+
response_rate = last_response_count - self.response_count
|
|
68
|
+
|
|
69
|
+
# 添加调试信息
|
|
70
|
+
self.logger.debug(f"Debug info - Iteration: {iteration}, Last item count: {last_item_count}, Last response count: {last_response_count}")
|
|
71
|
+
self.logger.debug(f"Debug info - Previous item count: {self.item_count}, Previous response count: {self.response_count}")
|
|
72
|
+
self.logger.debug(f"Debug info - Item rate: {item_rate}, Response rate: {response_rate}")
|
|
73
|
+
|
|
74
|
+
self.item_count, self.response_count = last_item_count, last_response_count
|
|
75
|
+
|
|
76
|
+
# 修复效率计算,确保使用正确的单位
|
|
77
|
+
if self.unit == 'min' and self.seconds > 0:
|
|
78
|
+
# 转换为每分钟速率
|
|
79
|
+
pages_per_min = response_rate * 60 / self.seconds if self.seconds > 0 else 0
|
|
80
|
+
items_per_min = item_rate * 60 / self.seconds if self.seconds > 0 else 0
|
|
81
|
+
self.logger.info(
|
|
82
|
+
f'Crawled {last_response_count} pages (at {pages_per_min:.0f} pages/min),'
|
|
83
|
+
f' Got {last_item_count} items (at {items_per_min:.0f} items/min).'
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
# 使用原始单位
|
|
87
|
+
self.logger.info(
|
|
88
|
+
f'Crawled {last_response_count} pages (at {response_rate} pages/{self.interval_display}{self.unit}),'
|
|
89
|
+
f' Got {last_item_count} items (at {item_rate} items/{self.interval_display}{self.unit}).'
|
|
90
|
+
)
|
|
91
|
+
self.logger.debug(f"Interval log iteration {iteration} completed, sleeping for {self.seconds} seconds")
|
|
92
|
+
await asyncio.sleep(self.seconds)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
self.logger.error(f"Error in interval logging: {e}")
|
|
95
95
|
await asyncio.sleep(self.seconds) # 即使出错也继续执行
|
crawlo/extension/log_stats.py
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
#!/usr/bin/python
|
|
2
|
-
# -*- coding:UTF-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
日志统计扩展
|
|
5
|
-
提供详细的爬虫运行统计信息
|
|
6
|
-
"""
|
|
7
|
-
import asyncio
|
|
8
|
-
from typing import Any
|
|
9
|
-
|
|
10
|
-
from crawlo.
|
|
11
|
-
from crawlo.utils import now, time_diff
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class LogStats:
|
|
15
|
-
"""
|
|
16
|
-
日志统计扩展,记录和输出爬虫运行过程中的各种统计信息
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
def __init__(self, crawler):
|
|
20
|
-
self.crawler = crawler
|
|
21
|
-
self.logger = get_logger(self.__class__.__name__
|
|
22
|
-
self._stats = crawler.stats
|
|
23
|
-
self._stats['start_time'] = now(fmt='%Y-%m-%d %H:%M:%S')
|
|
24
|
-
|
|
25
|
-
@classmethod
|
|
26
|
-
def from_crawler(cls, crawler):
|
|
27
|
-
return cls(crawler)
|
|
28
|
-
|
|
29
|
-
@classmethod
|
|
30
|
-
def create_instance(cls, crawler):
|
|
31
|
-
return cls.from_crawler(crawler)
|
|
32
|
-
|
|
33
|
-
async def spider_closed(self, reason: str = 'finished') -> None:
|
|
34
|
-
try:
|
|
35
|
-
self._stats['end_time'] = now(fmt='%Y-%m-%d %H:%M:%S')
|
|
36
|
-
self._stats['cost_time(s)'] = time_diff(start=self._stats['start_time'], end=self._stats['end_time'])
|
|
37
|
-
self._stats['reason'] = reason
|
|
38
|
-
except Exception as e:
|
|
39
|
-
# 添加日志以便调试
|
|
40
|
-
self.logger.error(f"Error in spider_closed: {e}")
|
|
41
|
-
# 静默处理,避免影响爬虫运行
|
|
42
|
-
pass
|
|
43
|
-
|
|
44
|
-
async def item_successful(self, _item: Any, _spider: Any) -> None:
|
|
45
|
-
try:
|
|
46
|
-
self._stats.inc_value('item_successful_count')
|
|
47
|
-
except Exception as e:
|
|
48
|
-
# 静默处理,避免影响爬虫运行
|
|
49
|
-
pass
|
|
50
|
-
|
|
51
|
-
async def item_discard(self, _item: Any, exc: Any, _spider: Any) -> None:
|
|
52
|
-
try:
|
|
53
|
-
# 只增加总的丢弃计数,不记录每个丢弃项目的原因详情
|
|
54
|
-
self._stats.inc_value('item_discard_count')
|
|
55
|
-
except Exception as e:
|
|
56
|
-
# 静默处理,避免影响爬虫运行
|
|
57
|
-
pass
|
|
58
|
-
|
|
59
|
-
async def response_received(self, _response: Any, _spider: Any) -> None:
|
|
60
|
-
try:
|
|
61
|
-
self._stats.inc_value('response_received_count')
|
|
62
|
-
except Exception as e:
|
|
63
|
-
# 静默处理,避免影响爬虫运行
|
|
64
|
-
pass
|
|
65
|
-
|
|
66
|
-
async def request_scheduled(self, _request: Any, _spider: Any) -> None:
|
|
67
|
-
try:
|
|
68
|
-
self._stats.inc_value('request_scheduler_count')
|
|
69
|
-
except Exception as e:
|
|
70
|
-
# 静默处理,避免影响爬虫运行
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding:UTF-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
日志统计扩展
|
|
5
|
+
提供详细的爬虫运行统计信息
|
|
6
|
+
"""
|
|
7
|
+
import asyncio
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from crawlo.logging import get_logger
|
|
11
|
+
from crawlo.utils import now, time_diff
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LogStats:
|
|
15
|
+
"""
|
|
16
|
+
日志统计扩展,记录和输出爬虫运行过程中的各种统计信息
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, crawler):
|
|
20
|
+
self.crawler = crawler
|
|
21
|
+
self.logger = get_logger(self.__class__.__name__)
|
|
22
|
+
self._stats = crawler.stats
|
|
23
|
+
self._stats['start_time'] = now(fmt='%Y-%m-%d %H:%M:%S')
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_crawler(cls, crawler):
|
|
27
|
+
return cls(crawler)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def create_instance(cls, crawler):
|
|
31
|
+
return cls.from_crawler(crawler)
|
|
32
|
+
|
|
33
|
+
async def spider_closed(self, reason: str = 'finished') -> None:
|
|
34
|
+
try:
|
|
35
|
+
self._stats['end_time'] = now(fmt='%Y-%m-%d %H:%M:%S')
|
|
36
|
+
self._stats['cost_time(s)'] = time_diff(start=self._stats['start_time'], end=self._stats['end_time'])
|
|
37
|
+
self._stats['reason'] = reason
|
|
38
|
+
except Exception as e:
|
|
39
|
+
# 添加日志以便调试
|
|
40
|
+
self.logger.error(f"Error in spider_closed: {e}")
|
|
41
|
+
# 静默处理,避免影响爬虫运行
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
async def item_successful(self, _item: Any, _spider: Any) -> None:
|
|
45
|
+
try:
|
|
46
|
+
self._stats.inc_value('item_successful_count')
|
|
47
|
+
except Exception as e:
|
|
48
|
+
# 静默处理,避免影响爬虫运行
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
async def item_discard(self, _item: Any, exc: Any, _spider: Any) -> None:
|
|
52
|
+
try:
|
|
53
|
+
# 只增加总的丢弃计数,不记录每个丢弃项目的原因详情
|
|
54
|
+
self._stats.inc_value('item_discard_count')
|
|
55
|
+
except Exception as e:
|
|
56
|
+
# 静默处理,避免影响爬虫运行
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
async def response_received(self, _response: Any, _spider: Any) -> None:
|
|
60
|
+
try:
|
|
61
|
+
self._stats.inc_value('response_received_count')
|
|
62
|
+
except Exception as e:
|
|
63
|
+
# 静默处理,避免影响爬虫运行
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
async def request_scheduled(self, _request: Any, _spider: Any) -> None:
|
|
67
|
+
try:
|
|
68
|
+
self._stats.inc_value('request_scheduler_count')
|
|
69
|
+
except Exception as e:
|
|
70
|
+
# 静默处理,避免影响爬虫运行
|
|
71
71
|
pass
|