Compare commits
8
Commits
a5c4557f44
...
main
@@ -1,26 +1,46 @@
|
|||||||
name: CET Monitor
|
name: CET Monitor
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "17 * * * *"
|
- cron: '*/30 6-18 * * *'
|
||||||
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
force_push:
|
||||||
|
description: "强制推送当前公告"
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
monitor:
|
monitor:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout
|
||||||
- uses: actions/setup-python@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
- run: pip install -r requirements.txt
|
|
||||||
- name: Run
|
- name: Install
|
||||||
|
run: pip install -r requirements.txt
|
||||||
|
|
||||||
|
- name: Run monitor
|
||||||
env:
|
env:
|
||||||
PUSH_TOKEN: ${{ secrets.PUSH_TOKEN }}
|
PUSH_TOKEN: ${{ secrets.PUSH_TOKEN }}
|
||||||
|
FORCE_PUSH: ${{ github.event.inputs.force_push }}
|
||||||
run: python main.py
|
run: python main.py
|
||||||
- name: Save state
|
|
||||||
|
- name: Commit state UTC
|
||||||
run: |
|
run: |
|
||||||
git config user.name "gitea-actions"
|
git config user.name "gitea-actions"
|
||||||
git config user.email "actions@localhost"
|
git config user.email "actions@localhost"
|
||||||
|
|
||||||
git add data/state.json
|
git add data/state.json
|
||||||
git diff --cached --quiet || git commit -m "update cet state"
|
|
||||||
|
git diff --cached --quiet || git commit -m "update cet state $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||||
|
|
||||||
git push
|
git push
|
||||||
|
|||||||
+1
-21
@@ -3,123 +3,103 @@
|
|||||||
{
|
{
|
||||||
"title": "中国教育考试网",
|
"title": "中国教育考试网",
|
||||||
"url": "//www.neea.edu.cn/",
|
"url": "//www.neea.edu.cn/",
|
||||||
"category": "📝 考试安排",
|
|
||||||
"hash": "cf41a21ad8b1fb90b1afa528fe0ca2b382be41176ba04bf796133f909e8ca201"
|
"hash": "cf41a21ad8b1fb90b1afa528fe0ca2b382be41176ba04bf796133f909e8ca201"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "考试动态",
|
"title": "考试动态",
|
||||||
"url": "/html1/category/16093/1124-1.htm",
|
"url": "/html1/category/16093/1124-1.htm",
|
||||||
"category": "📝 考试安排",
|
|
||||||
"hash": "be0e11646e3f9c624d5facda8c80f848ac1864a5c0e05c95da1d88ce71575cd4"
|
"hash": "be0e11646e3f9c624d5facda8c80f848ac1864a5c0e05c95da1d88ce71575cd4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "CET委员会",
|
"title": "CET委员会",
|
||||||
"url": "/html1/folder/19081/5123-1.htm",
|
"url": "/html1/folder/19081/5123-1.htm",
|
||||||
"category": "📢 其他",
|
|
||||||
"hash": "e2fdf6b7ed34f4439e1ae488f34f406fbef851da0a69ceec918d357bc9ed73eb"
|
"hash": "e2fdf6b7ed34f4439e1ae488f34f406fbef851da0a69ceec918d357bc9ed73eb"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "考试大纲",
|
"title": "考试大纲",
|
||||||
"url": "/html1/folder/16113/1588-1.htm",
|
"url": "/html1/folder/16113/1588-1.htm",
|
||||||
"category": "📝 考试安排",
|
|
||||||
"hash": "8117ca68e3c80bab0270ed37d66f0d6cc0d5faa96bc78a894c6839a0e0ee006e"
|
"hash": "8117ca68e3c80bab0270ed37d66f0d6cc0d5faa96bc78a894c6839a0e0ee006e"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "2026年上半年全国大学英语四、六级考试报名工作启动",
|
"title": "2026年上半年全国大学英语四、六级考试报名工作启动",
|
||||||
"url": "/html1/report/2603/2-1.htm",
|
"url": "/html1/report/2603/2-1.htm",
|
||||||
"category": "📝 报名",
|
|
||||||
"hash": "29ec94196f15fa6268836c3717f68f9c6b1b30438f78ae6f50aeb172758b22be"
|
"hash": "29ec94196f15fa6268836c3717f68f9c6b1b30438f78ae6f50aeb172758b22be"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "2025年下半年全国大学英语四、六级考试(CET)成绩查询有关安排",
|
"title": "2025年下半年全国大学英语四、六级考试(CET)成绩查询有关安排",
|
||||||
"url": "/html1/report/2602/55-1.htm",
|
"url": "/html1/report/2602/55-1.htm",
|
||||||
"category": "📊 成绩",
|
|
||||||
"hash": "8d698177e736f98ff0fbec5f3cadd4281d37db2983fd4f273bb151870a31c9af"
|
"hash": "8d698177e736f98ff0fbec5f3cadd4281d37db2983fd4f273bb151870a31c9af"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "2025年下半年全国大学英语四、六级考试报名工作启动",
|
"title": "2025年下半年全国大学英语四、六级考试报名工作启动",
|
||||||
"url": "/html1/report/2509/26-1.htm",
|
"url": "/html1/report/2509/26-1.htm",
|
||||||
"category": "📝 报名",
|
|
||||||
"hash": "678fceb9259545140b334dbfde975713a554a482520d234fc1600f031f32e9b4"
|
"hash": "678fceb9259545140b334dbfde975713a554a482520d234fc1600f031f32e9b4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "CET笔试",
|
"title": "CET笔试",
|
||||||
"url": "/html1/folder/16113/1586-1.htm",
|
"url": "/html1/folder/16113/1586-1.htm",
|
||||||
"category": "📢 其他",
|
|
||||||
"hash": "79eee32cf9c97911824e4662f86bbc9afc8aea9daba2f807541df051cfb9ca6c"
|
"hash": "79eee32cf9c97911824e4662f86bbc9afc8aea9daba2f807541df051cfb9ca6c"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "CET口试",
|
"title": "CET口试",
|
||||||
"url": "/html1/folder/16113/1587-1.htm",
|
"url": "/html1/folder/16113/1587-1.htm",
|
||||||
"category": "📢 其他",
|
|
||||||
"hash": "b869b0e2f1055c0a61ee9b51d230ca319d87dcd16dfe689eed6088f5fcfedf65"
|
"hash": "b869b0e2f1055c0a61ee9b51d230ca319d87dcd16dfe689eed6088f5fcfedf65"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "在线报名",
|
"title": "在线报名",
|
||||||
"url": "http://cet-bm.neea.edu.cn/",
|
"url": "http://cet-bm.neea.edu.cn/",
|
||||||
"category": "📝 报名",
|
|
||||||
"hash": "364aa4633a1367162c75f6498e686e5a1fd74ebabb2c4733debf471998a51167"
|
"hash": "364aa4633a1367162c75f6498e686e5a1fd74ebabb2c4733debf471998a51167"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "成绩查询",
|
"title": "成绩查询",
|
||||||
"url": "//cet.neea.edu.cn/cet/",
|
"url": "//cet.neea.edu.cn/cet/",
|
||||||
"category": "📊 成绩",
|
|
||||||
"hash": "903607d3b97b81ceee311b7204bfdf0c495009e9c033c217a1e01c142c0da057"
|
"hash": "903607d3b97b81ceee311b7204bfdf0c495009e9c033c217a1e01c142c0da057"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "补办成绩证明",
|
"title": "补办成绩证明",
|
||||||
"url": "//bbcjzm.neea.edu.cn/",
|
"url": "//bbcjzm.neea.edu.cn/",
|
||||||
"category": "📊 成绩",
|
|
||||||
"hash": "bc56f9653c65bdfe0add8bdfd70af699b51bc0c192a96b5c62ea2ea7b347efb9"
|
"hash": "bc56f9653c65bdfe0add8bdfd70af699b51bc0c192a96b5c62ea2ea7b347efb9"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "成绩验证",
|
"title": "成绩验证",
|
||||||
"url": "//www.neea.edu.cn/html1/folder/21033/658-1.htm",
|
"url": "//www.neea.edu.cn/html1/folder/21033/658-1.htm",
|
||||||
"category": "📊 成绩",
|
|
||||||
"hash": "842cbf459d0325ed6e44da417542a717c47ce69f63fe0fb7a4ae60814e1bfb4a"
|
"hash": "842cbf459d0325ed6e44da417542a717c47ce69f63fe0fb7a4ae60814e1bfb4a"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "成绩核查",
|
"title": "成绩核查",
|
||||||
"url": "/html1/folder/19081/5179-1.htm",
|
"url": "/html1/folder/19081/5179-1.htm",
|
||||||
"category": "📊 成绩",
|
|
||||||
"hash": "aaf8378d7ebeed08fdef16746859583234c36ced0135e67d75ac6e88ee81303f"
|
"hash": "aaf8378d7ebeed08fdef16746859583234c36ced0135e67d75ac6e88ee81303f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "成绩证明",
|
"title": "成绩证明",
|
||||||
"url": "/html1/folder/19081/5180-1.htm",
|
"url": "/html1/folder/19081/5180-1.htm",
|
||||||
"category": "📊 成绩",
|
|
||||||
"hash": "9537685c4d115a38a34f4d014ccd56eb8e3c83a17b9179664e6858051720b4d1"
|
"hash": "9537685c4d115a38a34f4d014ccd56eb8e3c83a17b9179664e6858051720b4d1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "省级教育考试机构",
|
"title": "省级教育考试机构",
|
||||||
"url": "javascript:",
|
"url": "javascript:",
|
||||||
"category": "📝 考试安排",
|
|
||||||
"hash": "7572327808ffd1f0a342f16085605f502025944baee9c4a89dbbb3b99dc950bb"
|
"hash": "7572327808ffd1f0a342f16085605f502025944baee9c4a89dbbb3b99dc950bb"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "北京教育考试院",
|
"title": "北京教育考试院",
|
||||||
"url": "http://www.bjeea.cn/",
|
"url": "http://www.bjeea.cn/",
|
||||||
"category": "📝 考试安排",
|
|
||||||
"hash": "5300448f8adf5442261f5ceaa1255869f179a0d7117ec3d30466e593f7f908cf"
|
"hash": "5300448f8adf5442261f5ceaa1255869f179a0d7117ec3d30466e593f7f908cf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "天津市教育招生考试院",
|
"title": "天津市教育招生考试院",
|
||||||
"url": "http://www.zhaokao.net",
|
"url": "http://www.zhaokao.net",
|
||||||
"category": "📝 考试安排",
|
|
||||||
"hash": "cb06a7f19bc2231b010e287c44d16f48332efade1b6819999a7472930ce28c14"
|
"hash": "cb06a7f19bc2231b010e287c44d16f48332efade1b6819999a7472930ce28c14"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "河北省教育考试院",
|
"title": "河北省教育考试院",
|
||||||
"url": "http://www.hebeea.edu.cn",
|
"url": "http://www.hebeea.edu.cn",
|
||||||
"category": "📝 考试安排",
|
|
||||||
"hash": "24491947d9152a979d94a8344867668b5be94022beb7c3c55008b33ca0b85a61"
|
"hash": "24491947d9152a979d94a8344867668b5be94022beb7c3c55008b33ca0b85a61"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "山西省招生考试管理中心",
|
"title": "山西省招生考试管理中心",
|
||||||
"url": "http://www.sxkszx.cn",
|
"url": "http://www.sxkszx.cn",
|
||||||
"category": "📝 考试安排",
|
|
||||||
"hash": "6084abd031ee4d800065b9648e665982dce30b0de38d553af6df7b3f828a0d3e"
|
"hash": "6084abd031ee4d800065b9648e665982dce30b0de38d553af6df7b3f828a0d3e"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"last_check": "2026-08-05T04:02:36.173896+00:00"
|
"last_check_utc": "2026-08-05T04:16:50.540062+00:00"
|
||||||
}
|
}
|
||||||
@@ -1,61 +1,146 @@
|
|||||||
import os,json,hashlib
|
|
||||||
from datetime import datetime,timezone
|
import os
|
||||||
|
import json
|
||||||
|
import hashlib
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
URL="https://cet.neea.edu.cn/"
|
|
||||||
STATE="data/state.json"
|
|
||||||
|
|
||||||
def category(t):
|
URL = "https://cet.neea.edu.cn/"
|
||||||
if "报名" in t:return "📝 报名"
|
STATE_FILE = "data/state.json"
|
||||||
if "成绩" in t:return "📊 成绩"
|
|
||||||
if "准考证" in t:return "🎫 准考证"
|
|
||||||
if "考试" in t:return "📝 考试安排"
|
def fetch_notices():
|
||||||
return "📢 其他"
|
headers = {"User-Agent": "Mozilla/5.0"}
|
||||||
|
|
||||||
|
for retry in range(3):
|
||||||
|
try:
|
||||||
|
r = requests.get(URL, headers=headers, timeout=20)
|
||||||
|
r.encoding = "utf-8"
|
||||||
|
|
||||||
|
soup = BeautifulSoup(r.text, "lxml")
|
||||||
|
result = []
|
||||||
|
|
||||||
def fetch():
|
|
||||||
r=requests.get(URL,headers={"User-Agent":"Mozilla/5.0"},timeout=20)
|
|
||||||
r.encoding="utf-8"
|
|
||||||
soup=BeautifulSoup(r.text,"lxml")
|
|
||||||
out=[]
|
|
||||||
for a in soup.find_all("a"):
|
for a in soup.find_all("a"):
|
||||||
t=a.get_text(strip=True)
|
title = a.get_text(strip=True)
|
||||||
if t and any(k in t for k in ["四六级","CET","成绩","报名","考试","准考证"]):
|
href = a.get("href")
|
||||||
out.append({"title":t,"url":a.get("href"),"category":category(t)})
|
|
||||||
return out[:20]
|
|
||||||
|
|
||||||
def load():
|
if title and any(k in title for k in [
|
||||||
if not os.path.exists(STATE): return []
|
"四六级", "CET", "成绩",
|
||||||
return json.load(open(STATE,encoding="utf-8")).get("items",[])
|
"报名", "考试", "准考证"
|
||||||
|
]):
|
||||||
|
result.append({
|
||||||
|
"title": title,
|
||||||
|
"url": href
|
||||||
|
})
|
||||||
|
|
||||||
def save(items):
|
return result[:20]
|
||||||
os.makedirs("data",exist_ok=True)
|
|
||||||
json.dump({"items":items,"last_check":datetime.now(timezone.utc).isoformat()},open(STATE,"w",encoding="utf-8"),ensure_ascii=False,indent=2)
|
except Exception as e:
|
||||||
|
print(f"获取失败 {retry + 1}/3:", e)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def make_hash(item):
|
||||||
|
return hashlib.sha256(
|
||||||
|
(item["title"] + str(item["url"])).encode("utf-8")
|
||||||
|
).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def load_state():
|
||||||
|
if not os.path.exists(STATE_FILE):
|
||||||
|
return {"items": []}
|
||||||
|
|
||||||
|
with open(STATE_FILE, "r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def save_state(items):
|
||||||
|
os.makedirs("data", exist_ok=True)
|
||||||
|
|
||||||
|
with open(STATE_FILE, "w", encoding="utf-8") as f:
|
||||||
|
json.dump({
|
||||||
|
"items": items,
|
||||||
|
"last_check_utc": datetime.now(timezone.utc).isoformat()
|
||||||
|
}, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
def h(x):
|
|
||||||
return hashlib.sha256((x["title"]+str(x["url"])).encode()).hexdigest()
|
|
||||||
|
|
||||||
def push(items):
|
def push(items):
|
||||||
token=os.getenv("PUSH_TOKEN")
|
token = os.getenv("PUSH_TOKEN")
|
||||||
if not token:return
|
|
||||||
msg="📢 CET四六级新通知\n\n"
|
if not token:
|
||||||
for x in items:
|
print("ERROR: PUSH_TOKEN 未配置")
|
||||||
msg+=f"{x['category']}\n{x['title']}\n{x['url']}\n\n"
|
return False
|
||||||
requests.post("https://push.showdoc.com.cn/server/api/push",
|
|
||||||
data={"title":"四六级新通知","content":msg,"token":token},timeout=10)
|
content = "📢 CET四六级新通知\n\n"
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
content += (
|
||||||
|
f"【{item['title']}】\n"
|
||||||
|
f"{item['url']}\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ShowDoc Push token 在 URL 路径中
|
||||||
|
url = f"https://push.showdoc.com.cn/server/api/push/{token}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
url,
|
||||||
|
data={
|
||||||
|
"title": "四六级新通知",
|
||||||
|
"content": content
|
||||||
|
},
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Push status:", r.status_code)
|
||||||
|
print("Push response:", r.text)
|
||||||
|
|
||||||
|
return r.status_code == 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print("Push异常:", e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
current=fetch()
|
notices = fetch_notices()
|
||||||
old={x.get("hash") for x in load()}
|
|
||||||
fresh=[]
|
|
||||||
saved=[]
|
|
||||||
for x in current:
|
|
||||||
x["hash"]=h(x)
|
|
||||||
saved.append(x)
|
|
||||||
if x["hash"] not in old:
|
|
||||||
fresh.append(x)
|
|
||||||
if fresh: push(fresh)
|
|
||||||
save(saved)
|
|
||||||
|
|
||||||
if __name__=="__main__":
|
if notices is None:
|
||||||
|
print("获取公告失败")
|
||||||
|
return
|
||||||
|
|
||||||
|
state = load_state()
|
||||||
|
|
||||||
|
old_hash = {
|
||||||
|
x["hash"]
|
||||||
|
for x in state.get("items", [])
|
||||||
|
}
|
||||||
|
|
||||||
|
force = os.getenv("FORCE_PUSH", "false").lower() == "true"
|
||||||
|
|
||||||
|
new_items = []
|
||||||
|
|
||||||
|
for item in notices:
|
||||||
|
item["hash"] = make_hash(item)
|
||||||
|
|
||||||
|
if force or item["hash"] not in old_hash:
|
||||||
|
new_items.append(item)
|
||||||
|
|
||||||
|
if new_items:
|
||||||
|
print("发现通知:", len(new_items))
|
||||||
|
|
||||||
|
if push(new_items):
|
||||||
|
print("推送成功")
|
||||||
|
save_state(notices)
|
||||||
|
else:
|
||||||
|
print("推送失败,不更新状态")
|
||||||
|
else:
|
||||||
|
print("无新增")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
Reference in New Issue
Block a user