Compare commits

...
8 Commits
3 changed files with 160 additions and 75 deletions
+27 -7
View File
@@ -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
View File
@@ -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"
} }
+124 -39
View File
@@ -1,61 +1,146 @@
import os,json,hashlib
import os
import json
import hashlib
from datetime import datetime, timezone from datetime import datetime, timezone
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
URL = "https://cet.neea.edu.cn/" URL = "https://cet.neea.edu.cn/"
STATE="data/state.json" STATE_FILE = "data/state.json"
def category(t):
if "报名" in t:return "📝 报名"
if "成绩" in t:return "📊 成绩"
if "准考证" in t:return "🎫 准考证"
if "考试" in t:return "📝 考试安排"
return "📢 其他"
def fetch(): def fetch_notices():
r=requests.get(URL,headers={"User-Agent":"Mozilla/5.0"},timeout=20) headers = {"User-Agent": "Mozilla/5.0"}
for retry in range(3):
try:
r = requests.get(URL, headers=headers, timeout=20)
r.encoding = "utf-8" r.encoding = "utf-8"
soup = BeautifulSoup(r.text, "lxml") soup = BeautifulSoup(r.text, "lxml")
out=[] result = []
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]
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) 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)
def h(x): with open(STATE_FILE, "w", encoding="utf-8") as f:
return hashlib.sha256((x["title"]+str(x["url"])).encode()).hexdigest() json.dump({
"items": items,
"last_check_utc": datetime.now(timezone.utc).isoformat()
}, f, ensure_ascii=False, indent=2)
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=[] if notices is None:
saved=[] print("获取公告失败")
for x in current: return
x["hash"]=h(x)
saved.append(x) state = load_state()
if x["hash"] not in old:
fresh.append(x) old_hash = {
if fresh: push(fresh) x["hash"]
save(saved) 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__": if __name__ == "__main__":
main() main()