Skip to content

Commit 61c275c

Browse files
committed
chore: Disable debug images and video recording for production
1 parent c89b4d3 commit 61c275c

7 files changed

Lines changed: 172 additions & 60 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,5 +97,7 @@ python3 startup.py
9797
| `MQTT_USER` | MQTT 用户名 | (空) |
9898
| `MQTT_PASSWORD` | MQTT 密码 | (空) |
9999
| `JOB_START_TIME` | 每天定时运行时间 | `07:00` |
100-
| `SLIDER_OFFSET` | 验证码滑块偏移微调,如果持续登录报错,考虑调整这个数值(-10 ~ 10) | `5` |
100+
| `SLIDER_OFFSET` | 验证码滑块偏移微调(-2 ~ 10) | `5` |
101101
| `IGNORE_USER_ID` | 忽略的户号(逗号分隔) | (空) |
102+
103+
*SLIDER_OFFSET 这个参数非常重要!如果持续登录报错,请不断调整这个数值,我这里是 5 最合适*

captcha_solver.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def solve_gap(self, image):
103103
model_width = 416
104104
scale_ratio = original_width / model_width
105105

106-
x_coordinate = boxes[..., :4].astype(np.int32)[0][0]
107-
scaled_x = int(x_coordinate * scale_ratio)
106+
x_coordinate = boxes[..., :4][0][0] # Keep as float
107+
scaled_x = x_coordinate * scale_ratio
108108

109109
return scaled_x

recorder.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import cv2
2+
import numpy as np
3+
import threading
4+
import time
5+
import logging
6+
import os
7+
8+
class ScreenRecorder:
9+
def __init__(self, driver, output_path, fps=5.0):
10+
self.driver = driver
11+
self.output_path = output_path
12+
self.fps = fps
13+
self.stop_event = threading.Event()
14+
self.thread = None
15+
self.logger = logging.getLogger(__name__)
16+
17+
def start(self):
18+
self.stop_event.clear()
19+
self.thread = threading.Thread(target=self._record_loop)
20+
self.thread.start()
21+
self.logger.info(f"Started screen recording to {self.output_path}")
22+
23+
def stop(self):
24+
if self.thread and self.thread.is_alive():
25+
self.stop_event.set()
26+
self.thread.join()
27+
self.logger.info(f"Stopped screen recording. Saved to {self.output_path}")
28+
29+
def _record_loop(self):
30+
video_writer = None
31+
32+
# Ensure directory exists
33+
os.makedirs(os.path.dirname(self.output_path), exist_ok=True)
34+
35+
try:
36+
while not self.stop_event.is_set():
37+
start_time = time.time()
38+
39+
try:
40+
# Capture screenshot as PNG binary
41+
png_data = self.driver.get_screenshot_as_png()
42+
43+
# Convert to numpy array
44+
nparr = np.frombuffer(png_data, np.uint8)
45+
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
46+
47+
if img is None:
48+
continue
49+
50+
# Initialize video writer on first frame
51+
if video_writer is None:
52+
height, width, _ = img.shape
53+
# MJPG is more compatible in headless docker
54+
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
55+
video_writer = cv2.VideoWriter(self.output_path, fourcc, self.fps, (width, height))
56+
57+
video_writer.write(img)
58+
59+
except Exception as e:
60+
self.logger.warning(f"Error capturing frame: {e}")
61+
62+
# Maintain FPS
63+
elapsed = time.time() - start_time
64+
wait_time = max(0, (1.0 / self.fps) - elapsed)
65+
time.sleep(wait_time)
66+
67+
finally:
68+
if video_writer:
69+
video_writer.release()

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ openai
66
onnxruntime
77
numpy
88
paho-mqtt==1.6.1
9-
python-dotenv
9+
python-dotenv
10+
opencv-python-headless

sgcc_client.py

