diff --git a/Login/login.py b/Login/login.py new file mode 100644 index 0000000..f5878bd --- /dev/null +++ b/Login/login.py @@ -0,0 +1,141 @@ +import requests +import re +import qrcode +import time +from bs4 import BeautifulSoup +from Login.redirect import StudentSession +from PIL import Image + +class QRCodeLogin: + def __init__(self,url="https://login.b8n.cn/qr/weixin/student/2"): + self.base_url = url + self.session = requests.Session() + self.session.headers.update({ + 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; SM-G981B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Mobile Safari/537.36 MicroMessenger/7.0.10.1580(0x27000A50) Process/tools NetType/WIFI Language/zh_CN ABI/arm64', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', + 'Referer': 'https://wx.qq.com/', + 'sec-ch-ua-platform': 'Android', + 'X-Requested-With': 'XMLHttpRequest', + }) + + def extract_qr_content(self, html): + """从HTML中提取二维码内容""" + soup = BeautifulSoup(html, 'html.parser') + scripts = soup.find_all('script') + script = scripts[2] + + # re匹配(sess、tm、sign) + pattern = r'https?://[^\s"\']+' + urls = re.findall(pattern, str(script))[0] + + sess_pattern = r'[?&]sess=([^&]+)' + tm_pattern = r'[?&]tm=([^&]+)' + sign_pattern = r'[?&]sign=([^&]+)' + + sess_match = re.search(sess_pattern, urls) + tm_match = re.search(tm_pattern, urls) + sign_match = re.search(sign_pattern, urls) + + sess = sess_match.group(1) if sess_match else None + tm = tm_match.group(1) if tm_match else None + sign = sign_match.group(1) if sign_match else None + + # print(f"sess: {sess}") + # print(f"tm: {tm}") + # print(f"sign: {sign}") + params = { + 'sess': sess, + 'tm': tm, + 'sign': sign + } + return params + + def create_qrcode_image(self, params, filename=None): + """创建二维码图片""" + # 构建URL + wx_url = "http://login.b8n.cn/weixin/login/student/2" + query_string = "&".join([f"{k}={v}" for k, v in params.items()]) + qr_url = f"{wx_url}?{query_string}" + + # print(f" 二维码内容: {qr_url}") + + # 生成二维码 + img = qrcode.make(qr_url) + img.save("login_qrcode.png") + + print(f" 二维码已保存: login_qrcode.png") + print(f" 请用微信扫描二维码登录") + image = Image.open('login_qrcode.png') + # 显示二维码 + image.show() + + def poll_login_status(self, max_attempts=20): + """轮询登录状态""" + check_url = f"{self.base_url}?op=checklogin" + + for i in range(max_attempts): + print(f"轮询检查登录状态... ({i + 1}/{max_attempts})") + + try: + response = self.session.get(check_url) + # print("轮询响应" + response.text) + if response.status_code == 200: + try: + data = response.json() + if data.get('status'): + redirect_url = data.get('url') + print(f"登录成功!获取跳转URL: {redirect_url}") + + return True, redirect_url + else: + print(f"等待扫码... ") + except: + print("响应不是JSON格式") + + except Exception as e: + print(f"请求失败: {e}") + + time.sleep(1) # 等待1秒 + + print("二维码已过期") + return False, None + + def run(self): + html = self.session.get(self.base_url).text + params = self.extract_qr_content(html) + + """运行完整流程""" + print("=" * 50) + print("微信扫码登录流程开始") + print("=" * 50) + + # 创建二维码图片 + print("\n1. 生成二维码图片...") + self.create_qrcode_image(params) + + print("\n2. 请用微信扫描二维码...") + + # 轮询登录状态 + print("\n3. 等待扫码确认...") + return self.poll_login_status() + + def external_getCookieAndCourseId(self): + # 爬取并生成二维码 + ok = False + url = "" + for retry in range(3): + print(f"\n\n尝试 #{retry + 1}: \n", end="") + ok, url = scraper.run() + if ok: break + + if ok: + s = StudentSession() + cookie = s.access_login_url(url) + course_id = s.getClassId() + return cookie, course_id + return None, None + +if __name__ == '__main__': + scraper = QRCodeLogin() + scraper.external_getCookieAndCourseId() diff --git a/Login/redirect.py b/Login/redirect.py new file mode 100644 index 0000000..127ae3c --- /dev/null +++ b/Login/redirect.py @@ -0,0 +1,110 @@ +import re + +import requests +from bs4 import BeautifulSoup + + +class StudentSession: + def __init__(self): + self.session = requests.Session() + self.session.headers.update({ + "authority": "bj.k8n.cn", + "scheme": "https", + "path": "/student/uidlogin?", + "sec-ch-ua": '"Chromium";v="142", "Microsoft Edge";v="142", "Not_A Brand";v="99"', + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": '"Windows"', + "dnt": "1", + "upgrade-insecure-requests": "1", + "prefer": "safe", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0", + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + "sec-fetch-site": "cross-site", + "sec-fetch-mode": "navigate", + "sec-fetch-dest": "document", + "referer": "https://login.b8n.cn/", + "accept-encoding": "gzip, deflate, br, zstd", + "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", + "priority": "u=0, i", +}) + self.cookie=None + self.course_id=None + + def access_login_url(self,redirect_url:str)->str: + """ + 这里禁止重定向是因为跳转到了/student页面后只有's'会话的cookie,没有remember_student的cookie + 只有在这个跳转页里才发现了remember_student,有了remember_student,就可以用cookie去访问其他页面了 + """ + # url处理 + url = "https://bj.k8n.cn/student/uidlogin?" + redirect_url.split('?')[1] + + response=self.session.get(url, allow_redirects=False) + print(f"状态码: {response.status_code}") + # print(f"最终URL: {response.url}") + # print(f"响应长度: {len(response.text)}") + # print(f"响应预览: {response.text}") + # 保存cookies供后续使用 + cookies = self.session.cookies.get_dict() + #去除掉没用的cookie + cookies.pop("s") + + # 使用列表推导式生成键值对字符串 + cookie= [f"{key}={value}" for key, value in cookies.items()][0] + + print(f"获取的Cookies: {cookie}") + self.cookie=cookie + return cookie + + def getClassId(self)->str: + """访问其他页面""" + url = "http://bj.k8n.cn/student" + resp=self.session.get(url) + print(f"学生页面状态码:{resp.status_code}") + # print(f"学生页面预览页:{resp.text}") + + soup = BeautifulSoup(resp.text, 'html.parser') + div_element = soup.find('div', class_='card mb-3 course') + course_id = div_element.get('course_id') + print(f"ClassID: {course_id}") + self.course_id=course_id + return course_id + + def other(self): + """可以get其他子网获取信息: + 比如获取名称信息和uid或者学号之类在cookie前面加注释之类的 + 又或者有其他大佬出手重构config.json,第一次看到这个项目的时候还是单人签到的, + 现在被很多大佬贡献改成多人了,对于这个多人的签到我也有一些想法就是加一个user类, + json结构像这样 + “user”:[ + { + "username":"zhangsan", + "uid":"123", + "course_id":"123", + "cookie":"remember....." + "scheduletime": "01:30", + "lat": "123", + "lng": "342", + "acc": "12", + "time": 0, + }, + { + "username":"zhangsan", + "uid":"123", + "course_id":"123", + "cookie":"remember....." + "scheduletime": "01:30", + "lat": "123", + "lng": "342", + "acc": "12", + "time": 0, + }, + ] + 实现多人不同班不同地点不同时间的签到 + 如果我说的有什么问题还请大佬指教>.<! + """ + pass + +# 立即执行 +if __name__ == '__main__': + s = StudentSession() + s.access_login_url("http://bj.k8n.cn/student?uid=2789785&tm=1764778952&sign=acf6e7872ea5036bf66c94d05bb435c4&option=&") \ No newline at end of file