diff --git a/.env.example b/.env.example index fa86b9b..53f9d2e 100644 --- a/.env.example +++ b/.env.example @@ -10,4 +10,5 @@ LOGIN_USERNAME= LOGIN_PASSWORD= QUESTION_BANK=测录融合 ANSWER_COUNT=0 -ANSWER_DELAY_MS=0 +ANSWER_WAIT_S=5-10 +RESULT_DELAY_S=2 diff --git a/README.md b/README.md index db8519e..5329823 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,14 @@ 它会执行: ```bat -uv run auto-answer practice --answer-count -1 --delay-ms 500 +uv run auto-answer practice --answer-count -1 --answer-wait-s 5-10 --result-delay-s 2 ``` 含义: - `--answer-count -1`:一直答题,直到没有下一题。 -- `--delay-ms 500`:每答完一题后等待 500ms。 +- `--answer-wait-s 5-10`:每题答题前随机等待 5 到 10 秒。 +- `--result-delay-s 2`:点击答案后到点击下一题前随机等待,范围是 1 到 3 秒。 - 题库名称、账号、密码等从 `.env` 读取。 ## 首次准备 @@ -49,7 +50,8 @@ LOGIN_USERNAME=你的账号 LOGIN_PASSWORD=你的密码 QUESTION_BANK=测录融合 ANSWER_COUNT=0 -ANSWER_DELAY_MS=0 +ANSWER_WAIT_S=5-10 +RESULT_DELAY_S=2 ``` 常用配置说明: @@ -59,7 +61,8 @@ ANSWER_DELAY_MS=0 - `LOGIN_USERNAME` / `LOGIN_PASSWORD`:登录账号密码。 - `QUESTION_BANK`:题库关键词,程序会从页面 tab 中自动匹配最接近的题库。 - `ANSWER_COUNT`:自动答题数量,`0` 表示只进入练习不答题,`-1` 表示一直答到结束。 -- `ANSWER_DELAY_MS`:每题答完后的等待时间,单位毫秒。 +- `ANSWER_WAIT_S`:每题答题前的随机等待时间,格式是 `最小秒数-最大秒数`。 +- `RESULT_DELAY_S`:点击答案后到点击下一题前的基准等待秒数,实际随机范围是 `50%-150%`。 - `STORAGE_STATE_PATH`:保存 cookie/localStorage 的文件。 - `SESSION_STATE_PATH`:保存 sessionStorage 的文件。 @@ -85,10 +88,10 @@ uv run auto-answer practice --bank "测录融合" uv run auto-answer practice --bank "测录融合" --answer-count 10 ``` -一直答到结束,每题等待 500ms: +一直答到结束,每题答题前等待 5 到 10 秒,点击答案后再等待约 1 到 3 秒: ```powershell -uv run auto-answer practice --bank "测录融合" --answer-count -1 --delay-ms 500 +uv run auto-answer practice --bank "测录融合" --answer-count -1 --answer-wait-s 5-10 --result-delay-s 2 ``` 只登录并保存登录态: diff --git a/src/auto_answer/cli.py b/src/auto_answer/cli.py index b1cc228..fa0453b 100644 --- a/src/auto_answer/cli.py +++ b/src/auto_answer/cli.py @@ -16,8 +16,10 @@ def build_settings(args: argparse.Namespace) -> Settings: values["question_bank"] = args.bank if getattr(args, "answer_count", None) is not None: values["answer_count"] = args.answer_count - if getattr(args, "delay_ms", None) is not None: - values["answer_delay_ms"] = args.delay_ms + if getattr(args, "answer_wait_s", None) is not None: + values["answer_wait_s"] = args.answer_wait_s + if getattr(args, "result_delay_s", None) is not None: + values["result_delay_s"] = args.result_delay_s return Settings(**values) @@ -52,9 +54,13 @@ def build_parser() -> argparse.ArgumentParser: help="Number of questions to answer automatically. Use -1 to answer until the end.", ) practice_parser.add_argument( - "--delay-ms", - type=int, - help="Delay after each answered question, in milliseconds.", + "--answer-wait-s", + help="Random wait before each question, in seconds. Format: min-max.", + ) + practice_parser.add_argument( + "--result-delay-s", + type=float, + help="Base wait after selecting answer before next question, in seconds.", ) open_parser = subparsers.add_parser("open", help="Open the target page and wait.") diff --git a/src/auto_answer/config.py b/src/auto_answer/config.py index 1d9b401..24a59e9 100644 --- a/src/auto_answer/config.py +++ b/src/auto_answer/config.py @@ -35,4 +35,5 @@ class Settings(BaseSettings): login_password: str | None = Field(default=None, alias="LOGIN_PASSWORD") question_bank: str | None = Field(default=None, alias="QUESTION_BANK") answer_count: int = Field(default=0, alias="ANSWER_COUNT") - answer_delay_ms: int = Field(default=0, alias="ANSWER_DELAY_MS") + answer_wait_s: str = Field(default="5-10", alias="ANSWER_WAIT_S") + result_delay_s: float = Field(default=2.0, alias="RESULT_DELAY_S") diff --git a/src/auto_answer/runner.py b/src/auto_answer/runner.py index 0c9bbdc..ca03b55 100644 --- a/src/auto_answer/runner.py +++ b/src/auto_answer/runner.py @@ -2,6 +2,7 @@ from __future__ import annotations from difflib import SequenceMatcher import json +import random import re import time from pathlib import Path @@ -220,16 +221,41 @@ class AnswerRunner: def _answer_questions(self, page, count: int) -> None: answered = 0 while count < 0 or answered < count: + self._wait_before_answer() answer = self._reveal_and_read_answer(page) total = "until end" if count < 0 else str(count) print(f"Answering question {answered + 1}/{total}: {answer}") self._select_answer_options(page, answer) + self._wait_after_answer() if not self._go_next_question(page): print("No next question detected. Stopping.") return answered += 1 - if self.settings.answer_delay_ms > 0: - time.sleep(self.settings.answer_delay_ms / 1000) + + def _wait_before_answer(self) -> None: + minimum, maximum = self._parse_delay_range(self.settings.answer_wait_s) + delay = random.uniform(minimum, maximum) + print(f"Waiting before answer: {delay:.2f}s") + time.sleep(delay) + + def _wait_after_answer(self) -> None: + base = max(self.settings.result_delay_s, 0) + delay = random.uniform(base * 0.5, base * 1.5) + print(f"Waiting before next question: {delay:.2f}s") + time.sleep(delay) + + def _parse_delay_range(self, value: str) -> tuple[float, float]: + match = re.fullmatch(r"\s*(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)\s*", value) + if not match: + raise SystemExit("ANSWER_WAIT_S must use min-max format, for example: 5-10") + + minimum = float(match.group(1)) + maximum = float(match.group(2)) + if minimum < 0 or maximum < 0: + raise SystemExit("ANSWER_WAIT_S cannot contain negative values.") + if minimum > maximum: + minimum, maximum = maximum, minimum + return minimum, maximum def _reveal_and_read_answer(self, page) -> str: body = page.locator("body") diff --git a/start.bat b/start.bat index a5141eb..83730ee 100644 --- a/start.bat +++ b/start.bat @@ -1,4 +1,4 @@ @echo off cd /d "%~dp0" -uv run auto-answer practice --answer-count -1 --delay-ms 500 +uv run auto-answer practice --answer-count -1 --answer-wait-s 5-10 --result-delay-s 2 pause