Lines changed: 81 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -76,44 +76,51 @@ def get_tracks(self, distance):
7676
:return: List of x-offsets
7777
"""
7878
tracks = []
79-
current = 0
79+
current = 0 # Integer current position
8080
mid = distance * 4 / 5 # Decelerate after 4/5
81-
t = 0.2 # Time interval
81+
t = 0.02 # Time interval (simulated) - reduced for smoother steps
8282
v = 0 # Initial velocity
8383

8484
while current < distance:
8585
if current < mid:
8686
# Acceleration phase
87-
a = 2 + random.uniform(-0.5, 0.5)
87+
a = random.randint(400, 600)
8888
else:
8989
# Deceleration phase
90-
a = -3 + random.uniform(-0.5, 0.5)
90+
a = -random.randint(600, 800)
9191

9292
v0 = v
9393
v = v0 + a * t
9494
move = v0 * t + 0.5 * a * t * t
95-
current += move
96-
tracks.append(round(move))
97-
98-
# Correction
99-
generated_distance = sum(tracks)
100-
diff = distance - generated_distance
101-
if diff != 0:
102-
tracks.append(diff)
10395

96+
# Ensure forward movement
97+
if move < 1: move = 1
98+
99+
move_int = round(move)
100+
101+
# Check if this move overshoots
102+
if current + move_int > distance:
103+
move_int = distance - current
104+
105+
tracks.append(move_int)
106+
current += move_int
107+
108+
if current >= distance:
109+
break
110+
104111
return tracks
105112

106113
def get_tracks_with_jitter(self, distance):
107114
"""
108-
Advanced tracks with Y-axis jitter and overshoot
115+
Advanced tracks with Y-axis jitter
109116
"""
110117
tracks = self.get_tracks(distance)
111118

112-
# Simulate overshoot
113-
if random.choice([True, False]):
114-
overshoot = random.randint(2, 5)
115-
tracks.append(overshoot)
116-
tracks.append(-overshoot)
119+
# Overshoot removed based on user feedback (causes validation failure)
120+
# if random.choice([True, False]):
121+
# overshoot = random.randint(2, 5)
122+
# tracks.append(overshoot)
123+
# tracks.append(-overshoot)
117124

118125
return tracks
119126

@@ -206,6 +213,11 @@ def init_driver(self):
206213
options.add_argument('--disable-dev-shm-usage')
207214
options.add_argument("--window-size=1920,1080")
208215

216+
# Anti-detection options
217+
options.add_argument("--disable-blink-features=AutomationControlled")
218+
options.add_experimental_option("excludeSwitches", ["enable-automation"])
219+
options.add_experimental_option('useAutomationExtension', False)
220+
209221
chrome_binary = os.getenv("CHROME_BINARY_PATH")
210222
# Fallback for Docker if env var is empty (overridden by .env)
211223
if not chrome_binary and os.environ.get('PYTHON_IN_DOCKER') == 'true':
@@ -226,6 +238,16 @@ def init_driver(self):
226238
service = ChromeService(driver_path)
227239

228240
driver = webdriver.Chrome(options=options, service=service)
241+
242+
# CDP command to hide webdriver property
243+
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
244+
"source": """
245+
Object.defineProperty(navigator, 'webdriver', {
246+
get: () => undefined
247+
})
248+
"""
249+
})
250+
229251
driver.implicitly_wait(self.wait_time)
230252
return driver
231253

@@ -301,7 +323,8 @@ def perform_login(self, driver):
301323
base64_img = driver.execute_script(js_img)
302324

303325
# Get rendered width (CSS width)
304-
js_width = 'return document.getElementById("slideVerify").childNodes[0].clientWidth;'
326+
# Get rendered width (CSS width) - Use getBoundingClientRect for float precision
327+
js_width = 'return document.getElementById("slideVerify").childNodes[0].getBoundingClientRect().width;'
305328
rendered_width = driver.execute_script(js_width)
306329

307330
img_data = base64_img.split(',')[1]
@@ -312,34 +335,34 @@ def perform_login(self, driver):
312335

313336
gap_pos = self.resolver.solve_gap(image)
314337

315-
# Apply scaling
316-
final_distance = int(gap_pos * scale_factor)
338+
# Apply scaling and round to nearest integer
339+
final_distance = int(round(gap_pos * scale_factor))
317340

318341
# Apply manual offset
319-
slider_offset = int(os.getenv("SLIDER_OFFSET", 2))
342+
slider_offset = int(os.getenv("SLIDER_OFFSET", 5))
320343
final_distance += slider_offset
321344

322345
logging.info(f"Captcha: Gap={gap_pos}, Scale={scale_factor:.2f}, Offset={slider_offset}, FinalDist={final_distance}")
323346

324347
# Save debug image with lines
325-
try:
326-
from PIL import ImageDraw
327-
debug_img = image.copy()
328-
draw = ImageDraw.Draw(debug_img)
348+
# try:
349+
# from PIL import ImageDraw
350+
# debug_img = image.copy()
351+
# draw = ImageDraw.Draw(debug_img)
329352

330-
# Red line: Original VLM detection
331-
draw.line([(gap_pos, 0), (gap_pos, debug_img.height)], fill="red", width=3)
353+
# # Red line: Original VLM detection
354+
# draw.line([(gap_pos, 0), (gap_pos, debug_img.height)], fill="red", width=3)
332355

333-
# Green line: Final target (converted back to image scale)
334-
final_target_on_image = int(final_distance / scale_factor)
335-
draw.line([(final_target_on_image, 0), (final_target_on_image, debug_img.height)], fill="green", width=3)
356+
# # Green line: Final target (converted back to image scale)
357+
# final_target_on_image = int(final_distance / scale_factor)
358+
# draw.line([(final_target_on_image, 0), (final_target_on_image, debug_img.height)], fill="green", width=3)
336359

337-
timestamp = time.strftime("%Y%m%d_%H%M%S")
338-
debug_path = f"./errors/captcha_{timestamp}.png"
339-
debug_img.save(debug_path)
340-
logging.info(f"Saved debug captcha image to {debug_path}")
341-
except Exception as e:
342-
logging.warning(f"Failed to save debug image: {e}")
360+
# timestamp = time.strftime("%Y%m%d_%H%M%S")
361+
# debug_path = f"./errors/captcha_{timestamp}.png"
362+
# debug_img.save(debug_path)
363+
# logging.info(f"Saved debug captcha image to {debug_path}")
364+
# except Exception as e:
365+
# logging.warning(f"Failed to save debug image: {e}")
343366

344367
self.simulate_slide(driver, final_distance)
345368
time.sleep(self.retry_delay)
@@ -348,13 +371,13 @@ def perform_login(self, driver):
348371
logging.info(f"Login failed (Attempt {attempt}), retrying captcha...")
349372

350373
# Capture screenshot to see the error message
351-
try:
352-
timestamp = time.strftime("%Y%m%d_%H%M%S")
353-
error_shot_path = f"./errors/login_fail_{timestamp}.png"
354-
driver.save_screenshot(error_shot_path)
355-
logging.info(f"Saved login failure screenshot to {error_shot_path}")
356-
except Exception as e:
357-
logging.warning(f"Failed to save failure screenshot: {e}")
374+
# try:
375+
# timestamp = time.strftime("%Y%m%d_%H%M%S")
376+
# error_shot_path = f"./errors/login_fail_{timestamp}.png"
377+
# driver.save_screenshot(error_shot_path)
378+
# logging.info(f"Saved login failure screenshot to {error_shot_path}")
379+
# except Exception as e:
380+
# logging.warning(f"Failed to save failure screenshot: {e}")
358381

359382
self._click_element(driver, By.CLASS_NAME, "el-button.el-button--primary")
360383
time.sleep(self.retry_delay * 2)
@@ -369,19 +392,31 @@ def run(self):
369392
# Force window size for headless mode
370393
driver.set_window_size(1920, 1080)
371394
size = driver.get_window_size()
372-
logging.info(f"Driver initialized. Window size: {size}")
395+
pixel_ratio = driver.execute_script("return window.devicePixelRatio;")
396+
logging.info(f"Driver initialized. Window size: {size}, DevicePixelRatio: {pixel_ratio}")
397+
398+
# Start Screen Recording
399+
# from recorder import ScreenRecorder
400+
# timestamp = time.strftime("%Y%m%d_%H%M%S")
401+
# video_path = f"./errors/record_{timestamp}.avi"
402+
# recorder = ScreenRecorder(driver, video_path, fps=3.0)
403+
# recorder.start()
373404

374405
publisher = MQTTPublisher()
375406

376407
try:
377408
if self.perform_login(driver):
378409
logging.info("Login successful!")
410+
# Stop recording immediately after success to save time/space
411+
# recorder.stop()
379412
else:
380413
logging.error("Login failed!")
414+
# recorder.stop()
381415
driver.quit()
382416
return
383417
except Exception as e:
384418
logging.error(f"Login exception: {e}")
419+
# recorder.stop()
385420
driver.quit()
386421
return
387422

@@ -410,6 +445,7 @@ def run(self):
410445

411446
logging.info("All tasks completed successfully.")
412447
self.cleanup_debug_images()
448+
# recorder.stop()
413449
driver.quit()
414450

415451
def cleanup_debug_images(self):

startup.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,20 @@ def setup_logging(level: str):
1919
logger.addHandler(sh)
2020

2121
def execute_job(spider: SGCCSpider, max_retries: int):
22-
for attempt in range(1, max_retries + 1):
23-
try:
24-
spider.run()
25-
next_run = schedule.next_run()
26-
if next_run:
27-
logging.info(f"Going to sleep. Next run scheduled at: {next_run.strftime('%Y-%m-%d %H:%M:%S')}")
28-
return
29-
except Exception as e:
30-
logging.error(f"Job failed (Attempt {attempt}/{max_retries}): {e}")
31-
continue
22+
try:
23+
spider.run()
24+
25+
# Calculate the real next run time (filter out past/current jobs)
26+
now = datetime.now()
27+
future_runs = [job.next_run for job in schedule.jobs if job.next_run and job.next_run > now]
28+
if future_runs:
29+
next_run = min(future_runs)
30+
logging.info(f"Going to sleep. Next run scheduled at: {next_run.strftime('%Y-%m-%d %H:%M:%S')}")
31+
else:
32+
logging.info("Going to sleep. No future runs scheduled.")
33+
34+
except Exception as e:
35+
logging.error(f"Job failed: {e}")
3236

3337
def main():
3438
if 'PYTHON_IN_DOCKER' not in os.environ:

vlm_solver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def solve_gap(self, image):
8686

8787
real_x_offset = (x_center_normalized / 1000) * real_width
8888

89-
return int(real_x_offset)
89+
return real_x_offset
9090

9191
except Exception as e:
9292
logging.error(f"VLM Solver failed: {e}")

0 commit comments

Comments
 (0)