add nowcoder

This commit is contained in:
hanyixuanten
2026-07-20 20:02:29 +08:00
parent 964f186cd6
commit 0498544fcb
7 changed files with 166 additions and 2 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ Guidance for AI coding agents working in this repository.
## Project Shape
- `fetch_contests.py` is the data generator. It fetches contests, normalizes records with `platform`, `title`, `start_time`, `end_time`, `status`, and `url`, then writes JSON files in the repository root.
- Current contest providers are Codeforces, AtCoder, and UOJ.
- Current contest providers are Codeforces, AtCoder, UOJ, and Nowcoder.
- `.github/workflows/deploy.yml` is the GitHub Actions automation. It installs Python dependencies, runs `fetch_contests.py`, and commits generated JSON changes.
- `server/index.php` is the serving/display layer. It reads the public raw `contests.json` and `contests_all.json`, caches them locally, and renders upcoming plus recently finished contests.
- User-facing documentation lives in [README.md](README.md) and [README_zh.md](README_zh.md). Keep both languages in sync.
+2
View File
@@ -9,6 +9,7 @@ Fetch programming contest schedules from several online judge platforms and expo
- Codeforces
- AtCoder
- UOJ
- Nowcoder
## Output Files
@@ -93,4 +94,5 @@ AI coding agents and maintainers should follow [AGENTS.md](AGENTS.md) when chang
- AtCoder uses the AtCoder Problems API and falls back to the official AtCoder contest page for upcoming contests when needed.
- UOJ is parsed from its public contests page, including the timeanddate duration parameters used by contest links.
- Nowcoder is parsed from the embedded contest metadata on its VIP contest index page.
- Network or upstream API changes can temporarily reduce the number of fetched contests.
+2
View File
@@ -9,6 +9,7 @@
- Codeforces
- AtCoder
- UOJ
- Nowcoder
## 输出文件
@@ -93,4 +94,5 @@ AI coding agents 和维护者在修改比赛提供商、输出契约、工作流
- AtCoder 使用 AtCoder Problems API;在需要时会回退解析 AtCoder 官方比赛页面以获取即将开始的比赛。
- UOJ 从公开比赛列表页面解析,并读取比赛链接中的 timeanddate 持续时间参数。
- Nowcoder 从牛客竞赛 VIP 列表页内嵌的比赛元数据解析。
- 网络问题或上游 API/页面结构变化,可能会暂时影响抓取到的比赛数量。
+48
View File
@@ -1,4 +1,20 @@
[
{
"platform": "Nowcoder",
"title": "2026牛客暑期多校训练营2",
"start_time": 1784692800,
"end_time": 1784710800,
"status": "upcoming",
"url": "https://ac.nowcoder.com/acm/contest/133877"
},
{
"platform": "Nowcoder",
"title": "2026牛客暑期多校训练营3",
"start_time": 1784865600,
"end_time": 1784883600,
"status": "upcoming",
"url": "https://ac.nowcoder.com/acm/contest/133878"
},
{
"platform": "AtCoder",
"title": "AtCoder Beginner Contest 468",
@@ -7,6 +23,14 @@
"status": "upcoming",
"url": "https://atcoder.jp/contests/abc468"
},
{
"platform": "Nowcoder",
"title": "牛客周赛 Round 154",
"start_time": 1785063600,
"end_time": 1785070800,
"status": "upcoming",
"url": "https://ac.nowcoder.com/acm/contest/137840"
},
{
"platform": "Codeforces",
"title": "Codeforces Round (Div. 1)",
@@ -23,6 +47,22 @@
"status": "upcoming",
"url": "https://codeforces.com/contests/2250"
},
{
"platform": "Nowcoder",
"title": "2026牛客暑期多校训练营4",
"start_time": 1785297600,
"end_time": 1785315600,
"status": "upcoming",
"url": "https://ac.nowcoder.com/acm/contest/133879"
},
{
"platform": "Nowcoder",
"title": "2026牛客暑期多校训练营5",
"start_time": 1785470400,
"end_time": 1785488400,
"status": "upcoming",
"url": "https://ac.nowcoder.com/acm/contest/133880"
},
{
"platform": "AtCoder",
"title": "RECRUIT Nihonbashi Half Marathon 2026 Summer (AtCoder Heuristic Contest 069)",
@@ -47,6 +87,14 @@
"status": "upcoming",
"url": "https://atcoder.jp/contests/abc469"
},
{
"platform": "Nowcoder",
"title": "牛客周赛 Round 155",
"start_time": 1785668400,
"end_time": 1785675600,
"status": "upcoming",
"url": "https://ac.nowcoder.com/acm/contest/138240"
},
{
"platform": "AtCoder",
"title": "UNIQUE VISION Programming Contest 2026 Summer (AtCoder Regular Contest 226)",
+72
View File
@@ -7,6 +7,14 @@
"status": "finished",
"url": "https://atcoder.jp/contests/abc463"
},
{
"platform": "Nowcoder",
"title": "牛客周赛 Round 149",
"start_time": 1782039600,
"end_time": 1782046800,
"status": "finished",
"url": "https://ac.nowcoder.com/acm/contest/136720"
},
{
"platform": "AtCoder",
"title": "AtCoder Heuristic Contest 067",
@@ -207,6 +215,14 @@
"status": "finished",
"url": "https://atcoder.jp/contests/adt_easy_20260626_2"
},
{
"platform": "Nowcoder",
"title": "牛客小白月赛134",
"start_time": 1782471600,
"end_time": 1782478800,
"status": "finished",
"url": "https://ac.nowcoder.com/acm/contest/137072"
},
{
"platform": "AtCoder",
"title": "AtCoder Weekday Contest 0100 Beta",
@@ -239,6 +255,14 @@
"status": "finished",
"url": "https://codeforces.com/contests/2240"
},
{
"platform": "Nowcoder",
"title": "牛客周赛 Round 150",
"start_time": 1782644400,
"end_time": 1782651600,
"status": "finished",
"url": "https://ac.nowcoder.com/acm/contest/137073"
},
{
"platform": "AtCoder",
"title": "AtCoder Regular Contest 223",
@@ -463,6 +487,14 @@
"status": "finished",
"url": "https://atcoder.jp/contests/awc0105"
},
{
"platform": "Nowcoder",
"title": "牛客小白月赛135",
"start_time": 1783076400,
"end_time": 1783083600,
"status": "finished",
"url": "https://ac.nowcoder.com/acm/contest/137264"
},
{
"platform": "AtCoder",
"title": "AtCoder Beginner Contest 465",
@@ -471,6 +503,14 @@
"status": "finished",
"url": "https://atcoder.jp/contests/abc465"
},
{
"platform": "Nowcoder",
"title": "牛客周赛 Round 151",
"start_time": 1783249200,
"end_time": 1783256400,
"status": "finished",
"url": "https://ac.nowcoder.com/acm/contest/137267"
},
{
"platform": "AtCoder",
"title": "AtCoder Daily Training ALL 2026/07/06 16:00start",
@@ -719,6 +759,14 @@
"status": "finished",
"url": "https://atcoder.jp/contests/awc0110"
},
{
"platform": "Nowcoder",
"title": "牛客挑战赛90",
"start_time": 1783681200,
"end_time": 1783692000,
"status": "finished",
"url": "https://ac.nowcoder.com/acm/contest/137418"
},
{
"platform": "UOJ",
"title": "UOJ NOI Round #10 Day1",
@@ -743,6 +791,14 @@
"status": "finished",
"url": "https://uoj.ac/contest/111"
},
{
"platform": "Nowcoder",
"title": "牛客周赛 Round 152",
"start_time": 1783854000,
"end_time": 1783861200,
"status": "finished",
"url": "https://ac.nowcoder.com/acm/contest/137448"
},
{
"platform": "AtCoder",
"title": "AtCoder Regular Contest-- 224",
@@ -951,6 +1007,14 @@
"status": "finished",
"url": "https://atcoder.jp/contests/adt_easy_20260717_1"
},
{
"platform": "Nowcoder",
"title": "2026牛客暑期多校训练营1",
"start_time": 1784260800,
"end_time": 1784278800,
"status": "finished",
"url": "https://ac.nowcoder.com/acm/contest/133876"
},
{
"platform": "AtCoder",
"title": "AtCoder Daily Training ALL 2026/07/17 18:00start",
@@ -999,6 +1063,14 @@
"status": "finished",
"url": "https://codeforces.com/contests/2247"
},
{
"platform": "Nowcoder",
"title": "牛客周赛 Round 153",
"start_time": 1784458800,
"end_time": 1784466000,
"status": "finished",
"url": "https://ac.nowcoder.com/acm/contest/137561"
},
{
"platform": "AtCoder",
"title": "AtCoder Regular Contest 225",
+38
View File
@@ -2,6 +2,7 @@ import requests
import json
import time
import re
import html
from datetime import datetime, timezone
from urllib.parse import urljoin, urlparse, parse_qs
from bs4 import BeautifulSoup
@@ -187,11 +188,47 @@ def fetch_uoj(include_all=False, min_end_time=None):
print(f"UOJ error: {e}")
return []
# ---------- Nowcoder ----------
def fetch_nowcoder(include_all=False, min_end_time=None):
url = "https://ac.nowcoder.com/acm/contest/vip-index"
try:
resp = requests.get(url, headers=HEADERS, timeout=15)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
contests = []
now = now_ts()
for item in soup.select(".platform-item[data-json]"):
try:
data = json.loads(html.unescape(item["data-json"]))
except (TypeError, ValueError):
continue
contest_id = data.get("contestId")
title = data.get("contestName")
start = data.get("contestStartTime")
end = data.get("contestEndTime")
if not contest_id or not title or not start or not end:
continue
start = int(start) // 1000
end = int(end) // 1000
if (include_all and (min_end_time is None or end >= min_end_time)) or end > now:
contests.append(make_contest(
"Nowcoder",
title,
start,
end,
f"https://ac.nowcoder.com/acm/contest/{contest_id}"
))
return contests
except Exception as e:
print(f"Nowcoder error: {e}")
return []
def main():
all_contests = []
all_contests.extend(fetch_codeforces())
all_contests.extend(fetch_atcoder())
all_contests.extend(fetch_uoj())
all_contests.extend(fetch_nowcoder())
all_contests.sort(key=lambda x: x["start_time"])
@@ -204,6 +241,7 @@ def main():
recent_finished_contests.extend(fetch_codeforces(include_all=True))
recent_finished_contests.extend(fetch_atcoder(include_all=True))
recent_finished_contests.extend(fetch_uoj(include_all=True, min_end_time=recent_finished_min_end_time))
recent_finished_contests.extend(fetch_nowcoder(include_all=True, min_end_time=recent_finished_min_end_time))
recent_finished_contests = filter_recent_finished(deduplicate_contests(recent_finished_contests))
recent_finished_contests.sort(key=lambda x: x["end_time"])
+3 -1
View File
@@ -164,7 +164,8 @@ if ($finished_contests_json === false) {
$platform_class = [
'Codeforces' => 'cf',
'AtCoder' => 'atc',
'UOJ' => 'uoj'
'UOJ' => 'uoj',
'Nowcoder' => 'nowcoder'
];
function h($value) {
@@ -209,6 +210,7 @@ function contest_value($contest, $key, $default) {
.platform.cf { background: #1f8acb; }
.platform.atc { background: #5b8c5a; }
.platform.uoj { background: #c0392b; }
.platform.nowcoder { background: #00a6a6; }
.info { flex: 1; margin-left: 15px; }
.title { font-size: 18px; font-weight: 600; color: #2c3e50; }
.time { font-size: 14px; color: #7f8c8d; margin-top: 5px; }