add nowcoder
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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/页面结构变化,可能会暂时影响抓取到的比赛数量。
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user