Repository navigation
Campus Info
ChangYo edited this page Oct 26, 2026
·
3 revisions
| 项 | 值 |
|---|---|
| 平台 |
news.sjtu.edu.cn、jwc.sjtu.edu.cn 等 |
| 认证 | 无(公开网页) |
| 数据方向 | 只读 |
| 难度 | 低 |
| 最后验证 | 2026-10-25 |
| 证据等级 | C(公开网页结构,随时会变) |
这页给出两个可运行的采集示例:交大新闻和教务处通知。其他校园信息源(图书馆、班车、食堂、空教室、场馆)没有稳定公开契约,统一放在待验证清单和资料来源。第二课堂有独立接口,见第二课堂。
Warning
下面的方法依赖 2026-10-25 观察到的 HTML 结构,没有 API 契约。学校改版后解析会失败。解析失败时应保留旧缓存并提示,不要把它当成「没有新闻」。
依赖:npm i cheerio。示例使用 Cheerio 解析 HTML;也可以使用 DOMParser 或正则,但需要自行复核选择器。
入口:https://news.sjtu.edu.cn/
列表链接形如 /zhxw/20261021/226815.html;标题在 <a> 文本里。[实测 2026-10-25]
import * as cheerio from 'cheerio';
const BASE = 'https://news.sjtu.edu.cn';
export async function listNews() {
const response = await fetch(BASE, { redirect: 'follow' });
if (!response.ok) throw new Error(`news list HTTP ${response.status}`);
const $ = cheerio.load(await response.text());
const items = new Map<string, { title: string; url: string }>();
$('a[href^="/zhxw/"]').each((_, element) => {
const href = $(element).attr('href');
const title = $(element).text().replace(/\s+/g, ' ').trim();
if (!href || !title) return;
items.set(href, { title, url: new URL(href, BASE).href });
});
return [...items.values()];
}要点:
- 用
Map去重:首页同一个链接可能出现多次。 - 只收
/zhxw/开头的链接;/jdyw/等导航链接不属于新闻正文。 - 更早的新闻走
/zhxw/index.html的分页;先抓首页,不要一上来全站翻页。
文章页结构(2026-10-25):
- 标题:
.Article-title(回退<title>,去掉_综合新闻_上海交通大学新闻学术网后缀)。 - 正文:
.Article_content。
export async function readNews(url: string) {
const response = await fetch(url);
if (!response.ok) throw new Error(`news article HTTP ${response.status}`);
const $ = cheerio.load(await response.text());
$('script, style').remove();
const title =
$('.Article-title').text().replace(/\s+/g, ' ').trim() ||
$('title').text().split('_')[0]!.trim();
const body = $('.Article_content').text().replace(/\s+/g, ' ').trim();
if (!title || !body) throw new Error('新闻结构已变化:标题或正文为空');
return { title, body, url };
}正文里的图片是相对路径 /resource/upload/...;要显示时用 new URL(src, BASE) 补全,并遵守图片版权。
入口:https://jwc.sjtu.edu.cn/xwtg/tztg.htm
列表项有两类链接:
../info/1222/129211.htm../content.jsp?urltype=news.NewsContentUrl&wbtreeid=1222&wbnewsid=129171
两者都要收;用 new URL(href, listUrl) 解析相对路径。[实测 2026-10-25]
const JWC_LIST = 'https://jwc.sjtu.edu.cn/xwtg/tztg.htm';
export async function listJwcNotices() {
const response = await fetch(JWC_LIST);
if (!response.ok) throw new Error(`jwc list HTTP ${response.status}`);
const $ = cheerio.load(await response.text());
const items = new Map<string, { title: string; url: string }>();
$('a[href*="/info/"], a[href*="content.jsp"]').each((_, element) => {
const href = $(element).attr('href');
const title = $(element).text().replace(/\s+/g, ' ').trim();
if (!href || !title || title === '通知通告') return;
const url = new URL(href, JWC_LIST).href;
items.set(url, { title, url });
});
return [...items.values()];
}分页:
- 列表底部有
tztg/236.htm、tztg/235.htm这类链接;找「下页」或a[href^="tztg/"]。 - 只抓最近 N 页(比如 2 页);不要全站翻。
- 公告可能带附件(PDF/DOC);正文页里找
a[href$=".pdf"]、.doc、.docx、.xls,按需下载。
文章页结构(2026-10-25):
- 标题:
.content-title(回退<title>,去掉-上海交通大学教务处后缀)。 - 正文:
.v_news_content。
export async function readJwcNotice(url: string) {
const response = await fetch(url);
if (!response.ok) throw new Error(`jwc article HTTP ${response.status}`);
const $ = cheerio.load(await response.text());
$('script, style').remove();
const title =
$('.content-title').text().replace(/\s+/g, ' ').trim() ||
$('title').text().split('-')[0]!.trim();
const body = $('.v_news_content').text().replace(/\s+/g, ' ').trim();
if (!title || !body) throw new Error('教务通知结构已变化:标题或正文为空');
return { title, body, url };
}async function collect() {
const news = (await listNews()).slice(0, 10);
const notices = (await listJwcNotices()).slice(0, 10);
const enriched = [];
for (const item of [...news, ...notices]) {
try {
const detail = item.url.includes('jwc.sjtu.edu.cn')
? await readJwcNotice(item.url)
: await readNews(item.url);
enriched.push({ ...item, ...detail, fetchedAt: new Date().toISOString() });
} catch (error) {
// 单条失败不阻塞整体;保留标题/链接,标 error
enriched.push({
...item,
body: null,
error: error instanceof Error ? error.message : String(error),
fetchedAt: new Date().toISOString(),
});
}
}
return enriched;
}存储建议:
- 主键用 URL;保留
title、body、fetchedAt、source。 - 列表页只用来发现新 URL;正文按 URL 增量抓,抓过的不要每次重抓。
- 解析失败时保留旧正文,标
stale/error;不要删。
- 页面没有 API 契约:选择器变化后解析结果会为空;必须保留「解析失败」状态。
-
相对链接:新闻
/zhxw/...、教务../info/...;统一用new URL(href, base)。 - 重复链接:首页/列表里同一篇可能出现多次;按 URL 去重。
-
编码:这两站当前页面是 UTF-8;如果遇到乱码,先看
Content-Type/charset,不要硬转。 - 附件:教务通知常带 PDF/DOC;正文抓不到时,附件可能是关键信息。
- 分页:新闻/教务都有分页;只抓最近 N 页,别全站扫。
- 改版:选择器是 2026-10-25 的观察值;改版后先看页面结构,再改选择器。
- 频率:新闻/教务没有公开限流,但不要高频轮询;小时级足够。
- 版权/隐私:新闻正文、通知附件不要重新分发;个人工具内展示,保留原文链接。
以下源没有稳定公开契约,不在本页给解析代码;需要时按待验证清单逐项验证,参考实现见资料来源:
- 图书馆借阅/座位/研讨间:需要校园会话或读者账号。
- 班车时刻/实时位置:可能以公告/图片发布。
- 食堂菜单/人流:更新频率和数据源不稳定。
- 空教室:依赖学期/教学楼参数。
- 体育场馆:只读场地图/订单可以研究;不要自动预约。
- 新闻列表/正文选择器:
[实测 2026-10-25,news.sjtu.edu.cn] - 教务通知列表/正文选择器:
[实测 2026-10-25,jwc.sjtu.edu.cn/xwtg/tztg.htm] - 其他源:
[社区]SJTU-Geek/sjtu-mcp-server、openclaw-sjtu(只作线索,未在本页验证)
- 2026-10-25:重写。只保留新闻、教务通知两个可执行源;其余移到待验证/来源页。
- 2026-10-26:第二课堂移出本页,改链到独立平台手册。
本 Wiki 记录的是开发实践,不是学校官方承诺。每个结论都标注证据与日期;校园平台会调整字段、权限和登录方式,动手前请按页面里的「最后验证」复核。发现过期内容请提 Issue 或直接改。