diff --git a/docs/traditional-media.md b/docs/traditional-media.md
index ec94098f4..e90c269ca 100644
--- a/docs/traditional-media.md
+++ b/docs/traditional-media.md
@@ -1629,6 +1629,30 @@ category 对应的关键词有
+## 南方网
+
+### 南方 +(按栏目 ID)
+
+
+
+::: tip 提示
+若此处输入的是栏目 ID(而非南方号 ID),则该接口会返回与输入栏目相关联栏目的文章。例如,输入栏目 ID `38`(广州),则返回的结果还会包含 ID 为 `3547`(市长报道集)的文章。
+:::
+
+1. `pc.nfapp.southcn.com` 下的文章页面,可通过 url 查看,例: 的栏目 ID 为 `13707`。
+2. `static.nfapp.southcn.com` 下的文章页面,可查看网页源代码,搜索 `columnid`。
+3. 列出了部分栏目,`id` 即为栏目 ID。
+
+
+
+### 南方 +(按作者)
+
+
+
+作者的 UUID 只可通过 `static.nfapp.southcn.com` 下的文章页面获取。点击文章下方的作者介绍,进入该作者的个人主页,即可从 url 中获取。
+
+
+
## 南方周末
### 新闻分类
diff --git a/lib/v2/southcn/maintainer.js b/lib/v2/southcn/maintainer.js
new file mode 100644
index 000000000..dd0067dac
--- /dev/null
+++ b/lib/v2/southcn/maintainer.js
@@ -0,0 +1,4 @@
+module.exports = {
+ '/nfapp/column/:column?': ['TimWu007'],
+ '/nfapp/reporter/:reporter': ['TimWu007'],
+};
diff --git a/lib/v2/southcn/nfapp/column.js b/lib/v2/southcn/nfapp/column.js
new file mode 100644
index 000000000..2931c0633
--- /dev/null
+++ b/lib/v2/southcn/nfapp/column.js
@@ -0,0 +1,35 @@
+const got = require('@/utils/got');
+const timezone = require('@/utils/timezone');
+const { parseDate } = require('@/utils/parse-date');
+const { art } = require('@/utils/render');
+const path = require('path');
+const { parseArticle } = require('./utils');
+
+module.exports = async (ctx) => {
+ const columnId = ctx.params.column ?? 38;
+ const currentUrl = `https://api.nfapp.southcn.com/nfplus-manuscript-web/article/list?columnId=${columnId}&nfhSubCount=1&pageNum=1&pageSize=20`; // this api only returns up to 10 articles
+
+ const { data: response } = await got(currentUrl);
+
+ const list = response.data.list
+ .filter((i) => i.articleType === 0)
+ .map((item) => ({
+ title: '【' + item.columnName + '】' + item.title,
+ description: art(path.join(__dirname, '../templates/description.art'), {
+ thumb: item.picMiddle,
+ description: item.summary === '详见内文' ? '' : item.summary,
+ }),
+ pubDate: timezone(parseDate(item.updateTime), +8),
+ link: `http://pc.nfapp.southcn.com/${item.columnId}/${item.articleId}.html`,
+ articleId: item.articleId,
+ shareUrl: item.shareUrl,
+ }));
+
+ const items = await Promise.all(list.map((item) => parseArticle(item, ctx.cache.tryGet)));
+
+ ctx.state.data = {
+ title: `南方+`,
+ link: `https://m.nfapp.southcn.com/${columnId}`,
+ item: items,
+ };
+};
diff --git a/lib/v2/southcn/nfapp/reporter.js b/lib/v2/southcn/nfapp/reporter.js
new file mode 100644
index 000000000..34e5a7741
--- /dev/null
+++ b/lib/v2/southcn/nfapp/reporter.js
@@ -0,0 +1,33 @@
+const got = require('@/utils/got');
+const timezone = require('@/utils/timezone');
+const { parseDate } = require('@/utils/parse-date');
+const { art } = require('@/utils/render');
+const { parseArticle } = require('./utils');
+const path = require('path');
+
+module.exports = async (ctx) => {
+ const reporterId = ctx.params.reporter;
+ const currentUrl = `https://api.nfapp.southcn.com/nanfang_if/reporter/list?reporterUuid=${reporterId}&pageSize=20&pageNo=1&origin=0`;
+
+ const { data: response } = await got(currentUrl);
+
+ const list = response.data.reportInfo.articleInfo.map((item) => ({
+ title: '【' + item.releaseColName + '】' + item.title,
+ description: art(path.join(__dirname, '../templates/description.art'), {
+ thumb: item.picMiddle,
+ description: item.attAbstract,
+ }),
+ pubDate: timezone(parseDate(item.publishtime), +8),
+ link: `http://pc.nfapp.southcn.com/${item.colID}/${item.fileId}.html`,
+ articleId: item.fileId,
+ shareUrl: item.shareUrl,
+ }));
+
+ const items = await Promise.all(list.map((item) => parseArticle(item, ctx.cache.tryGet)));
+
+ ctx.state.data = {
+ title: `南方+ - ${response.data.reportInfo.reporterName}`,
+ link: `https://static.nfapp.southcn.com/apptpl/reporterWorksList/index.html?reporterUuid=${reporterId}`,
+ item: items,
+ };
+};
diff --git a/lib/v2/southcn/nfapp/utils.js b/lib/v2/southcn/nfapp/utils.js
new file mode 100644
index 000000000..d92543215
--- /dev/null
+++ b/lib/v2/southcn/nfapp/utils.js
@@ -0,0 +1,23 @@
+const got = require('@/utils/got');
+const cheerio = require('cheerio');
+
+const parseArticle = (item, tryGet) =>
+ tryGet(item.link, async () => {
+ const detailResponse = await got({
+ method: 'get',
+ url: item.link,
+ });
+
+ const $ = cheerio.load(detailResponse.data);
+
+ $('.article-nfh-icon, .article-crumb, .article-share, .article-copyright').remove();
+
+ item.description += $('#content').html() ?? '';
+ item.author = $('meta[name="author"]').attr('content');
+
+ return item;
+ });
+
+module.exports = {
+ parseArticle,
+};
diff --git a/lib/v2/southcn/radar.js b/lib/v2/southcn/radar.js
new file mode 100644
index 000000000..5155d7402
--- /dev/null
+++ b/lib/v2/southcn/radar.js
@@ -0,0 +1,12 @@
+module.exports = {
+ 'nfapp.southcn.com': {
+ _name: '南方网',
+ '.': [
+ {
+ title: '南方+',
+ docs: 'https://docs.rsshub.app/traditional-media.html#nan-fang-wang',
+ source: ['/'],
+ },
+ ],
+ },
+};
diff --git a/lib/v2/southcn/router.js b/lib/v2/southcn/router.js
new file mode 100644
index 000000000..3fd129644
--- /dev/null
+++ b/lib/v2/southcn/router.js
@@ -0,0 +1,4 @@
+module.exports = (router) => {
+ router.get('/nfapp/column/:column?', require('./nfapp/column'));
+ router.get('/nfapp/reporter/:reporter', require('./nfapp/reporter'));
+};
diff --git a/lib/v2/southcn/templates/description.art b/lib/v2/southcn/templates/description.art
new file mode 100644
index 000000000..e94386a36
--- /dev/null
+++ b/lib/v2/southcn/templates/description.art
@@ -0,0 +1,6 @@
+{{ if thumb }}
+
+{{ /if }}
+{{ if description }}
+
{{ description }}
+{{ /if }}
\ No newline at end of file