高考分数查询结果自动推送至微信(卷II)

祝各位端午节安康!只要心中无,每天都是

        在上一篇文章高考分数查询结果自动推送至微信(卷Ⅰ)-CSDN博客中谈了思路,今天具体实现。文中将敏感信息已做处理,读者根据自己的实际情况替换。

主要逻辑

- 轮询接口列表

- 提取IP和端口

- 替换查询接口的IP和端口

- 替换验证码接口的IP和端口

- 检测可达性

- 成功查询后保存结果并推送消息,跳出循环

某省去年放出的查询接口主页

主页源码中与查询入口有关的网页源码(部分)

<ul class="cfrk">
    <li> <a href="http://xxx.xxx.xxx.xxx:81/n_score/index.jsp" class="gkcj-btn">入口一</a></li>
    <li>  <a href="http://xxx.xxx.xxx.xxx:82/n_score/index.jsp" class="gkcj-btn">入口二</a></li>
    <li><a href="http://xxx.xxx.xxx.xxx:81/n_score/index.jsp" class="gkcj-btn">入口三</a></li>
    <li>  <a href="http://xxx.xxx.xxx.xxx:83/n_score/index.jsp" class="gkcj-btn">入口四</a></li>
    <li><a href="http://xxx.xxx.xxx.xxx:81/n_score/index.jsp" class="gkcj-btn">入口五</a> </li>
	中间省略若干行......
    <li>  <a href="http://xxx.xxx.xxx.xxx:86/n_score/index.jsp" class="gkcj-btn">入口十六</a></li>
</ul>

我们从上面接口列表中提取IP和端口,检测可达性,自动查询分数,然后推送微信。

完整程序

# coding=utf-8
import requests
from bs4 import BeautifulSoup
import time
import itertools
import json
import random
import ddddocr
import onnxruntime
import schedule

# 设置日志级别用于去除ddddocr广告
onnxruntime.set_default_logger_severity(3)

# 你的 Server 酱 SCKEY
SERVER_CHAN_SCKEY = 'YOUR_SERVER_CHAN_SCKEY'  # 替换成你的 SCKEY


def imgRecognition(img):
    """
    识别验证码图片内容

    :param img: 图片文件路径
    :return: 返回识别结果字符串,如果失败则返回None
    """
    try:
        ocr = ddddocr.DdddOcr()
        with open(img, 'rb') as f:
            img_bytes = f.read()
        res = ocr.classification(img_bytes)
        return res
    except Exception as e:
        print(f"OCR failed: {e}")
        return None


def getVerifyimagepage(session, headers, cookies, captcha_url):
    """
    获取验证码图片并保存为 img.jpg

    :param session: requests.Session 对象
    :param headers: 请求头信息
    :param cookies: 请求时带的Cookie
    :param captcha_url: 验证码URL
    :return: 无
    """
    time.sleep(random.random())
    t = random.uniform(0.0, 1.0)
    params = {
        'rnd': t,
    }
    try:
        res = session.get(captcha_url, headers=headers, params=params, cookies=cookies, verify=False)
        with open('img.jpg', 'wb') as img:
            img.write(res.content)
    except Exception as e:
        print(f"Failed to get image: {e}")


def get_Kaptcha(session, headers, cookies, captcha_url):
    """
    获取验证码图片并进行识别

    :param session: requests.Session 对象
    :param headers: 请求头信息
    :param cookies: 请求时带的Cookie
    :param captcha_url: 验证码URL
    :return: 返回识别结果字符串,如果失败则返回None
    """
    getVerifyimagepage(session, headers, cookies, captcha_url)
    return imgRecognition('img.jpg')


def get_query_endpoints(session, base_url):
    """
    获取包含查询接口的URL列表

    :param session: requests.Session 对象
    :param base_url: 主页URL
    :return: 返回查询接口URL列表,如果获取失败则返回空列表
    """
    try:
        response = session.get(base_url)
        response.raise_for_status()
        html_content = response.text
        soup = BeautifulSoup(html_content, 'html.parser')
        ul_tag = soup.find('ul', class_='cfrk')
        if ul_tag:
            return [a['href'] for a in ul_tag.find_all('a')]
        else:
            print("No query endpoints found.")
            return []
    except requests.RequestException as e:
        print(f"Failed to get query endpoints: {e}")
        return []


def extract_ip_port(url):
    """
    从URL中提取IP和端口部分

    :param url: 完整URL
    :return: 返回IP和端口字符串,如果提取失败则返回None
    """
    parts = url.split('/')
    if len(parts) > 2:
        return parts[2]
    return None


def ping_website(session, full_url):
    """
    检查网站的可达性

    :param session: requests.Session 对象
    :param full_url: 完整网站URL
    :return: 网站可达返回True,否则返回False
    """
    try:
        response = session.head(full_url)
        return response.status_code == 200
    except requests.RequestException as e:
        print(f"Request failed: {e}")
        return False


