(十三)分别控制并发数量与请求间隔
1. 两项同时进行不代表两次发出之间有间隔
并发数为 2,表示最多安排两个详情任务;如果两者同时开始,仍可能在很短时间内发出两次请求。请求间隔管的是开始时机,可以让所有尝试共用一个门:
# 项目文件:crawler_project/src/catalogcrawl/pacing.py
# 一把异步锁统一安排所有请求尝试的起始间隔,重试也从这里取得时机。
from asyncio import Lock, sleep
import math
from time import monotonic
class StartGate:
def __init__(self, interval: float):
if not math.isfinite(interval) or interval < 0:
raise ValueError("请求间隔应为非负的有限秒数")
self.interval = interval
self.next_at = 0.0
self.lock = Lock()
async def wait(self):
async with self.lock:
delay = self.next_at - monotonic()
if delay > 0:
await sleep(delay)
self.next_at = monotonic() + self.intervalmonotonic() 提供不会因系统时间校准而倒退的经过时间读数。第一项直接取得时机;设置下一次可开始的位置。第二项在锁内发现时间还没到,异步等待剩余部分,再更新下一次时机。
这把 asyncio.Lock 保护“读取下次时间、等待、更新下次时间”这一整组安排。正在等间隔时,循环仍可以推进已经发出的请求;同一门上的另一个新尝试要依次取得锁。重试也走这里,避免失败时突然绕过原来的间隔安排。