目录
##1. 账号密码登录
##2. 二维码登录
因为要登录学某通查看一些东西,看着登录界面心血来潮,于是乎简单做一下逆向分析。此教程仅为学习记录。
1. 账号密码登录
先随便输入一下账号密码,调用了这个接口
可以看到账号密码都进行了base64编码,看一下函数调用栈。
可以看到在发送XHR请求前调用了两个方法
loginByPhoneAndPwd
loginByPhoneAndPwdSubmit
跟进loginByPhoneAndPwdSubmit方法,打个断点。很不巧的就看到了输入的明文以及调用的加密函数,
跳入加密函数中
可以看到使用的是一个AES-CBC加密,并且iv向量与key相等都为
u2oh6Vu^HWe4_AES
function encryptByAES(message, key) {
let CBCOptions = {
iv: CryptoJS.enc.Utf8.parse(key),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
};
let aeskey = CryptoJS.enc.Utf8.parse(key);
let secretData = CryptoJS.enc.Utf8.parse(message);
let encrypted = CryptoJS.AES.encrypt(
secretData,
aeskey,
CBCOptions
);
return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
}
加密之后再对密文进行base64,由于账号密码都是同种加密方式,因此在了解加密方式和密钥之后,就可以自己封装实现了。
2. 二维码登录
没有看到请求返回二维码的XHR,所以登录二维码是在生成html的时候直接嵌入的。
并且二维码是由uuid来进行获取。
扫描一下二维码看看提交的参数
这里用到的uuid和enc参数,这里我们不需要单独去分析uuid和enc是怎么生成的,因为这个是返回html的时候就写在里面的,所以只需要爬取页面,获得这两个参数就行了。
一直看到浏览器不断的在请求https://passport2.chaoxing.com/getauthstatus/v2来获取登录结果
同时也是根据uuid和enc来进行判断
被扫描之后判断用户是否确认登录再进行跳转。
分别使用php、python、node.js对上面的流程进行代码封装之后的运行结果。
下面直接贴出代码
php
<?php
mb_internal_encoding('UTF-8');
mb_http_output('UTF-8');
mb_regex_encoding('UTF-8');
date_default_timezone_set('Asia/Shanghai');
if (php_sapi_name() === 'cli') {
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
@exec('chcp 65001 >nul 2>&1');
}
}
function encryptByAES($message, $key) {
$key = mb_substr($key, 0, 16, 'UTF-8');
$iv = $key;
$encrypted = openssl_encrypt(
$message,
'AES-128-CBC',
$key,
OPENSSL_RAW_DATA,
$iv
);
return base64_encode($encrypted);
}
function loginWithPassword($username, $password) {
$url = 'https://passport2.chaoxing.com/fanyalogin';
$encryptKey = "u2oh6Vu^HWe4_AES";
$encryptedUsername = encryptByAES($username, $encryptKey);
$encryptedPassword = encryptByAES($password, $encryptKey);
$postData = http_build_query([
'fid' => '-1',
'uname' => $encryptedUsername,
'password' => $encryptedPassword,
'refer' => 'http%3A%2F%2Fi.mooc.chaoxing.com%2Fspace%2Findex%3Fnull',
't' => 'true',
'forbidotherlogin' => '0',
'validate' => '',
'doubleFactorLogin' => '0',
'independentId' => '0',
'independentNameId' => '0'
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postData,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_ENCODING => '',
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Content-Type: application/x-www-form-urlencoded',
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0',
'Accept: application/json, text/plain, */*',
'Origin: https://passport2.chaoxing.com',
'Referer: https://passport2.chaoxing.com/login'
]
]);
$response = curl_exec($ch);
if ($response === false) {
curl_close($ch);
return false;
}
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
curl_close($ch);
$result = json_decode($body, true);
if ($result === null) {
return false;
}
$cookies = [];
if (preg_match_all('/set-cookie:\s*([^\r\n]+)/i', $headers, $matches)) {
foreach ($matches[1] as $cookieLine) {
// 提取 cookie 键值对(第一个分号之前的部分)
$cookieParts = explode(';', $cookieLine, 2);
$cookie = trim($cookieParts[0]);
if (!empty($cookie)) {
$cookies[] = $cookie;
}
}
}
return [
'status' => $result,
'cookies' => $cookies,
'cookieString' => implode('; ', $cookies)
];
}
/**
* 检查二维码扫描状态
*/
function checkQrCodeStatus($uuid, $enc) {
$url = 'https://passport2.chaoxing.com/getauthstatus/v2';
$postData = http_build_query([
'enc' => $enc,
'uuid' => $uuid,
'doubleFactorLogin' => '0'
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postData,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_ENCODING => '',
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Content-Type: application/x-www-form-urlencoded',
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0',
'Accept: application/json, text/plain, */*',
'Origin: https://passport2.chaoxing.com',
'Referer: https://passport2.chaoxing.com/login'
]
]);
$response = curl_exec($ch);
if ($response === false) {
curl_close($ch);
return false;
}
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
curl_close($ch);
$result = json_decode($body, true);
if ($result === null) {
return false;
}
$cookies = [];
if (preg_match_all('/set-cookie:\s*([^\r\n]+)/i', $headers, $matches)) {
foreach ($matches[1] as $cookieLine) {
$cookieParts = explode(';', $cookieLine, 2);
$cookie = trim($cookieParts[0]);
if (!empty($cookie)) {
$cookies[] = $cookie;
}
}
}
return [
'status' => $result,
'cookies' => $cookies,
'cookieString' => implode('; ', $cookies)
];
}
function getCourseList($cookieString) {
$url = 'https://mooc1-1.chaoxing.com/mooc-ans/visit/courselistdata';
$postData = http_build_query([
'courseType' => '1',
'courseFolderId' => '0',
'baseEducation' => '0',
'superstarClass' => '',
'courseFolderSize' => '1'
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postData,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Content-Type: application/x-www-form-urlencoded',
'Cookie: ' . $cookieString,
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0',
'Accept: application/json, text/plain, */*',
'Origin: https://mooc1-1.chaoxing.com',
'Referer: https://mooc1-1.chaoxing.com/'
]
]);
$response = curl_exec($ch);
if ($response === false) {
curl_close($ch);
return false;
}
curl_close($ch);
// 尝试解析JSON响应,如果失败则返回原始HTML
$result = json_decode($response, true);
if ($result !== null && json_last_error() === JSON_ERROR_NONE) {
return $result;
} else {
// 返回HTML字符串
return $response;
}
}
/**
* 格式化输出课程列表
* @param string $html HTML内容
*/
function formatCourseList($html) {
libxml_use_internal_errors(true);
$dom = new DOMDocument('1.0', 'UTF-8');
// 确保HTML使用UTF-8编码声明
if (!preg_match('/<meta[^>]+charset/i', $html) && !preg_match('/<\?xml[^>]+encoding/i', $html)) {
$html = '<?xml encoding="UTF-8"?>' . $html;
}
@$dom->loadHTML($html);
libxml_clear_errors();
$xpath = new DOMXPath($dom);
// 查找所有课程
$courses = $xpath->query('//li[contains(@class, "course")]');
if ($courses->length == 0) {
echo "未找到课程\n";
return;
}
echo "\n" . str_repeat("=", 80) . "\n";
echo "课程列表(共 " . $courses->length . " 门课程)\n";
echo str_repeat("=", 80) . "\n\n";
$index = 1;
foreach ($courses as $course) {
// 获取课程ID
$courseId = $course->getAttribute('courseId');
$clazzId = $course->getAttribute('clazzId');
$personId = $course->getAttribute('personId');
// 获取课程名称
$courseNameNodes = $xpath->query('.//span[contains(@class, "course-name")]', $course);
$courseName = $courseNameNodes->length > 0 ? trim($courseNameNodes->item(0)->textContent) : '未知课程';
// 获取课程链接
$linkNodes = $xpath->query('.//a[contains(@class, "color1")]', $course);
$courseUrl = $linkNodes->length > 0 ? $linkNodes->item(0)->getAttribute('href') : '';
// 获取学校/机构
$schoolNodes = $xpath->query('.//p[contains(@class, "margint10")]', $course);
$school = $schoolNodes->length > 0 ? trim($schoolNodes->item(0)->textContent) : '';
// 获取教师 - 查找所有包含title属性的p标签
$teacherNodes = $xpath->query('.//p[@title and contains(@class, "line2")]', $course);
$teacher = '';
if ($teacherNodes->length > 0) {
// 优先使用title属性
foreach ($teacherNodes as $node) {
$title = trim($node->getAttribute('title'));
$text = trim($node->textContent);
if (!empty($title) && $title !== $school && !preg_match('/开课时间|课程已结束/', $title)) {
$teacher = $title;
break;
} elseif (!empty($text) && $text !== $school && !preg_match('/开课时间|课程已结束/', $text)) {
$teacher = $text;
break;
}
}
// 如果还没找到,尝试查找所有line2类的p标签
if (empty($teacher)) {
$allLine2Nodes = $xpath->query('.//p[contains(@class, "line2")]', $course);
foreach ($allLine2Nodes as $node) {
$text = trim($node->textContent);
if (!empty($text) && $text !== $school && !preg_match('/开课时间|课程已结束|^\s*$/', $text)) {
$teacher = $text;
break;
}
}
}
}
// 获取开课时间
$timeNodes = $xpath->query('.//p[contains(text(), "开课时间")]', $course);
$courseTime = $timeNodes->length > 0 ? trim($timeNodes->item(0)->textContent) : '';
// 检查是否已结束
$isEnded = $xpath->query('.//a[contains(@class, "not-open-tip")]', $course)->length > 0;
// 格式化输出
echo sprintf("%d. %s\n", $index, $courseName);
if (!empty($school)) {
echo " 学校/机构: {$school}\n";
}
if (!empty($teacher)) {
echo " 授课教师: {$teacher}\n";
}
if (!empty($courseTime)) {
echo " {$courseTime}\n";
}
echo " 课程ID: {$courseId} | 班级ID: {$clazzId} | 人员ID: {$personId}\n";
if ($isEnded) {
echo " 状态: 课程已结束\n";
}
if (!empty($courseUrl)) {
echo " 链接: {$courseUrl}\n";
}
echo "\n" . str_repeat("-", 80) . "\n\n";
$index++;
}
}
// 选择登录方式
$loginMethod = '';
if (php_sapi_name() === 'cli') {
// 命令行模式:可以选择登录方式
// 可以通过命令行参数指定
if (isset($argv[1]) && $argv[1] === 'password') {
$loginMethod = 'password';
} elseif (isset($argv[1]) && $argv[1] === 'qr') {
$loginMethod = 'qr';
} else {
// 如果没有指定,提示用户选择
echo "\n请选择登录方式:\n";
echo "1. 二维码登录(默认)\n";
echo "2. 账号密码登录\n";
echo "请输入选项 (1 或 2,直接回车默认选择1): ";
$input = trim(fgets(STDIN));
if ($input === '2' || strtolower($input) === 'password') {
$loginMethod = 'password';
} else {
$loginMethod = 'qr';
}
}
} else {
// Web模式默认使用二维码
$loginMethod = 'qr';
}
// 如果是账号密码登录
if ($loginMethod === 'password') {
// 从命令行参数或环境变量获取账号密码
$username = '';
$password = '';
if (php_sapi_name() === 'cli') {
if (isset($argv[2])) {
$username = $argv[2];
}
if (isset($argv[3])) {
$password = $argv[3];
}
// 如果没有提供,提示输入
if (empty($username)) {
echo "请输入用户名: ";
$username = trim(fgets(STDIN));
}
if (empty($password)) {
echo "请输入密码: ";
// 隐藏密码输入(Windows下可能不支持)
$password = trim(fgets(STDIN));
}
}
if (empty($username) || empty($password)) {
echo "错误:用户名和密码不能为空\n";
echo "使用方法: php xxt.php password <用户名> <密码>\n";
exit(1);
}
echo "正在使用账号密码登录...\n";
$loginResult = loginWithPassword($username, $password);
if ($loginResult === false) {
echo "登录请求失败\n";
exit(1);
}
$statusData = $loginResult['status'];
$statusStr = json_encode($statusData, JSON_UNESCAPED_UNICODE);
echo "[" . date('Y-m-d H:i:s') . "] 登录响应: {$statusStr}\n";
// 检查登录是否成功
if (isset($statusData['status']) && $statusData['status'] === true) {
echo "\n✓ 登录成功!\n";
// 提取 cookie 并获取课程列表
if (!empty($loginResult['cookieString'])) {
echo "\n正在获取课程列表...\n";
$courseList = getCourseList($loginResult['cookieString']);
if ($courseList !== false) {
// 检查返回的是HTML还是JSON
if (is_string($courseList) && (stripos($courseList, '<html') !== false || stripos($courseList, '<li') !== false)) {
// 如果是HTML格式,使用格式化函数
formatCourseList($courseList);
} else {
// 如果是JSON格式
echo "\n课程列表数据:\n";
echo json_encode($courseList, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
}
} else {
echo "获取课程列表失败\n";
}
} else {
echo "警告:未获取到 Cookie\n";
}
} else {
$errorMsg = isset($statusData['msg2']) ? $statusData['msg2'] : (isset($statusData['msg']) ? $statusData['msg'] : '登录失败');
echo "\n✗ 登录失败: {$errorMsg}\n";
exit(1);
}
exit(0);
}
// 二维码登录方式
$url = 'https://passport2.chaoxing.com/login';
$headers = [
'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',
'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',
'Connection: keep-alive',
'Host: passport2.chaoxing.com',
'Sec-Fetch-Dest: document',
'Sec-Fetch-Mode: navigate',
'Sec-Fetch-Site: none',
'Sec-Fetch-User: ?1',
'Upgrade-Insecure-Requests: 1',
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0',
'sec-ch-ua: "Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"',
'sec-ch-ua-mobile: ?0',
'sec-ch-ua-platform: "Windows"'
];
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_TIMEOUT => 10
]);
$html = curl_exec($ch);
if ($html === false) {
exit('cURL Error: ' . curl_error($ch));
}
curl_close($ch);
$uuid = $enc = null;
libxml_use_internal_errors(true);
$dom = new DOMDocument('1.0', 'UTF-8');
@$dom->loadHTML($html);
libxml_clear_errors();
$xpath = new DOMXPath($dom);
$uuidNodes = $xpath->query('//input[@id="uuid"]');
if ($uuidNodes->length > 0) {
$uuid = $uuidNodes->item(0)->getAttribute('value');
}
$encNodes = $xpath->query('//input[@id="enc"]');
if ($encNodes->length > 0) {
$enc = $encNodes->item(0)->getAttribute('value');
}
if ($uuid) {
$qrCodeUrl = "https://passport2.chaoxing.com/createqr?uuid={$uuid}&fid=-1";
$ch = curl_init($qrCodeUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0',
'Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8'
]
]);
$qrImage = curl_exec($ch);
if ($qrImage === false) {
echo "获取二维码失败: " . curl_error($ch) . "\n";
} else {
if (php_sapi_name() === 'cli') {
echo "uuid: {$uuid}\n";
echo "enc: " . ($enc ?: '未找到') . "\n";
echo "二维码URL: {$qrCodeUrl}\n\n";
$base64 = base64_encode($qrImage);
echo "\033]1337;File=inline=1;width=auto:" . $base64 . "\a\n";
echo "\033_Ga=T,f=100,s=200,v=200;" . $base64 . "\033\\\n";
echo "\n提示:如果二维码未显示,请复制上面的URL到浏览器查看\n";
if ($enc) {
echo "\n开始检查二维码扫描状态...\n";
echo "按 Ctrl+C 可以停止检查\n\n";
$maxAttempts = 300;
$attempt = 0;
while ($attempt < $maxAttempts) {
$result = checkQrCodeStatus($uuid, $enc);
if ($result === false) {
if ($attempt % 10 == 0) {
echo "[" . date('Y-m-d H:i:s') . "] 检查失败,重试中...\n";
}
} else {
$statusData = $result['status'];
if (isset($statusData['status']) && $statusData['status'] === true) {
$statusStr = json_encode($statusData, JSON_UNESCAPED_UNICODE);
echo "[" . date('Y-m-d H:i:s') . "] 状态: {$statusStr}\n";
echo "\n✓ 二维码已被扫描,登录成功!\n";
if (!empty($result['cookieString'])) {
echo "\n正在获取课程列表...\n";
$courseList = getCourseList($result['cookieString']);
if ($courseList !== false) {
if (is_string($courseList) && (stripos($courseList, '<html') !== false || stripos($courseList, '<li') !== false)) {
formatCourseList($courseList);
} else {
echo "\n课程列表数据:\n";
echo json_encode($courseList, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
}
} else {
echo "获取课程列表失败\n";
}
} else {
echo "警告:未获取到 Cookie\n";
}
break;
} else {
if ($attempt % 5 == 0) {
$statusStr = json_encode($statusData, JSON_UNESCAPED_UNICODE);
echo "[" . date('Y-m-d H:i:s') . "] 等待扫描... 状态: {$statusStr}\n";
}
}
}
$attempt++;
sleep(1);
}
if ($attempt >= $maxAttempts) {
echo "\n检查超时,请重新运行程序\n";
}
}
} else {
// Web模式:直接输出图片
header('Content-Type: image/png');
echo $qrImage;
exit; // 输出图片后退出
}
}
curl_close($ch);
} else {
echo "uuid: 未找到,无法生成二维码\n";
echo "enc: " . ($enc ?: '未找到') . "\n";
}
?>
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
import re
import json
import time
import sys
import os
from datetime import datetime
from bs4 import BeautifulSoup
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64
import pytz
tz = pytz.timezone('Asia/Shanghai')
if sys.platform == 'win32':
try:
os.system('chcp 65001 >nul 2>&1')
except:
pass
def encrypt_by_aes(message, key):
key = key[:16].encode('utf-8')
iv = key
cipher = AES.new(key, AES.MODE_CBC, iv)
padded_data = pad(message.encode('utf-8'), AES.block_size)
encrypted = cipher.encrypt(padded_data)
return base64.b64encode(encrypted).decode('utf-8')
def login_with_password(username, password):
url = 'https://passport2.chaoxing.com/fanyalogin'
encrypt_key = "u2oh6Vu^HWe4_AES"
encrypted_username = encrypt_by_aes(username, encrypt_key)
encrypted_password = encrypt_by_aes(password, encrypt_key)
data = {
'fid': '-1',
'uname': encrypted_username,
'password': encrypted_password,
'refer': 'http%3A%2F%2Fi.mooc.chaoxing.com%2Fspace%2Findex%3Fnull',
't': 'true',
'forbidotherlogin': '0',
'validate': '',
'doubleFactorLogin': '0',
'independentId': '0',
'independentNameId': '0'
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0',
'Accept': 'application/json, text/plain, */*',
'Origin': 'https://passport2.chaoxing.com',
'Referer': 'https://passport2.chaoxing.com/login'
}
try:
response = requests.post(url, data=data, headers=headers, verify=False, timeout=10)
result = response.json()
cookies = []
for cookie in response.cookies:
cookies.append(f"{cookie.name}={cookie.value}")
return {
'status': result,
'cookies': cookies,
'cookieString': '; '.join(cookies)
}
except Exception as e:
print(f"登录请求失败: {e}")
return None
def check_qr_code_status(uuid, enc):
"""
检查二维码扫描状态
"""
url = 'https://passport2.chaoxing.com/getauthstatus/v2'
data = {
'enc': enc,
'uuid': uuid,
'doubleFactorLogin': '0'
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0',
'Accept': 'application/json, text/plain, */*',
'Origin': 'https://passport2.chaoxing.com',
'Referer': 'https://passport2.chaoxing.com/login'
}
try:
response = requests.post(url, data=data, headers=headers, verify=False, timeout=10)
result = response.json()
cookies = []
for cookie in response.cookies:
cookies.append(f"{cookie.name}={cookie.value}")
return {
'status': result,
'cookies': cookies,
'cookieString': '; '.join(cookies)
}
except Exception as e:
return None
def get_course_list(cookie_string):
"""
获取课程列表
"""
url = 'https://mooc1-1.chaoxing.com/mooc-ans/visit/courselistdata'
data = {
'courseType': '1',
'courseFolderId': '0',
'baseEducation': '0',
'superstarClass': '',
'courseFolderSize': '1'
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': cookie_string,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0',
'Accept': 'application/json, text/plain, */*',
'Origin': 'https://mooc1-1.chaoxing.com',
'Referer': 'https://mooc1-1.chaoxing.com/'
}
try:
response = requests.post(url, data=data, headers=headers, verify=False, timeout=10)
return response.text
except Exception as e:
return None
def format_course_list(html):
soup = BeautifulSoup(html, 'html.parser')
courses = soup.find_all('li', class_='course')
if not courses:
print("未找到课程")
return
print("\n" + "=" * 80)
print(f"课程列表(共 {len(courses)} 门课程)")
print("=" * 80 + "\n")
for index, course in enumerate(courses, 1):
course_id = course.get('courseid', '')
clazz_id = course.get('clazzid', '')
person_id = course.get('personid', '')
course_name_elem = course.find('span', class_='course-name')
course_name = course_name_elem.get('title', course_name_elem.text.strip()) if course_name_elem else '未知课程'
link_elem = course.find('a', class_='color1')
course_url = link_elem.get('href', '') if link_elem else ''
school_elem = course.find('p', class_='margint10')
school = school_elem.text.strip() if school_elem else ''
teacher = ''
line2_elems = course.find_all('p', class_='line2')
for elem in line2_elems:
text = elem.text.strip()
title = elem.get('title', '')
if text and text != school and '开课时间' not in text and '课程已结束' not in text:
teacher = title if title else text
break
time_elem = course.find('p', string=re.compile('开课时间'))
course_time = time_elem.text.strip() if time_elem else ''
# 检查是否已结束
is_ended = course.find('a', class_='not-open-tip') is not None
# 格式化输出
print(f"{index}. {course_name}")
if school:
print(f" 学校/机构: {school}")
if teacher:
print(f" 授课教师: {teacher}")
if course_time:
print(f" {course_time}")
print(f" 课程ID: {course_id} | 班级ID: {clazz_id} | 人员ID: {person_id}")
if is_ended:
print(" 状态: 课程已结束")
if course_url:
print(f" 链接: {course_url}")
print("\n" + "-" * 80 + "\n")
def main():
login_method = ''
if len(sys.argv) > 1:
if sys.argv[1] == 'password':
login_method = 'password'
elif sys.argv[1] == 'qr':
login_method = 'qr'
if not login_method:
print("\n请选择登录方式:")
print("1. 二维码登录(默认)")
print("2. 账号密码登录")
choice = input("请输入选项 (1 或 2,直接回车默认选择1): ").strip()
if choice == '2' or choice.lower() == 'password':
login_method = 'password'
else:
login_method = 'qr'
# 账号密码登录
if login_method == 'password':
username = ''
password = ''
if len(sys.argv) > 2:
username = sys.argv[2]
if len(sys.argv) > 3:
password = sys.argv[3]
if not username:
username = input("请输入用户名: ").strip()
if not password:
password = input("请输入密码: ").strip()
if not username or not password:
print("错误:用户名和密码不能为空")
print("使用方法: python xxt.py password <用户名> <密码>")
sys.exit(1)
print("正在使用账号密码登录...")
login_result = login_with_password(username, password)
if not login_result:
print("登录请求失败")
sys.exit(1)
status_data = login_result['status']
status_str = json.dumps(status_data, ensure_ascii=False)
current_time = datetime.now(tz).strftime('%Y-%m-%d %H:%M:%S')
print(f"[{current_time}] 登录响应: {status_str}")
if status_data.get('status') == True:
print("\n✓ 登录成功!")
if login_result.get('cookieString'):
print("\n正在获取课程列表...")
course_list = get_course_list(login_result['cookieString'])
if course_list:
if '<li' in course_list or '<html' in course_list.lower():
format_course_list(course_list)
else:
print("\n课程列表数据:")
print(json.dumps(json.loads(course_list), ensure_ascii=False, indent=2))
else:
print("获取课程列表失败")
else:
print("警告:未获取到 Cookie")
else:
error_msg = status_data.get('msg2') or status_data.get('msg', '登录失败')
print(f"\n✗ 登录失败: {error_msg}")
sys.exit(1)
sys.exit(0)
# 二维码登录
url = 'https://passport2.chaoxing.com/login'
headers = {
'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',
'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',
'Connection': 'keep-alive',
'Host': 'passport2.chaoxing.com',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-User': '?1',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0',
'sec-ch-ua': '"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"'
}
try:
response = requests.get(url, headers=headers, verify=False, timeout=10)
html = response.text
# 提取 uuid 和 enc
soup = BeautifulSoup(html, 'html.parser')
uuid_elem = soup.find('input', {'id': 'uuid'})
enc_elem = soup.find('input', {'id': 'enc'})
uuid = uuid_elem.get('value') if uuid_elem else None
enc = enc_elem.get('value') if enc_elem else None
if uuid:
qr_code_url = f"https://passport2.chaoxing.com/createqr?uuid={uuid}&fid=-1"
# 获取二维码图片
qr_response = requests.get(qr_code_url, verify=False, timeout=10)
if qr_response.status_code == 200:
print(f"uuid: {uuid}")
print(f"enc: {enc if enc else '未找到'}")
print(f"二维码URL: {qr_code_url}\n")
# 保存二维码图片
qr_filename = f'qrcode_{uuid}.png'
with open(qr_filename, 'wb') as f:
f.write(qr_response.content)
print(f"二维码已保存为: {qr_filename}\n")
print("提示:请使用学习通APP扫描二维码\n")
# 轮询检查二维码扫描状态
if enc:
print("开始检查二维码扫描状态...")
print("按 Ctrl+C 可以停止检查\n")
max_attempts = 300
attempt = 0
while attempt < max_attempts:
result = check_qr_code_status(uuid, enc)
if not result:
if attempt % 10 == 0:
current_time = datetime.now(tz).strftime('%Y-%m-%d %H:%M:%S')
print(f"[{current_time}] 检查失败,重试中...")
else:
status_data = result['status']
if status_data.get('status') == True:
status_str = json.dumps(status_data, ensure_ascii=False)
current_time = datetime.now(tz).strftime('%Y-%m-%d %H:%M:%S')
print(f"[{current_time}] 状态: {status_str}")
print("\n✓ 二维码已被扫描,登录成功!")
if result.get('cookieString'):
print("\n正在获取课程列表...")
course_list = get_course_list(result['cookieString'])
if course_list:
if '<li' in course_list or '<html' in course_list.lower():
format_course_list(course_list)
else:
print("\n课程列表数据:")
print(json.dumps(json.loads(course_list), ensure_ascii=False, indent=2))
else:
print("获取课程列表失败")
else:
print("警告:未获取到 Cookie")
break
else:
if attempt % 5 == 0:
status_str = json.dumps(status_data, ensure_ascii=False)
current_time = datetime.now(tz).strftime('%Y-%m-%d %H:%M:%S')
print(f"[{current_time}] 等待扫描... 状态: {status_str}")
attempt += 1
time.sleep(1)
if attempt >= max_attempts:
print("\n检查超时,请重新运行程序")
else:
print("获取二维码失败")
else:
print("uuid: 未找到,无法生成二维码")
print(f"enc: {enc if enc else '未找到'}")
except Exception as e:
print(f"错误: {e}")
sys.exit(1)
if __name__ == '__main__':
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
main()
版权属于:龙辉博客
本文链接:https://blog.eirds.cn/470.html
如果没有特别声明则为本博原创。转载时须注明出处及本声明!