def send_to_wechat(message):
    """
    通过 Server 酱发送消息到微信

    :param message: 要发送的消息文本
    :return: 返回响应的JSON数据,如果发送失败则返回None
    """
    url = f'https://sc.ftqq.com/{SERVER_CHAN_SCKEY}.send'
    data = {
        'text': '查询结果通知',
        'desp': message
    }
    try:
        response = requests.post(url, data=data)
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        print(f"Failed to send message: {e}")
        return None


def perform_query(session, headers, cookies, query_url, idNumber, ticketNumber, captcha_url):
    """
    执行具体的查询任务

    :param session: requests.Session 对象
    :param headers: 请求头信息
    :param cookies: 请求时带的Cookie
    :param query_url: 查询接口URL
    :param idNumber: 身份证号
    :param ticketNumber: 准考证号
    :param captcha_url: 验证码URL
    :return: 返回查询结果的JSON数据,如果查询失败则返回None
    """
    randCode = get_Kaptcha(session, headers, cookies, captcha_url)
    if randCode is None:
        print("Failed to get CAPTCHA code.")
        return None
    query_full_url = f'{query_url}?idNumber={idNumber}&ticketNumber={ticketNumber}&randCode={randCode}'
    try:
        response = session.get(query_full_url, headers=headers, cookies=cookies)
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        print(f"Query failed: {e}")
        return None


terminate = False  # 终止程序标志


def job():
    """
    每分钟执行一次的任务,用于进行查询并处理结果

    :return: 无,但当查询成功并发送消息后,会返回return schedule.CancelJob取消定时任务
    """
    global terminate

    for full_url in url_cycle:
        ip_port = extract_ip_port(full_url)
        if ip_port:
            base_query_url = base_query_url_template.format(ip_port)
            captcha_url = base_captcha_url_template.format(ip_port)
            if ping_website(session, full_url):
                print(f"{full_url} is reachable")
                # 动态设置 headers 中的 Referer 和 Host
                headers["Referer"] = f"http://{ip_port}/n_score/"
                headers["Host"] = ip_port
                result = perform_query(session, headers, cookies, base_query_url, idNumber, ticketNumber, captcha_url)

                if result:
                    print("Query success:", result)
                    score_text = json.dumps(result, ensure_ascii=False, indent=4)

                    json_path = 'result.json'
                    with open(json_path, 'w') as json_file:
                        json.dump(result, json_file, ensure_ascii=False, indent=4)

                    response = send_to_wechat(score_text)

                    if response and response.get('message') == '':
                        print("信息发送成功")
                        terminate = True
                        return schedule.CancelJob  # 成功后返回取消任务
                    else:
                        print("发送信息失败!")
                else:
                    print("查询失败")

                break  # 无论成功还是失败,都跳出循环

            else:
                print(f"{full_url} 暂时不能访问!")
        else:
            print(f"IP、端口错误 {full_url}")


# 初始化配置
session = requests.Session()
headers = {
    "Accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Cache-Control": "no-cache",
    "Pragma": "no-cache",
    "Proxy-Connection": "keep-alive",
    "Referer": "http://xxx.xxx.xxx.xxx:81/n_score/",  # 此处应与查询的IP和端口一致
    "Host": "xxx.xxx.xxx.xxx:81",  # 此处应与查询的IP和端口一致
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
}
cookies = {
    "JSESSIONID": "FB32CDA83DF30554A9C01A2BE77260B6",  # 实际使用时请替换
    "Hm_lvt_9b3b578b0b797c6004bfd68445de9e88": "1687654663"  # 时间戳可以不要
}

base_url = "https://xxx.xxx.xxx.xxx/cx/"  # 查询入口主页
idNumber = "your_id_number"  # 替换为实际身份证号
ticketNumber = "your_ticket_number"  # 替换为实际准考证号
base_query_url_template = 'http://{}/n_score/rest/api/queryscore/snquery'  # 查询接口模板
base_captcha_url_template = 'http://{}/n_score/Kaptcha.do'  # 生成验证码接口模板

# 获取查询接口列表
href_list = get_query_endpoints(session, base_url)
url_cycle = itertools.cycle(href_list)  # 将查询接口用于循环迭代

# 使用 schedule 模块每分钟执行一次 job
schedule.every(1).minutes.do(job)

while not terminate:
    schedule.run_pending()
    time.sleep(1)

print("程序结束")

几点说明:

1、在Server酱中传送json或text

# 假定得到的结果为字典
text = {"querytime": "查询时间:2022-09-22 12:46:47", "totalScore": " 433"}
# 将结果转为json
json_text = json.dumps(text, ensure_ascii=False)
data = {
    'title': '查询结果通知',
    'desp': json_text
}
headers = {'Content-Type': 'application/json'} # 在头中设置Content-Type 为 json
response = requests.post("https://sctapi.ftqq.com/你自己的key.send?title={}", data=data, headers=headers)

