浏览代码

feat: update

root 3 周之前
父节点
当前提交
4bda276400
共有 3 个文件被更改,包括 127 次插入77 次删除
  1. 11 11
      config/config.json
  2. 43 41
      docker-compose.yml
  3. 73 25
      tls_registration_bot.py

+ 11 - 11
config/config.json

@@ -9,7 +9,7 @@
         {
         {
             "identifier": "vfs.ie.nl",
             "identifier": "vfs.ie.nl",
             "debug": false,
             "debug": false,
-            "enable": false,
+            "enable": true,
             "need_account": true,
             "need_account": true,
             "need_proxy": true,
             "need_proxy": true,
             "proxy_pool": "isp_all",
             "proxy_pool": "isp_all",
@@ -27,7 +27,7 @@
                 "account_pool_id": "ie.nl.booker",
                 "account_pool_id": "ie.nl.booker",
                 "target_instances": 1,
                 "target_instances": 1,
                 "account_cd": 180,
                 "account_cd": 180,
-                "booking_cooldown": 10,
+                "booking_cooldown": 300,
                 "max_bookings_per_account": 8
                 "max_bookings_per_account": 8
             },
             },
             "query_wait": {
             "query_wait": {
@@ -453,7 +453,7 @@
         {
         {
             "identifier": "vfs.ie.at",
             "identifier": "vfs.ie.at",
             "debug": false,
             "debug": false,
-            "enable": false,
+            "enable": true,
             "need_account": true,
             "need_account": true,
             "need_proxy": true,
             "need_proxy": true,
             "proxy_pool": "isp_all",
             "proxy_pool": "isp_all",
@@ -471,7 +471,7 @@
                 "account_pool_id": "ie.at.booker",
                 "account_pool_id": "ie.at.booker",
                 "target_instances": 1,
                 "target_instances": 1,
                 "account_cd": 180,
                 "account_cd": 180,
-                "booking_cooldown": 10,
+                "booking_cooldown": 300,
                 "max_bookings_per_account": 8
                 "max_bookings_per_account": 8
             },
             },
             "query_wait": {
             "query_wait": {
@@ -651,7 +651,7 @@
         {
         {
             "identifier": "vfs.ie.hu",
             "identifier": "vfs.ie.hu",
             "debug": false,
             "debug": false,
-            "enable": false,
+            "enable": true,
             "need_account": true,
             "need_account": true,
             "need_proxy": true,
             "need_proxy": true,
             "proxy_pool": "isp_all",
             "proxy_pool": "isp_all",
@@ -669,7 +669,7 @@
                 "account_pool_id": "ie.hu.booker",
                 "account_pool_id": "ie.hu.booker",
                 "target_instances": 1,
                 "target_instances": 1,
                 "account_cd": 180,
                 "account_cd": 180,
-                "booking_cooldown": 10,
+                "booking_cooldown": 180,
                 "max_bookings_per_account": 8
                 "max_bookings_per_account": 8
             },
             },
             "query_wait": {
             "query_wait": {
@@ -1190,7 +1190,7 @@
         {
         {
             "identifier": "visaonweb.ie.be",
             "identifier": "visaonweb.ie.be",
             "debug": false,
             "debug": false,
-            "enable": true,
+            "enable": false,
             "need_account": true,
             "need_account": true,
             "need_proxy": true,
             "need_proxy": true,
             "proxy_pool": "local",
             "proxy_pool": "local",
@@ -1339,12 +1339,12 @@
         {
         {
             "identifier": "greekemba.ie.gr",
             "identifier": "greekemba.ie.gr",
             "debug": false,
             "debug": false,
-            "enable": false,
+            "enable": true,
             "need_account": true,
             "need_account": true,
             "need_proxy": true,
             "need_proxy": true,
             "proxy_pool": "isp_all",
             "proxy_pool": "isp_all",
             "proxy_cd": 5,
             "proxy_cd": 5,
-            "session_max_life": 600,
+            "session_max_life": 1440,
             "sentinel": {
             "sentinel": {
                 "account_source": "built-in",
                 "account_source": "built-in",
                 "account_pool_id": "ie.gr.sentinel",
                 "account_pool_id": "ie.gr.sentinel",
@@ -1354,9 +1354,9 @@
             },
             },
             "booker": {
             "booker": {
                 "account_source": "order",
                 "account_source": "order",
-                "target_instances": 1,
+                "target_instances": 10,
                 "account_cd": 180,
                 "account_cd": 180,
-                "booking_cooldown": 10,
+                "booking_cooldown": 30,
                 "max_bookings_per_account": 1
                 "max_bookings_per_account": 1
             },
             },
             "query_wait": {
             "query_wait": {

+ 43 - 41
docker-compose.yml

@@ -25,46 +25,47 @@ services:
           cpus: '2.0'
           cpus: '2.0'
           memory: 4G
           memory: 4G
 
 
-  # visa-booker:
-  #   build: .
-  #   image: coordinator:latest
-  #   container_name: coordinator-booker
-  #   command: ["python3", "main_booker.py"]
-  #   restart: unless-stopped
-  #   shm_size: '2gb'
-  #   volumes:
-  #     - ./config:/app/config
-  #     - ./logs:/app/logs
-  #     - ./data:/app/data
-  #     - ./plugins:/app/plugins
-  #   environment:
-  #     - TZ=Asia/Shanghai
-  #     - DISPLAY=:99
-  #     - CHROME_BIN=/opt/ungoogled-chromium/chrome
-  #   # 资源限制
-  #   deploy:
-  #     resources:
-  #       limits:
-  #         cpus: '2.0'
-  #         memory: 4G
+  visa-booker:
+    build: .
+    image: coordinator:latest
+    container_name: coordinator-booker
+    command: ["python3", "main_booker.py"]
+    restart: unless-stopped
+    shm_size: '2gb'
+    volumes:
+      - ./config:/app/config
+      - ./logs:/app/logs
+      - ./data:/app/data
+      - ./plugins:/app/plugins
+    environment:
+      - TZ=Asia/Shanghai
+      - DISPLAY=:99
+      - CHROME_BIN=/opt/ungoogled-chromium/chrome
+    # 资源限制
+    deploy:
+      resources:
+        limits:
+          cpus: '2.0'
+          memory: 4G
 
 
-  # visa-sweeper:
-  #   build: .
-  #   image: coordinator:latest
-  #   container_name: coordinator-sweeper
-  #   command: ["python3", "main_sweeper.py"]
-  #   restart: unless-stopped
-  #   volumes:
-  #     - ./config:/app/config
-  #     - ./logs:/app/logs
-  #   environment:
-  #     - TZ=Asia/Shanghai
-  #   # 资源限制极低,因为它只是个网络请求脚本,不运行浏览器
-  #   deploy:
-  #     resources:
-  #       limits:
-  #         cpus: '0.2'
-  #         memory: 256M
+  visa-sweeper:
+    build: .
+    image: coordinator:latest
+    container_name: coordinator-sweeper
+    command: ["python3", "main_sweeper.py"]
+    restart: unless-stopped
+    volumes:
+      - ./config:/app/config
+      - ./data:/app/data
+      - ./logs:/app/logs
+    environment:
+      - TZ=Asia/Shanghai
+    # 资源限制极低,因为它只是个网络请求脚本,不运行浏览器
+    deploy:
+      resources:
+        limits:
+          cpus: '0.2'
+          memory: 256M
 
 
   registration-bot:
   registration-bot:
     build: .
     build: .
@@ -72,9 +73,10 @@ services:
     container_name: tls-bot-worker
     container_name: tls-bot-worker
     environment:
     environment:
       - CAPSOLVER_KEY=CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A
       - CAPSOLVER_KEY=CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A
-    command: ["python3", "-u" ,"tls_registration_bot.py", "-n", "3", "-m", "10", "-p", "isp_all", "-u", "https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr"]
+    command: ["python3", "-u" ,"tls_registration_bot.py", "-n", "1", "-m", "50", "-p", "isp_all", "-u", "https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr"]
     volumes:
     volumes:
-      - ./config:/app/config     # 挂载代理配置文件
+      - ./config:/app/config
+      - ./data:/app/data
     deploy:
     deploy:
       resources:
       resources:
         limits:
         limits:

+ 73 - 25
tls_registration_bot.py

@@ -16,12 +16,14 @@ from DrissionPage.common import Keys
 from DrissionPage import ChromiumPage, ChromiumOptions
 from DrissionPage import ChromiumPage, ChromiumOptions
 from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
 from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
 from toolkit.vs_cloud_api import VSCloudApi
 from toolkit.vs_cloud_api import VSCloudApi
+from toolkit.proxy_tunnel import ProxyTunnel
 from vs_types import NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
 from vs_types import NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
 from utils.mouse import HumanMouse
 from utils.mouse import HumanMouse
 from utils.keyboard import HumanKeyboard
 from utils.keyboard import HumanKeyboard
 from utils.scroll import HumanScroll
 from utils.scroll import HumanScroll
 from utils.fingerprint_utils import FingerprintGenerator
 from utils.fingerprint_utils import FingerprintGenerator
 
 
+
 def generate_random_account_detail() -> Dict:
 def generate_random_account_detail() -> Dict:
     """基于 randomuser 生成随机账户信息,并保留指定固定字段。"""
     """基于 randomuser 生成随机账户信息,并保留指定固定字段。"""
     today = datetime.today()
     today = datetime.today()
@@ -137,19 +139,34 @@ class TlsRegistrator:
         self.instance_id = uuid.uuid4().hex[:8]
         self.instance_id = uuid.uuid4().hex[:8]
         self.tls_url = tls_url
         self.tls_url = tls_url
         # self.instance_id = '18d389e9'
         # self.instance_id = '18d389e9'
-        self.workspace = os.path.abspath(os.path.join("data", f"reg_session_{self.instance_id}"))
+        self.workspace = os.path.abspath(os.path.join("data/temp_browser_data", f"reg_session_{self.instance_id}"))
         self.page = None
         self.page = None
         self.mouse = None
         self.mouse = None
         self.keyboard = None
         self.keyboard = None
+        
+        # 持有隧道实例
+        self.tunnel = None
 
 
     def _log(self, msg):
     def _log(self, msg):
-        print(f"[TLS-Reg-{self.instance_id}] {msg}")
+        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        print(f"[{now}][TLS-Reg-{self.instance_id}] {msg}")
 
 
     def _get_free_port(self):
     def _get_free_port(self):
         """获取可用端口,防止 DrissionPage 解析日志报错"""
         """获取可用端口,防止 DrissionPage 解析日志报错"""
         with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
         with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
             s.bind(('', 0))
             s.bind(('', 0))
             return s.getsockname()[1]
             return s.getsockname()[1]
+        
+    def save_screenshot(self, name_prefix):
+        try:
+            timestamp = int(time.time())
+            filename = f"{self.instance_id}_{name_prefix}_{timestamp}.jpg"
+            save_path = os.path.join("data", filename)
+            os.makedirs("data", exist_ok=True)
+            self.page.get_screenshot(path=save_path, full_page=False)
+            self._log(f"Screenshot saved to {save_path}")
+        except Exception as e:
+            self._log(f"Failed to save screenshot: {e}")
 
 
     def init_browser(self):
     def init_browser(self):
         """初始化独立、配置好代理的浏览器环境"""
         """初始化独立、配置好代理的浏览器环境"""
@@ -161,16 +178,23 @@ class TlsRegistrator:
         co.set_local_port(port)
         co.set_local_port(port)
         co.set_user_data_path(self.workspace)
         co.set_user_data_path(self.workspace)
         
         
+        chrome_path = os.getenv("CHROME_BIN")
+        if chrome_path and os.path.exists(chrome_path):
+            co.set_paths(browser_path=chrome_path)
+        
         # 2. 代理配置 (支持账号密码)
         # 2. 代理配置 (支持账号密码)
         if self.proxy_config and self.proxy_config.get("ip"):
         if self.proxy_config and self.proxy_config.get("ip"):
             p = self.proxy_config
             p = self.proxy_config
             if p.get("username") and p.get("password"):
             if p.get("username") and p.get("password"):
-                proxy_str = f"{p['username']}:{p['password']}@{p['ip']}:{p['port']}"
+                self.tunnel = ProxyTunnel(p['ip'], p['port'], p['username'], p['password'])
+                local_proxy = self.tunnel.start()
+                self._log(f"Tunnel started at {local_proxy}")
+                co.set_argument(f'--proxy-server={local_proxy}')
             else:
             else:
                 proxy_str = f"{p.get('scheme', 'http')}://{p['ip']}:{p['port']}"
                 proxy_str = f"{p.get('scheme', 'http')}://{p['ip']}:{p['port']}"
-            
-            self._log(f"Setting proxy: {p['ip']}:{p['port']}")
-            co.set_proxy(proxy_str)
+                co.set_argument(f'--proxy-server={proxy_str}')
+        else:
+            self._log("[WARN] No proxy configured!")
 
 
         fingerprint_gen = FingerprintGenerator()
         fingerprint_gen = FingerprintGenerator()
         specific_fp = fingerprint_gen.generate(self.instance_id)
         specific_fp = fingerprint_gen.generate(self.instance_id)
@@ -187,9 +211,10 @@ class TlsRegistrator:
         co.set_argument(f"--fingerprint-brand={specific_fp.get('brand')}")
         co.set_argument(f"--fingerprint-brand={specific_fp.get('brand')}")
         self.page = ChromiumPage(co)
         self.page = ChromiumPage(co)
         self.page.get(self.tls_url)
         self.page.get(self.tls_url)
+        time.sleep(5)
         cf_bypasser = CloudflareBypasser(self.page, log=True)
         cf_bypasser = CloudflareBypasser(self.page, log=True)
-        cf_bypasser.bypass()
-        time.sleep(3) 
+        cf_bypasser.bypass(max_retry=8)
+        time.sleep(3)
         cf_bypasser.handle_waiting_room()
         cf_bypasser.handle_waiting_room()
         
         
         self._log("正在初始化拟人化工具...")
         self._log("正在初始化拟人化工具...")
@@ -340,17 +365,42 @@ class TlsRegistrator:
         def fill_date_field(page, selector, date_str):
         def fill_date_field(page, selector, date_str):
             if not date_str:
             if not date_str:
                 return
                 return
+                
             ele = page.ele(selector)
             ele = page.ele(selector)
             ele.scroll.to_see(center=True)
             ele.scroll.to_see(center=True)
-            ele.click()
-            time.sleep(0.2)
+            
+            js_detect_format = """
+                const parts = new Intl.DateTimeFormat().formatToParts(new Date(2023, 11, 31));
+                let format = [];
+                for (let part of parts) {
+                    if (part.type === 'year') format.push('Y');
+                    if (part.type === 'month') format.push('M');
+                    if (part.type === 'day') format.push('D');
+                }
+                return format;
+            """
+            date_format = page.run_js(js_detect_format)
+            
             year, month, day = date_str.split('-')
             year, month, day = date_str.split('-')
-            page.actions.type(year)
-            page.actions.type(Keys.TAB)
+            date_dict = {
+                'Y': year,
+                'M': month.zfill(2),
+                'D': day.zfill(2)
+            }
+            ele.click()
             time.sleep(0.1)
             time.sleep(0.1)
-            page.actions.type(month)
+            page.actions.type(Keys.LEFT * 3)
             time.sleep(0.1)
             time.sleep(0.1)
-            page.actions.type(day)
+            for i, char in enumerate(date_format):
+                val = date_dict[char]
+                page.actions.type(val)
+                time.sleep(0.1)
+                if char == 'Y':
+                    if i < 2:
+                        page.actions.type(Keys.RIGHT)
+                        time.sleep(0.1)
+                else:
+                    pass
         
         
         email = self.account_detail.get('email')
         email = self.account_detail.get('email')
         password = self.account_detail.get('pwd')
         password = self.account_detail.get('pwd')
@@ -592,6 +642,7 @@ class TlsRegistrator:
         submit_btn = self.page.ele('tag:button@@text():Confirm')
         submit_btn = self.page.ele('tag:button@@text():Confirm')
         submit_btn.scroll.to_see(center=True)
         submit_btn.scroll.to_see(center=True)
         submit_btn.click()
         submit_btn.click()
+        time.sleep(6)
         
         
     def upload_account_to_server(self):
     def upload_account_to_server(self):
         """
         """
@@ -641,9 +692,7 @@ class TlsRegistrator:
             
             
 def register_worker(proxy_config, tls_url, capsolver_key):
 def register_worker(proxy_config, tls_url, capsolver_key):
     """单个注册任务的工作线程函数"""
     """单个注册任务的工作线程函数"""
-    # account_detail = generate_random_account_detail() # 替换为你的实际调用
-    account_detail = {"email": f"test_{random.randint(1000,9999)}@test.com"} 
-    
+    account_detail = generate_random_account_detail()
     bot = None
     bot = None
     try:
     try:
         bot = TlsRegistrator(
         bot = TlsRegistrator(
@@ -661,22 +710,21 @@ def register_worker(proxy_config, tls_url, capsolver_key):
         bot.activate(sent_at=sent_at)
         bot.activate(sent_at=sent_at)
         bot.make_account_useful()
         bot.make_account_useful()
         bot.upload_account_to_server()
         bot.upload_account_to_server()
-        
+        bot.save_screenshot(f'success_{account_detail.get("email")}')
         print(f"[SUCCESS] 账号 {account_detail.get('email')} 注册成功! 使用代理: {proxy_config.get('ip')}")
         print(f"[SUCCESS] 账号 {account_detail.get('email')} 注册成功! 使用代理: {proxy_config.get('ip')}")
         return True
         return True
         
         
     except Exception as e:
     except Exception as e:
         print(f"[ERROR] 注册失败 | 代理 IP: {proxy_config.get('ip')} | 异常信息: {e}")
         print(f"[ERROR] 注册失败 | 代理 IP: {proxy_config.get('ip')} | 异常信息: {e}")
+        bot.save_screenshot('tls_registration_failed')
         return False
         return False
         
         
     finally:
     finally:
-        # ⚠️ 极度重要:无论成功或失败,必须关闭浏览器释放内存!
-        if bot and hasattr(bot, 'close'):
-            try: bot.close()
-            except: pass
-        elif bot and hasattr(bot, 'quit'):
-            try: bot.quit()
-            except: pass
+        if bot:
+            try:
+                bot.cleanup()
+            except:
+                pass
 
 
 
 
 def main():
 def main():