前言
选课系统改了之后可以宣布进入大脚本时代了,随机发放你让我抢牛魔。
本篇就是介绍一下CAU选课的脚本,代码和使用。 用cpp实现的。 不保证没有bug,如有问题请指出,因为本人也是第一次实现用cpp搞的脚本。(Windows平台)
酌情使用脚本,有可能会被封IP甚至封号。
简单介绍一下是干什么的,针对cau的选课,只能用于本学期计划选课和公选课选课-方案内的抢课脚本,也就是置入的必修课、专业选修课、体育课、英语课、通识课、美育课的抢课脚本,当然,抢其他的类别里面的原理差别不大,想实现可以自己搞,问问ai,简单学一下api和原理就可以弄
一、源文件以及用法
核心就用到了两个文件一个是源文件gc.cpp、一个是配置文件config.txt
至于环境Windows下只需要有g++编译器即可,HTTP请求用的是Windows自带的API,不需要安装。
注意: ./gc.exe --list只能用于本学期计划选课部分,因为公选课实在是太多了,就没弄,需要手动配置,下面有配置方案
先配置好config.txt才能正常编译运行抢课! 先看下面关于config.txt的描述,先配置Cookie,当然gc.cpp是什么不重要,拷贝下来即可
gc.cpp
cpp
/**
* CAU抢课脚本
*
* 编译: g++ -o gc.exe gc.cpp -lwinhttp -std=c++20 -O2
* 用法:
* ./gc.exe --list 列出可选课程,选后自动写入config.txt,先执行这行再执行下一行 只能用于本学期计划选课! 公选课请手动配置config.txt
* ./gc.exe 直接抢课(参数从config.txt读取)
*/
#include <windows.h>
#include <winhttp.h>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <atomic>
#include <chrono>
#include <mutex>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <algorithm>
#pragma comment(lib, "winhttp.lib")
std::atomic<bool> g_running{true};
std::atomic<int> g_totalAttempts{0};
std::atomic<int> g_failCount{0};
std::mutex g_printMutex;
auto g_startTime = std::chrono::steady_clock::now();
//console 参数
namespace console {
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
enum Color {
RED = FOREGROUND_RED | FOREGROUND_INTENSITY,
GREEN = FOREGROUND_GREEN | FOREGROUND_INTENSITY,
YELLOW= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY,
CYAN = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY,
WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
GRAY = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
};
void setColor(Color c) { SetConsoleTextAttribute(hConsole, static_cast<WORD>(c)); }
void resetColor() { SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); }
void init() {
SetConsoleOutputCP(CP_UTF8);
SetConsoleCtrlHandler([](DWORD) -> BOOL { g_running = false; return TRUE; }, TRUE);
}
void print(const std::string& msg, Color c = WHITE) {
std::lock_guard<std::mutex> lock(g_printMutex);
setColor(c);
std::cout << msg;
resetColor();
}
std::string timestamp() {
auto now = std::chrono::system_clock::now();
auto t = std::chrono::system_clock::to_time_t(now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
std::tm tm;
localtime_s(&tm, &t);
char buf[32];
snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%03lld", tm.tm_hour, tm.tm_min, tm.tm_sec, ms.count());
return buf;
}
void info(const std::string& msg) { print("[" + timestamp() + "] " + msg + "\n", CYAN); }
void success(const std::string& msg) { print("[" + timestamp() + "] [OK] " + msg + "\n", GREEN); }
void error(const std::string& msg) { print("[" + timestamp() + "] [FAIL] " + msg + "\n", RED); }
void warn(const std::string& msg) { print("[" + timestamp() + "] [WARN] " + msg + "\n", YELLOW); }
void log(const std::string& msg) { print("[" + timestamp() + "] " + msg + "\n", GRAY); }
}
//字符串处理函数
namespace strutil {
std::string trim(const std::string& s) {
auto start = s.find_first_not_of(" \t\r\n");
if (start == std::string::npos) return "";
return s.substr(start, s.find_last_not_of(" \t\r\n") - start + 1);
}
std::wstring toWide(const std::string& s) {
if (s.empty()) return L"";
int len = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, nullptr, 0);
std::wstring result(len, L'\0');
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, &result[0], len);
result.resize(len - 1);
return result;
}
bool contains(const std::string& haystack, const std::string& needle) {
return haystack.find(needle) != std::string::npos;
}
std::string jsonValue(const std::string& json, const std::string& key) {
std::string search = "\"" + key + "\"";
auto pos = json.find(search);
if (pos == std::string::npos) return "";
pos = json.find(':', pos + search.size());
if (pos == std::string::npos) return "";
pos++;
while (pos < json.size() && (json[pos] == ' ' || json[pos] == '\t')) pos++;
if (pos >= json.size()) return "";
if (json[pos] == '"') {
pos++;
auto end = json.find('"', pos);
return end == std::string::npos ? "" : json.substr(pos, end - pos);
}
auto end = json.find_first_of(",}\n\r", pos);
return end == std::string::npos ? json.substr(pos) : json.substr(pos, end - pos);
}
}
// config
struct Config {
std::string kcid;
std::string jx0404id;
std::string jsessionid1;
std::string serverid = "125";
std::string jsessionid2;
int threadCount = 8;
int retryIntervalMs = 100;
int mode = 0; // 0 = 计划选课 1 = 公选课
std::string cookieString() const {
std::string c = "JSESSIONID=" + jsessionid1;
if (!serverid.empty()) c += "; SERVERID=" + serverid;
if (!jsessionid2.empty()) c += "; JSESSIONID=" + jsessionid2;
return c;
}
//存入
bool loadFromFile(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) return false;
std::string line;
while (std::getline(file, line)) {
auto cp = line.find('#');
if (cp != std::string::npos) line = line.substr(0, cp);
auto eq = line.find('=');
if (eq == std::string::npos) continue;
std::string key = strutil::trim(line.substr(0, eq));
std::string val = strutil::trim(line.substr(eq + 1));
if (key == "kcid") kcid = val;
else if (key == "jx0404id") jx0404id = val;
else if (key == "jsessionid1") jsessionid1 = val;
else if (key == "jsessionid2") jsessionid2 = val;
else if (key == "serverid") serverid = val;
else if (key == "thread_count") threadCount = std::stoi(val);
else if (key == "retry_interval_ms") retryIntervalMs = std::stoi(val);
else if (key == "mode") mode = std::stoi(val);
}
return true;
}
bool validate() const {
if (kcid.empty()) { console::error("config.txt 中缺少 kcid"); return false; }
if (jx0404id.empty()) { console::error("config.txt 中缺少 jx0404id"); return false; }
if (jsessionid1.empty()) { console::error("config.txt 中缺少 jsessionid1"); return false; }
return true;
}
void print() const {
console::info("模式: " + std::string(mode == 0 ? "计划选课" : "公选课"));
console::info("课程ID: " + kcid + " 教学班ID: " + jx0404id);
console::info("线程数: " + std::to_string(threadCount)
+ " 间隔: " + std::to_string(retryIntervalMs) + "ms");
console::info("Cookie: " + jsessionid1.substr(0, 16) + "...");
}
};
//HTTP 请求
class HttpClient {
public:
struct Response {
int statusCode = 0;
std::string body;
};
HttpClient() {
m_session = WinHttpOpen(
L"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
}
~HttpClient() { if (m_session) WinHttpCloseHandle(m_session); }
Response get(const std::string& host, int port, const std::string& path,
const std::string& cookie, const std::string& referer) {
return doRequest(L"GET", host, port, path, "", cookie, referer);
}
Response post(const std::string& host, int port, const std::string& path,
const std::string& postData,
const std::string& cookie, const std::string& referer) {
return doRequest(L"POST", host, port, path, postData, cookie, referer);
}
private:
HINTERNET m_session = nullptr;
Response doRequest(const std::wstring& method,
const std::string& host, int port, const std::string& path,
const std::string& postData,
const std::string& cookie, const std::string& referer) {
Response resp;
std::wstring wHost = strutil::toWide(host);
HINTERNET hConnect = WinHttpConnect(m_session, wHost.c_str(), port, 0);
if (!hConnect) { resp.statusCode = -1; return resp; }
DWORD flags = (port == 443) ? WINHTTP_FLAG_SECURE : 0;
HINTERNET hRequest = WinHttpOpenRequest(hConnect, method.c_str(),
strutil::toWide(path).c_str(), nullptr,
WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, flags);
if (!hRequest) { WinHttpCloseHandle(hConnect); resp.statusCode = -1; return resp; }
DWORD timeout = 10000;
WinHttpSetOption(hRequest, WINHTTP_OPTION_CONNECT_TIMEOUT, &timeout, sizeof(timeout));
WinHttpSetOption(hRequest, WINHTTP_OPTION_SEND_TIMEOUT, &timeout, sizeof(timeout));
WinHttpSetOption(hRequest, WINHTTP_OPTION_RECEIVE_TIMEOUT, &timeout, sizeof(timeout));
DWORD disableRedirect = WINHTTP_DISABLE_REDIRECTS;
WinHttpSetOption(hRequest, WINHTTP_OPTION_DISABLE_FEATURE, &disableRedirect, sizeof(disableRedirect));
std::string headers = "Accept: */*\r\n"
"X-Requested-With: XMLHttpRequest\r\n"
"Referer: " + referer + "\r\n"
"Cache-Control: no-cache\r\n"
"Pragma: no-cache\r\n";
if (!postData.empty())
headers += "Content-Type: application/x-www-form-urlencoded\r\n";
std::wstring wHeaders = strutil::toWide(headers);
WinHttpAddRequestHeaders(hRequest, wHeaders.c_str(), -1, WINHTTP_ADDREQ_FLAG_ADD);
if (!cookie.empty()) {
std::wstring wCookie = strutil::toWide("Cookie: " + cookie + "\r\n");
WinHttpAddRequestHeaders(hRequest, wCookie.c_str(), -1,
WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE);
}
LPVOID pData = nullptr;
DWORD dataLen = 0;
std::string pd = postData;
if (!pd.empty()) { pData = (LPVOID)pd.data(); dataLen = (DWORD)pd.size(); }
if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
pData, dataLen, dataLen, 0)) {
resp.statusCode = -1;
resp.body = "SendRequest failed: " + std::to_string(GetLastError());
WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect);
return resp;
}
if (!WinHttpReceiveResponse(hRequest, nullptr)) {
resp.statusCode = -1;
resp.body = "ReceiveResponse failed";
WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect);
return resp;
}
DWORD sc = 0, sz = sizeof(sc);
WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX, &sc, &sz, WINHTTP_NO_HEADER_INDEX);
resp.statusCode = static_cast<int>(sc);
DWORD bytesRead = 0;
char buffer[4096];
while (WinHttpReadData(hRequest, buffer, sizeof(buffer), &bytesRead) && bytesRead > 0)
resp.body.append(buffer, bytesRead);
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
return resp;
}
};
struct SelectResult {
bool isSuccess = false;
bool isFull = false;
bool isConflict = false;
bool isSessionExpired = false;
std::string message;
};
SelectResult trySelectCourse(HttpClient& client, const Config& cfg) {
SelectResult r;
std::string oper = cfg.mode == 1 ? "ggxxkxkOper" : "bxqjhxkOper";
std::string referer = cfg.mode == 1
? "https://newjw.cau.edu.cn/jsxsd/xsxkkc/comeInGgxxkxk"
: "https://newjw.cau.edu.cn/jsxsd/xsxkkc/comeInBxqjhxk";
std::string path = "/jsxsd/xsxkkc/" + oper + "?kcid=" + cfg.kcid
+ "&cfbs=null&jx0404id=" + cfg.jx0404id + "&xkzy=&trjf=";
auto resp = client.get("newjw.cau.edu.cn", 443, path,
cfg.cookieString(), referer);
if (resp.statusCode <= 0) { r.message = "网络错误: " + resp.body; return r; }
if (resp.statusCode == 302 || resp.statusCode == 401) {
r.isSessionExpired = true; r.message = "Session过期"; return r;
}
if (strutil::contains(resp.body, "\"success\":true") ||
strutil::contains(resp.body, "\"success\": true") ||
strutil::contains(resp.body, "选课成功") ||
strutil::contains(resp.body, "操作成功")) {
r.isSuccess = true; r.message = "选课成功!"; return r;
}
r.message = strutil::jsonValue(resp.body, "msg");
if (r.message.empty()) r.message = strutil::jsonValue(resp.body, "message");
if (r.message.empty()) r.message = strutil::jsonValue(resp.body, "czMsg");
if (r.message.empty()) r.message = resp.body.substr(0, std::min<size_t>(200, resp.body.size()));
if (strutil::contains(r.message, "满") || strutil::contains(r.message, "人数") ||
strutil::contains(r.message, "容量") || strutil::contains(r.message, "余量"))
r.isFull = true;
if (strutil::contains(r.message, "冲突") || strutil::contains(r.message, "时间"))
r.isConflict = true;
if (strutil::contains(r.message, "登录") || strutil::contains(r.message, "Session"))
r.isSessionExpired = true;
return r;
}
void grabWorker(int threadId, const Config& cfg) {
HttpClient client;
int localAttempts = 0;
while (g_running) {
localAttempts++;
g_totalAttempts++;
auto result = trySelectCourse(client, cfg);
if (result.isSuccess) {
console::success("[线程#" + std::to_string(threadId) + "] 选课成功!!!");
console::success("服务器返回: " + result.message);
g_running = false;
return;
}
if (result.isSessionExpired) {
console::error("[线程#" + std::to_string(threadId) + "] Session过期,请重新从浏览器复制Cookie!");
g_running = false;
return;
}
if (result.isConflict) {
console::error("[线程#" + std::to_string(threadId) + "] 课程冲突: " + result.message);
g_running = false;
return;
}
g_failCount++;
if (localAttempts % 20 == 0) {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - g_startTime).count();
double rate = elapsed > 0 ? (g_totalAttempts * 1000.0 / elapsed) : 0;
std::ostringstream oss;
oss << "[#" << threadId << "] " << localAttempts
<< "次 | 总计:" << g_totalAttempts
<< " | " << std::fixed << std::setprecision(0) << rate << "req/s"
<< " | " << result.message;
console::log(oss.str());
}
if (cfg.retryIntervalMs > 0 && g_running)
std::this_thread::sleep_for(std::chrono::milliseconds(cfg.retryIntervalMs));
}
}
//课程列表
struct CourseInfo {
std::string kcid;
std::string jx0404id;
std::string name;
std::string teacher;
std::string time;
std::string remain;
std::string dsfme; // 待释放容量
};
std::string jsonStrVal(const std::string& json, const std::string& key, size_t from = 0) {
std::string k = "\"" + key + "\":\"";
auto p = json.find(k, from);
if (p == std::string::npos) { k = "\"" + key + "\": \""; p = json.find(k, from); }
if (p == std::string::npos) return "";
p += k.size();
auto e = json.find('"', p);
return e == std::string::npos ? "" : json.substr(p, e - p);
}
std::string jsonStrValRev(const std::string& json, const std::string& key, size_t before) {
std::string k = "\"" + key + "\":\"";
auto p = json.rfind(k, before);
if (p == std::string::npos) { k = "\"" + key + "\": \""; p = json.rfind(k, before); }
if (p == std::string::npos) return "";
p += k.size();
auto e = json.find('"', p);
return e == std::string::npos ? "" : json.substr(p, e - p);
}
// 获取json
std::string jsonValRev(const std::string& json, const std::string& key, size_t before) {
std::string k = "\"" + key + "\":";
auto p = json.rfind(k, before);
if (p == std::string::npos) { k = "\"" + key + "\": "; p = json.rfind(k, before); }
if (p == std::string::npos) return "";
p += k.size();
while (p < json.size() && json[p] == ' ') p++;
if (p >= json.size()) return "";
if (json[p] == '"') { p++; auto e = json.find('"', p); return e == std::string::npos ? "" : json.substr(p, e - p); }
auto e = json.find_first_of(",}\n\r", p);
return e == std::string::npos ? json.substr(p) : json.substr(p, e - p);
}
void listCourses(const Config& cfg) {
console::info("正在从教务系统获取可选课程列表...");
HttpClient client;
// 访问选课
auto page = client.get("newjw.cau.edu.cn", 443,
"/jsxsd/xsxkkc/comeInBxqjhxk",
cfg.cookieString(),
"https://newjw.cau.edu.cn/jsxsd/framework/xsMainV_new_zgnydx.jsp?t1=1");
if (page.statusCode != 200) {
console::error("无法访问选课页面 HTTP " + std::to_string(page.statusCode));
return;
}
// AJAX
std::string postData =
"sEcho=1&iColumns=18&iDisplayStart=0&iDisplayLength=500"
"&mDataProp_0=jx0404id&mDataProp_1=kch&mDataProp_2=kcmc"
"&mDataProp_3=jhkcsx&mDataProp_4=kth&mDataProp_5=skbjs"
"&mDataProp_6=xf&mDataProp_7=skls&mDataProp_8=sksj"
"&mDataProp_9=skdd&mDataProp_10=xqmc&mDataProp_11=xkrs"
"&mDataProp_12=xxrs&mDataProp_13=syrs";
auto resp = client.post("newjw.cau.edu.cn", 443,
"/jsxsd/xsxkkc/xsxkBxqjhxk?kcxx=&skls=&skxq=&skjc=&sfym=&sfct=&sfxx=&skfs=",
postData,
cfg.cookieString(),
"https://newjw.cau.edu.cn/jsxsd/xsxkkc/comeInBxqjhxk");
if (resp.statusCode != 200) {
console::error("获取课程列表失败 HTTP " + std::to_string(resp.statusCode));
return;
}
// 解析JSON
std::vector<CourseInfo> courses;
size_t pos = 0;
while (true) {
pos = resp.body.find("\"jx02id\"", pos);
if (pos == std::string::npos) break;
std::string jx02id = jsonStrVal(resp.body, "jx02id", pos);
if (jx02id.empty()) { pos += 12; continue; }
CourseInfo ci;
ci.kcid = jx02id;
ci.jx0404id = jsonStrValRev(resp.body, "jx0404id", pos);
if (ci.jx0404id.empty()) ci.jx0404id = jsonStrVal(resp.body, "jx0404id", pos);
ci.name = jsonStrValRev(resp.body, "kcmc", pos);
if (ci.name.empty()) ci.name = jsonStrVal(resp.body, "kcmc", pos);
ci.teacher = jsonStrValRev(resp.body, "skls", pos);
if (ci.teacher.empty()) ci.teacher = jsonStrVal(resp.body, "skls", pos);
ci.time = jsonStrValRev(resp.body, "sksj", pos);
if (ci.time.empty()) ci.time = jsonStrVal(resp.body, "sksj", pos);
ci.remain = jsonStrValRev(resp.body, "syrs", pos);
if (ci.remain.empty()) ci.remain = jsonStrVal(resp.body, "syrs", pos);
ci.dsfme = jsonValRev(resp.body, "dsfme", pos);
if (!ci.kcid.empty() && !ci.jx0404id.empty()) {
bool dup = false;
for (auto& c : courses)
if (c.kcid == ci.kcid && c.jx0404id == ci.jx0404id) { dup = true; break; }
if (!dup) courses.push_back(ci);
}
pos += 2;
if (courses.size() >= 500) break;
}
if (courses.empty()) {
console::warn("未能解析课程列表");
console::info("已将API返回保存到 page_dump.txt");
std::ofstream dump("page_dump.txt");
dump << resp.body;
dump.close();
return;
}
console::info("找到 " + std::to_string(courses.size()) + " 门可选课程:\n");
for (size_t i = 0; i < courses.size(); i++) {
auto& c = courses[i];
std::ostringstream oss;
oss << std::setw(2) << (i + 1) << ". " << c.name;
if (!c.teacher.empty()) oss << " | " << c.teacher;
if (!c.time.empty()) oss << " | " << c.time;
if (!c.remain.empty()) oss << " | 剩余:" << c.remain;
oss << " | 待释放:" << (c.dsfme.empty() ? "0" : c.dsfme);
console::print(oss.str() + "\n");
}
console::print("\n输入序号选择要抢的课 (1-" + std::to_string(courses.size()) + "): ");
int choice;
std::cin >> choice;
if (choice < 1 || choice > (int)courses.size()) {
console::error("无效选择");
return;
}
auto& picked = courses[choice - 1];
console::success("已选择: " + picked.name);
console::info(" kcid: " + picked.kcid);
console::info(" jx0404id: " + picked.jx0404id);
// 写入config.txt
std::ifstream fin("config.txt");
std::string content((std::istreambuf_iterator<char>(fin)), std::istreambuf_iterator<char>());
fin.close();
auto setval = [&](const std::string& key, const std::string& val) {
size_t p = content.find(key + "=");
if (p == std::string::npos) return;
size_t e = content.find('\n', p);
if (e == std::string::npos) e = content.size();
content.replace(p, e - p, key + "=" + val);
};
setval("kcid", picked.kcid);
setval("jx0404id", picked.jx0404id);
std::ofstream fout("config.txt");
fout << content;
fout.close();
console::success("已更新 config.txt! 直接运行 ./gc.exe 即可抢课");
}
int main(int argc, char* argv[]) {
console::init();
console::print("\n");
console::print(" CAU抢课脚本\n", console::CYAN);
console::print(" 使用脚本有可能会被封IP甚至封号处理,慎重使用!\n",console::RED);
Config cfg;
if (!cfg.loadFromFile("config.txt")) {
console::error("无法读取 config.txt");
return 1;
}
// --list
if (argc >= 2 && std::string(argv[1]) == "--list") {
if (cfg.jsessionid1.empty()) {
console::error("请先在 config.txt 中填写有效的 jsessionid1 和 jsessionid2");
return 1;
}
listCourses(cfg);
return 0;
}
if (!cfg.validate()) {
console::error("config.txt 配置不完整");
console::error("用 ./gc.exe --list 从教务系统选择课程");
return 1;
}
cfg.print();
console::print("\n");
console::info(" 开始抢课! 按 Ctrl+C 停止\n");
g_startTime = std::chrono::steady_clock::now();
std::vector<std::thread> threads;
for (int i = 0; i < cfg.threadCount; i++)
threads.emplace_back(grabWorker, i + 1, std::cref(cfg));
for (auto& t : threads)
if (t.joinable()) t.join();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - g_startTime).count();
console::print("\n══════════════════════════════\n", console::CYAN);
if (g_failCount > 0 && g_totalAttempts == g_failCount)
console::error("所有请求均失败,请检查 config.txt 中的 Cookie 是否有效");
else if (g_running)
console::warn("被用户中断");
else
console::success("抢课成功! ");
double rate = elapsed > 0 ? (g_totalAttempts * 1000.0 / elapsed) : 0;
console::info("总请求: " + std::to_string(g_totalAttempts)
+ " 耗时: " + std::to_string(elapsed / 1000.0) + "s"
+ " 速率: " + std::to_string(static_cast<int>(rate)) + " req/s");
console::print("══════════════════════════════\n", console::CYAN);
return 0;
}
config.txt
txt
# 抢课目标
kcid=01B2B140F47E44F7B7E891A4FC2AC898
jx0404id=202620271000695
# Cookie
jsessionid1=xxxxx
serverid=xxxx
jsessionid2=xxxxx
# 抢课模式: 0=计划选课 1=公选课
mode=1
# 抢课参数
# 线程数 和 抢课的时间间隔
# 线程数越大、抢课的时间间隔越小,访问次数越多,但是不要太高,线程数不要超过15,间隔不要低于30ms,下面这个默认基本够用,也可以上升到10和50
thread_count=8
retry_interval_ms=100
配置config中的Cookie参数
最重要的参数是Cookie里面的参数,步骤是进入选课页面 + F12 + 点击应用 + 点击Cookie + 点击Cookie下面的https:xxxxxxx, 即可看到这三个参数,填入这三个参数即可。

