qg监督系统实现思路以及源码


前言

因为大学职位缘故,需要每天在QQ群里通报支部qg的完成情况,事情很简单,但是每天重复@人真的很繁琐,于是便编写了以下程序,减轻工作负担。


一、思路

1.1 登录

1.1.1 扫码

qg后台只允许二维码扫描登录,如果要实现免密登录需要的技术太高,本人暂时没达到,于是采用selenium控制浏览器,获取网页中二维码,并传输到pushplus公众号或者钉钉,实现扫码登录(直接在电脑扫码也一样)

1.1.2 选择需要管理的学习组织

这里也是使用selenium进行选择,选择完成后自动点击进入,就可以获得cookie了

![在这里插入图片描述](https://img-blog.csdnimg.cn/9be136b216394c65810b67c865ad9aef.png

1.2 数据请求

1.2.1 数据分析

进入F12进行抓包,可以分析看到这个包里是返回数据的

在这里插入图片描述

响应数据如下:

在这里插入图片描述
查看数据请求域名和请求头

1.2.2 请求URL

请求URL
在这里插入图片描述

1.2.3 请求头

请求头

在这里插入图片描述
简单分析一下

"startDate"和"endDate"表示开始日期和结束日期
“pageSize”:表示一页中的数据条数
“order”:表示正序或倒序排列
“orgGrayId”:表示机构灰度ID
“apiCode”:可能是这个接口的唯一标识符号

那么最主要的参数就是apiCode startDate endDate pageSize
调整这几个参数,加上之前获得的cookie,就可以得到我们想要的数据了

1.3 消息推送

1.3.1 登录二维码推送

1:pushplus公众号
     pushplus公众号推送文档
根据文档获取你的PPtoke,然后关注公众号就可以了

2:钉钉推送
参考官方文档,创建钉钉机器人,获取DDtoken和DDsecret,实现推送。


1.3.2 QQ群消息推送

使用Qmsg进行QQ消息的推送
配置参考官方文档:Qmsg使用文档
此项目已经将Qmsg捐赠版(私服)集成,可直接使用

二、系统实现

2.1.引入库

import win32gui
import time
from pyzbar.pyzbar import decode
from Tools.out import up
import subprocess
from Tools.pluspush import PlusPushHandler
from Tools.dingding import DingDingHandler
import sys
import os
from configparser import ConfigParser
import urllib.parse
import re
from PIL import Image
from io import BytesIO
import base64
import requests
import json
import datetime
from selenium.webdriver.common.by import By
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from requests.cookies import RequestsCookieJar

2.1. 配置文件

配置文件内容:

login: 为qg后台登录域名
database: 为qg后台数据获取域名
Score: 为需要达到的分数
Department: 为你管理的支部全称
PushMode:推送方式

; 基本设置
[url]
login = https://login.xuexi.cn/login/#/
database = https://odrp.xuexi.cn/report/commonReport

[base]
;目标分数
Score = 35
;所属部门
Department = 信息学院###支部学习管理组

[push]

PushMode = 2
DDtoken = None
DDsecret = None
PPtoken=###

2.2 读取配置文件

将配置文件信息集成到xue_cfg

def load_config(nologo=False):
    if nologo == False:
        print("=" * 60 + "\n" )
    else:
        pass
    xue_cfg = ConfigParser()
    sys_patch = os.path.split(os.path.abspath(sys.argv[0]))[0]
    if (not os.path.exists(sys_patch + "/Config")):
        os.mkdir(sys_patch + "/Config")

    if (not os.path.exists(sys_patch + "/Config/Config.cfg")):
        print("=" * 60)
        print("@启动失败,缺少配置文件: Config/Config.cfg")
        sys.exit()
    else:
        xue_cfg.read(sys_patch + "/Config/Config.cfg", encoding='utf-8')
        # 读取环境变量
        if os.environ.get("url") is not None:
            xue_cfg.set("url", "login", os.environ.get("login"))
        if os.environ.get("url") is not None:
            xue_cfg.set("url", "database", os.environ.get("database"))
        if os.environ.get("Department") is not None:
            xue_cfg.set("base", "Department", os.environ.get("Department"))
        if os.environ.get("Score") is not None:
            xue_cfg.set("base", "Score", os.environ.get("Score"))
        if os.environ.get("PushMode") is not None:
            xue_cfg.set("push", "PushMode", os.environ.get("PushMode"))
        if os.environ.get("DDtoken") is not None:
            xue_cfg.set("push", "DDtoken", os.environ.get("DDtoken"))
        if os.environ.get("DDsecret") is not None:
            xue_cfg.set("push", "DDsecret", os.environ.get("DDsecret"))
        if os.environ.get("PPtoken") is not None:
            xue_cfg.set("push", "PPtoken", os.environ.get("PPtoken"))
    return xue_cfg

2.3 登录界面打开

def login():
    option = webdriver.ChromeOptions()

    option.add_argument('--window-position=0,0')
    option.add_argument('--log-level=3')

    driver = webdriver.Chrome(options=option)
    # with open('stealth.min.js') as f:
    #     net_stealth = f.read()
    # f.close()
    # driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {"source": net_stealth})

    driver.get(xue_cfg["url"]["login"])
    print("完成页面打开")
    return driver

2.4 登录二维码获取与推送

def sent_img():
    driver.switch_to.frame(
        WebDriverWait(driver, 30, 0.2).until(
            lambda driver: driver.find_element(by=By.XPATH, value="/html/body/div[1]/div[1]/div/div[2]/iframe"))
    )
    img = WebDriverWait(driver, 30, 0.2).until(
        lambda driver: driver.find_element(by=By.TAG_NAME, value="img")
    )
    upimg(img)#发送图片
    
def upimg(img):
    path = img.get_attribute("src")
    
    img = base64_to_image(base64_str=path)
    
    decocdeQR = decode(img)
    
    url = "dtxuexi://appclient/page/study_feeds?url=" + \
        urllib.parse.quote(decocdeQR[0].data.decode('ascii'))
    
    print("发送二维码...\n" + "=" * 60)
    
    sendMessage(msg={"url": url, "qrcode": path}, mode="link")
    
    print("图片获取成功")
    
def base64_to_image(base64_str):
        base64_data = re.sub('^data:image/.+;base64,', '', base64_str)
        byte_data = base64.b64decode(base64_data)
        image_data = BytesIO(byte_data)
        img = Image.open(image_data)
        return img
        
def sendMessage(msg, mode="msg"):
        if xue_cfg["push"]["PushMode"] == "1":
            token = xue_cfg["push"]["DDtoken"]
            secret = xue_cfg["push"]["DDsecret"]
            if token is not None and secret is not None:
                ddhandler = DingDingHandler(token, secret)
                ddhandler.ddmsgsend(msg, mode)
            else:
                print("钉钉token未设置,取消发送消息")
        elif xue_cfg["push"]["PushMode"] == "2":
            token = xue_cfg["push"]["PPtoken"]
            if token is not None:
                ddhandler = PlusPushHandler(token)
                ddhandler.ppmsgsend(msg, mode)
            else:
                print("PlusPush token未设置,取消发送消息")

2.5 获取cookies

def get_cookies():
    WebDriverWait(driver, 120, 1).until(
            lambda driver: driver.find_element(by=By.CLASS_NAME,value="ant-select-arrow"))

    driver.find_element(by=By.CLASS_NAME,value="ant-select-arrow").click()
    WebDriverWait(driver, 120, 1).until(EC.invisibility_of_element_located((By.CLASS_NAME, "ant-select-dropdown-hidden")))
    time.sleep(0.9)
    WebDriverWait(driver, 120, 1).until(
        lambda driver: driver.find_element(by=By.TAG_NAME,value="li"))
    zuzhi=driver.find_elements(by=By.TAG_NAME,value="li")

    for i in zuzhi:
        if i.text==xue_cfg["base"]["Department"]:
            i.click()
        else:
            pass
    print("组织选择成功")

    driver.find_element(by=By.CLASS_NAME,value="ant-btn-lg").click()
    print("获取cookies中")
    WebDriverWait(driver, 120, 1).until_not(
        lambda driver: driver.find_element(by=By.CLASS_NAME,value="copyright___2BmNv"))
    print("cookies获取成功\n")
    cookies = driver.get_cookies()
    driver.quit()
    return cookies

2.6 解析cookies

def parse_cookies():
    jar = RequestsCookieJar()
    for cookie in cookies:
        jar.set(cookie['name'], cookie['value'])

    return jar

2.7 获取当前时间信息

def get_now_time():
    time_now = datetime.datetime.now()
    print(time_now)
    month = time_now.month
    day = time_now.day

    if (len(str(month)) == 1):
        month = "0" + str(month)

    elif (len(str(day)) == 1):
        day = "0" + str(day)
    return time_now,month,day

2.8 从数据端口获取所需数据

def data_acquisition():
    header = {
        "Connection": "keep-alive",
        "Content-Length": "193",
        "Content-Type": "application/json;charset=UTF-8",
        "Host": "odrp.xuexi.cn",
        "Origin": "https://study.xuexi.cn",
        "Pragma": "no-cache",
        "Referer": "https://study.xuexi.cn/",
        "sec-ch-ua": 'Microsoft Edge";v="107", "Chromium";v="107", "Not=A?Brand";v="24',
        'sec-ch-ua-mobile': '?0',
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"
    }
    time = str((time_now + datetime.timedelta(days=-1)).strftime("%Y%m%d"))//解析为需要的时间数据格式
    date = {
        "apiCode": "ab4afc14",
        "dataMap": {
            "startDate": time,
            "endDate": time,
            "offset": 0,
            "sort": "totalScore",
            "pageSize": 51,
            "order": "desc",
            "isActivate": "",
            "orgGrayId": "kHTJT8q11mPzE+KSpxvqkw=="
        }
    }
    date = json.dumps(date)

    res = requests.post(xue_cfg["url"]["database"], headers=header, data=date, cookies=jar)
    res = json.loads(res.text)

    res = json.loads(res["data_str"])
    res = res["dataList"]["data"]
    return res

2.9 主函数

if __name__ == '__main__':
    unfinisheds = 0
    proc_1 = subprocess.Popen(['python', "./Tools/run_Qmsg.py"])
    hwnd = wait_for_window("Qmsg Login", timeout=30)

    xue_cfg = load_config(True)
    qq_at = ""
    time_now,month,day=get_now_time()
    file_path = "data/每日情况/名单-{}月{}日.txt".format(month, day)
    with open("data/QQ/qq.txt", mode="r", encoding="utf-8") as f8:
        qq_lines = f8.readlines()

    if os.path.exists(file_path) and os.path.getsize(file_path) != 0:

        readlocal(qq_at)

    else:

        driver=login()

        #获取图片&&发送图片
        sent_img()

        #等待扫码
        WebDriverWait(driver, 120, 1).until(
            lambda driver: driver.find_element(by=By.CLASS_NAME,value="ant-form-item-control"))

        print("扫码成功")

        #获取cookies
        cookies=get_cookies()

        # get_newer_cookie_and_save(cookies)

        #解析cookies
        jar = parse_cookies()

        #从服务器获取数据
        res=data_acquisition()

        data_sort = sorted(res, key=lambda x: (x.get("rangeScore", 0)))

        daily_situation=open("data\每日情况\名单-"+"{}月{}日".format(month,day)+".txt","w+",encoding='utf-8',newline="")
        daily_situation.write('   今天是{}月{}日'.format(month,day)+"\n\n昨日不合格分数:\n**********"+"\n")


        for i in data_sort:

            if (i["rangeScore"] < int(xue_cfg["base"]["Score"])):
                unfinisheds = unfinisheds + 1
                daily_situation.write(str(unfinisheds) + " " + i["userName"] + " 分数:" + str(i["rangeScore"]) + "\n")
                for i1 in qq_lines:
                    value = i1.replace("\n", '').rsplit(",")
                    if value[0] == i["userName"]:
                        qq_at+="@at=" + value[1] + "@\n"

        if unfinisheds==0:

            daily_situation.write("无, 学习任务均已完成\n**********")
            daily_situation.close()
            daily_situation = open("data\每日情况\名单-" + "{}月{}日".format(month, day) + ".txt", "r", encoding='utf-8', newline="")
            content = daily_situation.read().strip("\n")
            daily_situation.close()
            print(content)
            up(content)

        else:

            print(qq_at)
            up(qq_at.strip("\n"))
            daily_situation.write("**********\n" + "请以上未完成的同学,手写一份检讨发群里")
            daily_situation.close()
            daily_situation = open("data\每日情况\名单-" + "{}月{}日".format(month, day) + ".txt", "r", encoding='utf-8', newline="")
            ss = daily_situation.read().strip("\n")
            daily_situation.close()
            print(ss)
            up(ss)

三、运行实例

3.1 Qsmg登录界面

在这里插入图片描述

3.2 扫码界面

1:本机直接扫码

在这里插入图片描述

2:pushplus公众号推送

在这里插入图片描述

3.3 QQ群消息实例

在这里插入图片描述


总结

本系统部署在服务器可以定期进行qg监督,减轻管理人员的工作量。也可以客制化消息推送内容,实现更多功能。
总体实现的方法比较简单,还有很大的改进空间,欢迎大家提出建议。后面也会将项目放到github上

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值