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/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.utils.log import get_logger
|
|
7
|
-
from crawlo.event import spider_opened, spider_closed
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class LogIntervalExtension(object):
|
|
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__, crawler.settings.get('LOG_LEVEL'))
|
|
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=spider_opened)
|
|
40
|
-
crawler.subscriber.subscribe(o.spider_closed, event=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}")
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding:UTF-8 -*-
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
from crawlo.utils.log import get_logger
|
|
7
|
+
from crawlo.event import spider_opened, spider_closed
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LogIntervalExtension(object):
|
|
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__, crawler.settings.get('LOG_LEVEL'))
|
|
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=spider_opened)
|
|
40
|
+
crawler.subscriber.subscribe(o.spider_closed, event=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.utils.log 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__, crawler.settings.get('LOG_LEVEL'))
|
|
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.utils.log 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__, crawler.settings.get('LOG_LEVEL'))
|
|
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
|
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
from crawlo.exceptions import NotConfigured
|
|
3
|
-
from crawlo.utils.log import get_logger
|
|
4
|
-
|
|
5
|
-
# 延迟获取logger,确保在日志系统配置之后获取
|
|
6
|
-
_logger = None
|
|
7
|
-
|
|
8
|
-
def logger():
|
|
9
|
-
"""延迟获取logger实例,确保在日志系统配置之后获取"""
|
|
10
|
-
global _logger
|
|
11
|
-
if _logger is None:
|
|
12
|
-
_logger = get_logger(__name__)
|
|
13
|
-
return _logger
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class CustomLoggerExtension:
|
|
17
|
-
"""
|
|
18
|
-
日志系统初始化扩展
|
|
19
|
-
遵循与 ExtensionManager 一致的接口规范:使用 create_instance
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
def __init__(self, settings: Any):
|
|
23
|
-
self.settings = settings
|
|
24
|
-
# 使用新的日志系统,但要简化配置传递
|
|
25
|
-
try:
|
|
26
|
-
from crawlo.logging import configure_logging
|
|
27
|
-
# 直接传递settings对象,让日志系统内部处理
|
|
28
|
-
configure_logging(settings)
|
|
29
|
-
except Exception as e:
|
|
30
|
-
# 如果日志系统配置失败,不应该阻止扩展加载
|
|
31
|
-
# 使用基本日志输出错误信息
|
|
32
|
-
import logging
|
|
33
|
-
logging.getLogger(__name__).warning(f"Failed to configure logging system: {e}")
|
|
34
|
-
# 不抛出异常,让扩展继续加载
|
|
35
|
-
|
|
36
|
-
@classmethod
|
|
37
|
-
def create_instance(cls, crawler: Any, *args: Any, **kwargs: Any) -> 'CustomLoggerExtension':
|
|
38
|
-
"""
|
|
39
|
-
工厂方法:兼容 ExtensionManager 的创建方式
|
|
40
|
-
被 ExtensionManager 调用
|
|
41
|
-
"""
|
|
42
|
-
# 可以通过 settings 控制是否启用
|
|
43
|
-
log_file = crawler.settings.get('LOG_FILE')
|
|
44
|
-
log_enable_custom = crawler.settings.get('LOG_ENABLE_CUSTOM', False)
|
|
45
|
-
|
|
46
|
-
# 只有当没有配置日志文件且未启用自定义日志时才禁用
|
|
47
|
-
if not log_file and not log_enable_custom:
|
|
48
|
-
raise NotConfigured("CustomLoggerExtension: LOG_FILE not set and LOG_ENABLE_CUSTOM=False")
|
|
49
|
-
|
|
50
|
-
return cls(crawler.settings)
|
|
51
|
-
|
|
52
|
-
def spider_opened(self, spider: Any) -> None:
|
|
53
|
-
logger_instance = logger()
|
|
54
|
-
try:
|
|
55
|
-
logger_instance.info(
|
|
56
|
-
f"CustomLoggerExtension: Logging initialized. "
|
|
57
|
-
f"LOG_FILE={self.settings.get('LOG_FILE')}, "
|
|
58
|
-
f"LOG_LEVEL={self.settings.get('LOG_LEVEL')}"
|
|
59
|
-
)
|
|
60
|
-
except Exception as e:
|
|
61
|
-
# 即使日志初始化信息无法打印,也不应该影响程序运行
|
|
1
|
+
from typing import Any
|
|
2
|
+
from crawlo.exceptions import NotConfigured
|
|
3
|
+
from crawlo.utils.log import get_logger
|
|
4
|
+
|
|
5
|
+
# 延迟获取logger,确保在日志系统配置之后获取
|
|
6
|
+
_logger = None
|
|
7
|
+
|
|
8
|
+
def logger():
|
|
9
|
+
"""延迟获取logger实例,确保在日志系统配置之后获取"""
|
|
10
|
+
global _logger
|
|
11
|
+
if _logger is None:
|
|
12
|
+
_logger = get_logger(__name__)
|
|
13
|
+
return _logger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CustomLoggerExtension:
|
|
17
|
+
"""
|
|
18
|
+
日志系统初始化扩展
|
|
19
|
+
遵循与 ExtensionManager 一致的接口规范:使用 create_instance
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, settings: Any):
|
|
23
|
+
self.settings = settings
|
|
24
|
+
# 使用新的日志系统,但要简化配置传递
|
|
25
|
+
try:
|
|
26
|
+
from crawlo.logging import configure_logging
|
|
27
|
+
# 直接传递settings对象,让日志系统内部处理
|
|
28
|
+
configure_logging(settings)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
# 如果日志系统配置失败,不应该阻止扩展加载
|
|
31
|
+
# 使用基本日志输出错误信息
|
|
32
|
+
import logging
|
|
33
|
+
logging.getLogger(__name__).warning(f"Failed to configure logging system: {e}")
|
|
34
|
+
# 不抛出异常,让扩展继续加载
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def create_instance(cls, crawler: Any, *args: Any, **kwargs: Any) -> 'CustomLoggerExtension':
|
|
38
|
+
"""
|
|
39
|
+
工厂方法:兼容 ExtensionManager 的创建方式
|
|
40
|
+
被 ExtensionManager 调用
|
|
41
|
+
"""
|
|
42
|
+
# 可以通过 settings 控制是否启用
|
|
43
|
+
log_file = crawler.settings.get('LOG_FILE')
|
|
44
|
+
log_enable_custom = crawler.settings.get('LOG_ENABLE_CUSTOM', False)
|
|
45
|
+
|
|
46
|
+
# 只有当没有配置日志文件且未启用自定义日志时才禁用
|
|
47
|
+
if not log_file and not log_enable_custom:
|
|
48
|
+
raise NotConfigured("CustomLoggerExtension: LOG_FILE not set and LOG_ENABLE_CUSTOM=False")
|
|
49
|
+
|
|
50
|
+
return cls(crawler.settings)
|
|
51
|
+
|
|
52
|
+
def spider_opened(self, spider: Any) -> None:
|
|
53
|
+
logger_instance = logger()
|
|
54
|
+
try:
|
|
55
|
+
logger_instance.info(
|
|
56
|
+
f"CustomLoggerExtension: Logging initialized. "
|
|
57
|
+
f"LOG_FILE={self.settings.get('LOG_FILE')}, "
|
|
58
|
+
f"LOG_LEVEL={self.settings.get('LOG_LEVEL')}"
|
|
59
|
+
)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
# 即使日志初始化信息无法打印,也不应该影响程序运行
|
|
62
62
|
pass
|