拿到这三个值填入后不要退选课页面。配置Cookie参数必须做!然后可以看下面实现哪一种
本学期计划选课方式
选mode = 0,可以使用./gc.exe --list看到你本学期的可以选的所有课,包含冲冲突的,已满的,限选的。输入对应的号然后会自动配置config里的参数,然后直接./gc.exe 即可!
显示出来大概率像下面这样:

当然,你也可以手动配置选课的信息,请看下面公选课方式
公选课方式
首先选mode = 1,不可以使用./gc.exe --list (当然你使用了显示的也是本学期计划选课的东西) .需要配置的是kcid和jx0404id这两个参数,方法:
1.在浏览器选课页面中搜出你要选的课程,按F12
2.点击网络、点击Fetch/XHR,此时大概率是这个页面: 什么也没有显示出来

3.点击你要选的课程的选课,如下图,就和正常选课的流程一样,无论结果如何不重要(当然你得有结果,无论是失败了还是成功了,别浏览器弹出来问你确不确定你点取消了) ,回到刚才的F12出来的页面

4.此时名称下面大概率出现了三个东西,点击最长的那个,名称大概为ggxxkxkOper? kcid=01131565&cfbs=null&jx0404id=202620271001357&xkzy=&trjf= ,此时我们就看到了对应的参数,当然点击右边的载荷选项更方便看到

将kcid和jx0404id填入即可,另外也可以这样看到自己的JESSIONID1和2在标头中的Cookie可以看到

填入所有信息之后,编译 + ./gc.exe即可
编译和用法
和gc.cpp里面写的是一样的
cpp
* 编译: g++ -o gc.exe gc.cpp -lwinhttp -std=c++20 -O2
* 用法:
* ./gc.exe --list 列出可选课程,选后自动写入config.txt,先执行这行再执行下一行 只能用于本学期计划选课! 公选课请手动配置config.txt
* ./gc.exe 抢课(参数从config.txt读取)
把所有的文件搞到一个目录下,在此目录下打开powershell(输入powershell), 针对自己的要求输入对应的指令。
另外,线程参数和时间间隔选项在config提到了,这个参数就可以了,不需要把线程数搞的太多和时间间隔搞的太低。