-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscrape.py
More file actions
131 lines (102 loc) · 3.62 KB
/
Copy pathscrape.py
File metadata and controls
131 lines (102 loc) · 3.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import asyncio
from pydantic import BaseModel, ConfigDict
from crawl import process_scheduled_tasks, worker
from crawler_pool import close_all, janitor
from monitor import DynamicRateLimiter, WorkerMonitor
from apscheduler.schedulers.asyncio import AsyncIOScheduler
scheduler = AsyncIOScheduler()
class AppConfig(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
worker_monitor: WorkerMonitor
rate_limiter: DynamicRateLimiter
app = AppConfig(
worker_monitor=WorkerMonitor(0),
rate_limiter=DynamicRateLimiter(),
)
class AppJanitor:
def __init__(self):
self._janitor = None # Initialize _app_janitor
async def __aenter__(self):
try:
# await get_crawler(BrowserConfig(
# extra_args=config["crawler"]["browser"].get("extra_args", []),
# **config["crawler"]["browser"].get("kwargs", {}),
# )) # warm‑up
self._janitor = asyncio.create_task(janitor()) # Start janitor here
return self
except Exception as e:
print(f"Error occurred in lifespan startup: {e}")
raise
async def __aexit__(self, exc_type, exc_val, exc_tb):
if self._janitor:
self._janitor.cancel()
await close_all()
print("Lifespan shutdown complete.")
# Add jobs to the scheduler
@scheduler.scheduled_job("interval", seconds=18, max_instances=10, coalesce=True)
async def scheduled_job():
monitor: WorkerMonitor = app.worker_monitor
await monitor.update_metrics()
# Only process if system is healthy
if await should_process_tasks(monitor.metrics):
await process_scheduled_tasks()
else:
print("System under load, deferring scheduled tasks")
async def should_process_tasks(metrics):
return all(
[
metrics["cpu_usage"] < 85,
metrics["memory_usage"] < 85,
metrics["error_rate"] < 0.15,
]
)
async def exception_handler(exc):
# Handle exceptions here
print(f"Exception in worker process: {exc}")
async def startup_event():
"""
Key Improvements:
Dynamic rate limiting based on system metrics
Per-machine monitoring and coordination
Adaptive queue length limits
Error rate tracking and backoff
System health checks before processing
"""
scheduler.start()
# worker_args = []
# worker_args = [i for i in range(NUM_WORKERS)]
# for i in range(NUM_WORKERS):
# asyncio.create_task(worker(i))
await worker()
# async with Pool(
# loop_initializer=uvloop.new_event_loop, exception_handler=exception_handler
# ) as pool:
# async for results in pool.map(worker, worker_args):
# print(await results)
# pass # Handle results if necessary
# Shutdown event to clean up resources
async def shutdown_event():
scheduler.shutdown()
async with AppJanitor() as aj:
await aj.__aexit__(None, None, None)
async def main():
async with AppJanitor():
print("Application startup initiated.")
try:
await startup_event()
except Exception as e:
print(f"Error occurred in startup event: {e}")
finally:
await shutdown_event()
print("Application shutdown complete.")
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\nKeyboard interrupt received")
# asyncio.run(shutdown_event())
except Exception as e:
print(f"Error occurred outside startup event: {e}")
# asyncio.run(shutdown_event())
finally:
print("Worker exited.")