#############################################################################################
# 如果结果为table, 可将table转为列表,再转成文格传送
# 提取表格数据
table_data = []
for table in soup.find_all('table'):
    for row in table.find_all('tr'):
        cols = row.find_all(['td'])
        cols = [ele.text.strip() for ele in cols]
        table_data.append(cols)
        
# 将列表中的元素都转换为字符串类型
str_list = [str(item) for item in table_data]
# 使用 join() 方法将列表转换为文本
text = ', '.join(str_list)

data = {
    'title': '查询结果通知',
    'desp': text
}
response = requests.post("https://sctapi.ftqq.com/你自己的key.send?title={}", data=data)

2、为了确保在查询分数网页和获取验证码时使用同一个会话,我们需要确保所有与网络交互的请求都通过同一个requests.Session。

3、可以将程序部署到阿里云使用FC功能 ,在高考查分前一天触发,让其自动运行直到查询到结果。

       

        此程序是基于我去年成功运行的版本进行修改的。目前,由于今年的查分接口尚未发布,我只能使用去年的接口列表进行开发。因此,当下的接口无法Ping通。基于此限制,我在理论上完成了整个程序,但尚未进行实际测试。

        希望以后高考分数,家长和考生第一时间知晓,自动推送。家长无需费心,考生无需烦恼,无需查询,消息早已送达。此时我无需多言,只愿学子一举夺魁,金榜题名。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/695994.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

6.全开源源码---小红书卡片-跳转微信-自动回复跳转卡片-商品卡片-发私信-发群聊-安全导流不封号-企业号白号都可以用

现在用我们的方法&#xff0c;可以规避违规风险&#xff0c;又可以丝滑引流&#xff0c;因为会以笔记的形式发给客户&#xff0c;点击之后直接跳微信&#xff0c;我们来看看演示效果吧&#xff08;没有风险提示&#xff09; 无论是引流还是销售产品都会事半功倍。

redis03 补充 redis驱动模型:事件驱动

1.文件事件 1.1 1.2 注&#xff1a; epoll是linux系统的底层IO多路复用技术 kqueue是mac的底层IO多路复用技术 在 Epoll 中&#xff0c;Epoll 就是事件通知器&#xff0c;可以向 Epoll 注册我们感兴趣的事件。 1.3 1.4 5. 5.1 5.2 5.35.4

onesixtyone一键扫描SNMP服务(KALI工具系列二十)

目录 1、KALI LINUX 简介 2、onesixtyone工具简介 3、在KALI中使用onesixtyone 3.1 目标主机IP&#xff08;win&#xff09; 3.2 KALI的IP 4、操作示例 4.1 扫描目标主机 4.2 加上团队名称 4.3 输出详细结果 4.4 扫描整个网段 5、总结 1、KALI LINUX 简介 Kali Lin…

ThinkPHP发邮件配置教程?群发功能安全吗?

ThinkPHP发邮件的注意事项&#xff1f;如何优化邮件发送的性能&#xff1f; 无论是用户注册、密码重置还是消息提醒&#xff0c;发送邮件都是一个常见的需求。AokSend将详细介绍如何在ThinkPHP框架中配置和发送邮件&#xff0c;帮助开发者轻松实现邮件功能。 ThinkPHP发邮件&…

Discuz! X3.4发帖时间修改插件批量操作版

下载地址&#xff1a;Discuz! X3.4发帖时间修改插件批量操作版 发帖时间与回复时间说明 1、使用本插件修改发帖时间&#xff0c;则帖子中的回复楼层的时间会保持同步同间隔修改&#xff0c;所谓同步同间隔就是如果某个回复是在主题发布之后一小时回复的&#xff0c;那么修改之…

MySQL—多表查询—练习(1)

一、引言 上几篇关于多表查询的基本几个部分全部学习完了。 多表查询的基本类型的查询包括以下&#xff1a; 1、内连接&#xff08;隐式内连接、显示内连接&#xff09;&#xff1a;... [INNER] JOIN ... ON 条件; &#xff09; 2、外连接&#xff08;左外连接、右外连接&…

[沉迷理论]进制链表树

往期文章推荐&#xff1a; 题解之最大子矩阵-CSDN博客 洛谷P1115最大子段和[神奇的题目]-CSDN博客 &#xff08;一条神奇的分割线&#xff09; 前言 好久没有更新的我总算在百忙之中抽出时间写了篇博客。 最近总算结束了动态规划的学习&#xff0c;真的是头昏脑涨啊。 最…

MySQl基础----Linux下搭建mysql软件及登录和基本使用(附实操图超简单一看就会)

