diff --git a/docs/traditional-media.md b/docs/traditional-media.md
index 7e3c313d19..c47546cc0b 100644
--- a/docs/traditional-media.md
+++ b/docs/traditional-media.md
@@ -1795,6 +1795,38 @@ category 对应的关键词有
+## 天下雜誌
+
+### 最新上線
+
+
+
+### 主頻道
+
+
+
+| 主頻道名稱 | 主頻道 ID |
+| ----- | ------ |
+| 財經 | 8 |
+| 產業 | 7 |
+| 國際 | 9 |
+| 管理 | 10 |
+| 環境 | 12 |
+| 教育 | 13 |
+| 人物 | 14 |
+| 政治社會 | 77 |
+| 調查排行 | 15 |
+| 健康關係 | 79 |
+| 時尚品味 | 11 |
+| 運動生活 | 103 |
+| 重磅外媒 | 16 |
+
+
+
+### 子頻道
+
+
+
## 卫报 The Guardian
通过提取文章全文,以提供比官方源更佳的阅读体验。
diff --git a/lib/v2/cw/maintainer.js b/lib/v2/cw/maintainer.js
new file mode 100644
index 0000000000..51a7e83af2
--- /dev/null
+++ b/lib/v2/cw/maintainer.js
@@ -0,0 +1,5 @@
+module.exports = {
+ '/master/:channel': ['TonyRL'],
+ '/sub/:channel': ['TonyRL'],
+ '/today': ['TonyRL'],
+};
diff --git a/lib/v2/cw/master.js b/lib/v2/cw/master.js
new file mode 100644
index 0000000000..6530807d96
--- /dev/null
+++ b/lib/v2/cw/master.js
@@ -0,0 +1,28 @@
+const cheerio = require('cheerio');
+const { baseUrl, cookieJar, got, parseList, parseItems } = require('./utils');
+
+module.exports = async (ctx) => {
+ const { channel } = ctx.params;
+ const pageUrl = `${baseUrl}/masterChannel.action`;
+ const { data: response } = await got(pageUrl, {
+ headers: {
+ Referer: baseUrl,
+ },
+ cookieJar,
+ searchParams: {
+ idMasterChannel: channel,
+ },
+ });
+ const $ = cheerio.load(response);
+
+ const list = parseList($, ctx.query.limit ? Number(ctx.query.limit) : 100);
+ const items = await parseItems(list, ctx.cache.tryGet);
+
+ ctx.state.data = {
+ title: $('head title').text(),
+ description: $('meta[name=description]').attr('content'),
+ image: `${baseUrl}/assets_new/img/fbshare.jpg`,
+ language: $('meta[property="og:locale"]').attr('content'),
+ item: items,
+ };
+};
diff --git a/lib/v2/cw/radar.js b/lib/v2/cw/radar.js
new file mode 100644
index 0000000000..9fa3a27e46
--- /dev/null
+++ b/lib/v2/cw/radar.js
@@ -0,0 +1,25 @@
+module.exports = {
+ 'cw.com.tw': {
+ _name: '天下雜誌',
+ '.': [
+ {
+ title: '最新上線',
+ docs: 'https://docs.rsshub.app/traditional-media.html#tian-xia-za-zhi',
+ source: ['/today', '/'],
+ target: '/cw/today',
+ },
+ {
+ title: '主頻道',
+ docs: 'https://docs.rsshub.app/traditional-media.html#tian-xia-za-zhi',
+ source: ['/masterChannel.action'],
+ target: (_, url) => `/cw/master/${new URL(url).searchParams.get('idMasterChannel')}`,
+ },
+ {
+ title: '子頻道',
+ docs: 'https://docs.rsshub.app/traditional-media.html#tian-xia-za-zhi',
+ source: ['/subchannel.action'],
+ target: (_, url) => `/cw/sub/${new URL(url).searchParams.get('idSubChannel')}`,
+ },
+ ],
+ },
+};
diff --git a/lib/v2/cw/router.js b/lib/v2/cw/router.js
new file mode 100644
index 0000000000..6d7edc690f
--- /dev/null
+++ b/lib/v2/cw/router.js
@@ -0,0 +1,5 @@
+module.exports = (router) => {
+ router.get('/master/:channel', require('./master'));
+ router.get('/sub/:channel', require('./sub'));
+ router.get('/today', require('./today'));
+};
diff --git a/lib/v2/cw/sub.js b/lib/v2/cw/sub.js
new file mode 100644
index 0000000000..7d4d9b0ad2
--- /dev/null
+++ b/lib/v2/cw/sub.js
@@ -0,0 +1,28 @@
+const cheerio = require('cheerio');
+const { baseUrl, cookieJar, got, parseList, parseItems } = require('./utils');
+
+module.exports = async (ctx) => {
+ const { channel } = ctx.params;
+ const pageUrl = `${baseUrl}/subchannel.action`;
+ const { data: response } = await got(pageUrl, {
+ headers: {
+ Referer: baseUrl,
+ },
+ cookieJar,
+ searchParams: {
+ idSubChannel: channel,
+ },
+ });
+ const $ = cheerio.load(response);
+
+ const list = parseList($, ctx.query.limit ? Number(ctx.query.limit) : 100);
+ const items = await parseItems(list, ctx.cache.tryGet);
+
+ ctx.state.data = {
+ title: $('head title').text(),
+ description: $('meta[name=description]').attr('content'),
+ image: `${baseUrl}/assets_new/img/fbshare.jpg`,
+ language: $('meta[property="og:locale"]').attr('content'),
+ item: items,
+ };
+};
diff --git a/lib/v2/cw/today.js b/lib/v2/cw/today.js
new file mode 100644
index 0000000000..02fd199b97
--- /dev/null
+++ b/lib/v2/cw/today.js
@@ -0,0 +1,24 @@
+const cheerio = require('cheerio');
+const { baseUrl, cookieJar, got, parseList, parseItems } = require('./utils');
+
+module.exports = async (ctx) => {
+ const pageUrl = `${baseUrl}/today`;
+ const { data: response } = await got(pageUrl, {
+ headers: {
+ Referer: baseUrl,
+ },
+ cookieJar,
+ });
+ const $ = cheerio.load(response);
+
+ const list = parseList(response, ctx.query.limit ? Number(ctx.query.limit) : 100);
+ const items = await parseItems(list, ctx.cache.tryGet);
+
+ ctx.state.data = {
+ title: $('head title').text(),
+ description: $('meta[name=description]').attr('content'),
+ image: `${baseUrl}/assets_new/img/fbshare.jpg`,
+ language: $('meta[property="og:locale"]').attr('content'),
+ item: items,
+ };
+};
diff --git a/lib/v2/cw/utils.js b/lib/v2/cw/utils.js
new file mode 100644
index 0000000000..e3263c870d
--- /dev/null
+++ b/lib/v2/cw/utils.js
@@ -0,0 +1,63 @@
+const cheerio = require('cheerio');
+const { parseDate } = require('@/utils/parse-date');
+const { CookieJar } = require('tough-cookie');
+const cookieJar = new CookieJar();
+const config = require('@/config').value;
+
+const baseUrl = 'https://www.cw.com.tw';
+
+const got = require('@/utils/got').extend({
+ headers: {
+ 'User-Agent': config.trueUA,
+ },
+});
+
+const parseList = ($, limit) =>
+ $('.caption')
+ .toArray()
+ .map((item) => {
+ item = $(item);
+ return {
+ title: item.find('h3').text(),
+ link: item.find('h3 a').attr('href'),
+ pubDate: parseDate(item.find('time').text()),
+ };
+ })
+ .slice(0, limit);
+
+const parseItems = (list, tryGet) =>
+ Promise.all(
+ list.map((item) =>
+ tryGet(item.link, async () => {
+ const { data: response } = await got(item.link, {
+ cookieJar,
+ });
+
+ const $ = cheerio.load(response);
+ const meta = JSON.parse($('head script[type="application/ld+json"]').eq(0).text());
+ $('.article__head .breadcrumb, .article__head h1, .article__provideViews, .ad').remove();
+ $('img.lazyload').each((_, img) => {
+ if (img.attribs['data-src']) {
+ img.attribs.src = img.attribs['data-src'];
+ delete img.attribs['data-src'];
+ }
+ });
+
+ item.title = $('head title').text();
+ item.category = $('meta[name=keywords]').attr('content').split(',');
+ item.pubDate = parseDate(meta.datePublished);
+ item.author = meta.author.name.replace(',', ' ') || meta.publisher.name;
+ item.description = $('.article__head .container').html() + $('.article__content').html();
+
+ return item;
+ })
+ )
+ );
+
+module.exports = {
+ baseUrl,
+ cookieJar,
+ got,
+ parseList,
+ parseItems,
+};