diff --git a/docs/traditional-media.md b/docs/traditional-media.md
index bf4c4ffb0..1aaebaa69 100644
--- a/docs/traditional-media.md
+++ b/docs/traditional-media.md
@@ -86,6 +86,12 @@ Solidot 提供的 feed:
+## 北极星电力网
+
+### 北极星环保
+
+
+
## 财新网
> 网站部分内容需要付费订阅, RSS 仅做更新提醒, 不含付费内容.
diff --git a/lib/router.js b/lib/router.js
index ce4c00b5f..d756edde1 100644
--- a/lib/router.js
+++ b/lib/router.js
@@ -1172,6 +1172,9 @@ router.get('/people/opinion/:id', require('./routes/people/opinion'));
router.get('/people/env/:id', require('./routes/people/env'));
router.get('/people/xjpjh/:keyword?/:year?', require('./routes/people/xjpjh'));
+// 北极星电力网
+router.get('/bjx/huanbao', require('./routes/bjx/huanbao'));
+
// gamersky
router.get('/gamersky/news', require('./routes/gamersky/news'));
router.get('/gamersky/ent/:category', require('./routes/gamersky/ent'));
diff --git a/lib/routes/bjx/huanbao.js b/lib/routes/bjx/huanbao.js
new file mode 100644
index 000000000..063c593d9
--- /dev/null
+++ b/lib/routes/bjx/huanbao.js
@@ -0,0 +1,84 @@
+const got = require('@/utils/got');
+const date = require('@/utils/date');
+const cheerio = require('cheerio');
+const url = require('url');
+const iconv = require('iconv-lite');
+
+module.exports = async (ctx) => {
+ const listURL = 'http://huanbao.bjx.com.cn/NewsList';
+ const response = await got({
+ method: 'get',
+ url: listURL,
+ });
+
+ const $ = cheerio.load(response.data);
+ const list = $('.list_main .list_left_ul a')
+ .map((_, a) => $(a).attr('href'))
+ .get();
+
+ const out = await Promise.all(
+ // 服务器禁止单个IP大并发访问,只能少返回几条
+ list.slice(0, 3).map((link) => fetchPage(ctx, link))
+ );
+
+ ctx.state.data = {
+ title: '北极星环保 - 环保行业垂直门户网站',
+ link: listURL,
+ item: out,
+ };
+};
+
+async function fetchPage(ctx, link) {
+ const cache = await ctx.cache.get(link);
+ if (cache) {
+ return JSON.parse(cache);
+ }
+
+ // 可能一篇文章过长会分成多页
+ const pages = [];
+
+ const result = await got.get(link, { responseType: 'buffer' });
+ const $page = cheerio.load(iconv.decode(result.data, 'gbk'));
+ pages.push($page);
+
+ // 如果是有分页链接,则使用顺序加载以保证顺序
+ const pagelinks = $page('.list_detail div.page a');
+ if (pagelinks.length > 0) {
+ for (let i = 0; i < pagelinks.length; i++) {
+ const $a = $page(pagelinks[i]);
+ if (!/^\d+$/.test($a.text().trim())) {
+ continue;
+ }
+ const sublink = url.resolve(link, $a.attr('href'));
+ /* eslint-disable no-await-in-loop */
+ const result = await got.get(sublink, { responseType: 'buffer' });
+ pages.push(cheerio.load(iconv.decode(result.data, 'gbk')));
+ }
+ }
+
+ // 将懒加载的loading图片转换为真实图片
+ pages.forEach(($p) => {
+ $p('#content')
+ .find('img[data-echo]')
+ .each((_, img) => {
+ const $img = $p(img);
+ $img.attr('src', $img.data('echo')).removeAttr('data-echo');
+ });
+ });
+
+ const item = {
+ title: $page('.list_detail > h1').text(),
+ description: pages.reduce((desc, $p) => desc + $p('#content').html(), ''),
+ pubDate: date(
+ $page('.list_detail .list_copy b')
+ .last()
+ .text()
+ ),
+ link,
+ author: $page('.list_detail .list_copy b')
+ .first()
+ .text(),
+ };
+ ctx.cache.set(link, JSON.stringify(item));
+ return item;
+}