绪论​ 涓滴之水可磨损大石&#xff0c;不是由于他力量强大&#xff0c;而是由于昼夜不舍地滴坠。 只有勤奋不懈地努力&#xff0c;才能够获得那些技巧。 ——贝多芬。新开MySQL篇章&#xff0c;本章非常基础包括如何在Linux上搭建&#xff08;当然上面的SQL语句你在其他能执行…

初阶 《分支和循环语句》 3.循环语句

3.循环语句 while for do while 3.1 while循环 前面已经掌握了 if 语句&#xff1a; if(条件)   语句; 当条件满足的情况下&#xff0c;if语句后的语句执行&#xff0c;否则不执行&#xff1b;但是这个语句只会执行一次。 由于我们发现生活中很多的实际的例子是&#xff1a;同…

MYSQL 索引下推 45讲

刘老师群里,看到一位小友 问<MYSQL 45讲>林晓斌的回答 大意是一个组合索引 (a,b,c) 条件 a > 5 and a <10 and b123, 这样的情况下是如何? 林老师给的回答是 A>5 ,然后下推B123 小友 问 "为什么不是先 进行范围查询,然后在索引下推 b123?" 然后就…

Leetcode 力扣114. 二叉树展开为链表 (抖音号:708231408)

给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同。 示例 1&#xf…

欢乐打地鼠小游戏html源码

这是一款简单的js欢乐打地鼠游戏&#xff0c;挺好玩的&#xff0c;老鼠出来用鼠标点击锤它&#xff0c;击中老鼠获得一积分。 欢乐打地鼠小游戏html源码

不同数据库背后的数据存储方案

在大数据和AI时代&#xff0c;数据库成为各类应用不可或缺的重要组成部分。而数据库中的数据依赖存储引擎进行管理&#xff0c;包括数据的存储、查询、更新和删除等。因此&#xff0c;在设计系统时&#xff0c;选择正确的数据库存储引擎方案变得尤为重要。这篇文章将以关系型、…

滑动窗口算法:巧妙玩转数据的窗外世界

✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走下来! 文章目录 目录 文章目录 前言 一 滑动窗口是什么&#xff1f; 二 相关题目解析 1. 长度最小的子数组 &#x1f973;题目解析 &#x1f973;算法原理 ✏️思路1 暴力枚举出所有子数组之和 ✏️思路2 滑动窗…

LabVIEW调用DLL时需注意的问题

在LabVIEW中调用DLL&#xff08;动态链接库&#xff09;是实现与外部代码集成的一种强大方式&#xff0c;但也存在一些常见的陷阱和复杂性。本文将从参数传递、数据类型匹配、内存管理、线程安全、调试和错误处理等多个角度详细介绍LabVIEW调用DLL时需要注意的问题&#xff0c;…

有趣的数学 为什么绝对值和模都用两个竖线表示?

绝对值和模都可以使用两个竖线表示&#xff0c;是因为它们在数学概念上有相似的性质&#xff0c;不过是应用场景不同。 绝对值&#xff08;Absolute Value&#xff09;&#xff1a; 绝对值是一个实数的非负值。它表示一个数在数轴上距离原点的距离。例如&#xff0c; 和 。 模&…

Hadoop3:MapReduce源码解读之Map阶段的TextInputFormat切片机制(3)

Job那块的断点代码截图省略&#xff0c;直接进入切片逻辑 参考&#xff1a;Hadoop3&#xff1a;MapReduce源码解读之Map阶段的Job任务提交流程&#xff08;1&#xff09; 5、TextInputFormat源码解析 类的继承关系 它的内容比较少 重写了两个父类的方法 这里关心一下泛型参数…

基于Java+SpringBoot制作一个软考助手答题小程序

基于Java+SpringBoot制作一个软考小助手考试答题小程序。其中系统前端功能包括注册登录、公告通知、考试答题、视频课程、考试记录、题库、题目评论、错题统计、我的收藏和用户信息管理模块;系统后台功能包括用户管理、题库管理、答题管理、学习视频管理以及系统管理模块。 摘…

WINUI——Behavior(行为)小结

前言 在使用MVVM进行WINUI或WPF开发时&#xff0c;Command在某些时候并不能满足逻辑与UI分离的要求。这时肯定就需要其它技术的支持&#xff0c;Behavior就是一种。在WPF中是有Behavior直接支持的&#xff0c;转到WINUI后&#xff0c;相对有一些麻烦&#xff0c;于是在此记录之…

RainBond 制作应用并上架【以ElasticSearch为例】

文章目录 安装 ElasticSearch 集群第 1 步:添加组件第 2 步:查看组件第 3 步:访问组件制作 ElasticSearch 组件准备工作ElasticSearch 集群原理尝试 Helm 安装 ES 集群RainBond 制作 ES 思路源代码Dockerfiledocker-entrypoint.shelasticsearch.yml制作组件第 1 步:添加组件…