2019-10-23 12:11:47 +08:00
|
|
|
|
const fs = require('fs');
|
2020-11-26 18:41:15 +08:00
|
|
|
|
const fetch = require('node-fetch');
|
2019-10-24 15:26:07 +08:00
|
|
|
|
const markdownTable = require('markdown-table');
|
2019-10-23 12:11:47 +08:00
|
|
|
|
|
|
|
|
|
const data = fs.readFileSync('./blogs-original.csv');
|
|
|
|
|
const rows = data.toString().split('\n');
|
|
|
|
|
|
|
|
|
|
const table = rows
|
2019-10-24 15:26:07 +08:00
|
|
|
|
.map(row => row.split(',').map(column => column.trim()))
|
2019-12-10 16:57:31 +08:00
|
|
|
|
.filter((row, i) => row.length === 4 && i !== 0)
|
2020-11-26 16:58:46 +08:00
|
|
|
|
.map(row => row.push(-1) && row) // row[4] to store count of RSS subscribers
|
|
|
|
|
|
|
|
|
|
async function getLatestSubstatsRes(feedUrl, cacheFilename) {
|
2020-11-26 18:20:40 +08:00
|
|
|
|
const substatsAPI = `https://api.spencerwoo.com/substats/?source=feedly|inoreader|feedsPub&queryKey=${feedUrl}`;
|
2020-11-26 16:58:46 +08:00
|
|
|
|
|
|
|
|
|
try {
|
2020-11-26 18:41:15 +08:00
|
|
|
|
const substatsRes = await fetch(substatsAPI, { timeout: 5000 }); // wait for 5s
|
|
|
|
|
let data = await substatsRes.json();
|
|
|
|
|
if (data.status === 200) {
|
2020-11-26 16:58:46 +08:00
|
|
|
|
// Mark lastModified
|
|
|
|
|
data['lastModified'] = new Date().getTime();
|
|
|
|
|
const totalSubs = data.data.totalSubs;
|
|
|
|
|
// Save to cache
|
|
|
|
|
fs.writeFileSync(cacheFilename, JSON.stringify(data));
|
|
|
|
|
return totalSubs;
|
|
|
|
|
} else {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
2020-11-26 18:41:15 +08:00
|
|
|
|
console.log(`Failed to fetch: ${feedUrl}`);
|
2020-11-26 16:58:46 +08:00
|
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function getTotalSubs(feedUrl, index) {
|
|
|
|
|
const cacheFilename = `./cache/${encodeURIComponent(feedUrl)}.json`;
|
|
|
|
|
let totalSubs = -1;
|
|
|
|
|
let fromCache = false;
|
|
|
|
|
|
|
|
|
|
if (fs.existsSync(cacheFilename)) {
|
|
|
|
|
const cachedRes = JSON.parse(fs.readFileSync(cacheFilename, 'utf8'));
|
2020-11-26 17:18:09 +08:00
|
|
|
|
// cache available within 5 days
|
|
|
|
|
const cacheExpired = 86400 * 1000 * 5 < (new Date().getTime() - parseInt(cachedRes.lastModified)) ? true : false;
|
2020-11-26 16:58:46 +08:00
|
|
|
|
|
|
|
|
|
totalSubs = !cacheExpired ?
|
|
|
|
|
cachedRes.data.totalSubs :
|
|
|
|
|
await getLatestSubstatsRes(feedUrl, cacheFilename);
|
|
|
|
|
fromCache = !cacheExpired;
|
|
|
|
|
} else {
|
|
|
|
|
totalSubs = await getLatestSubstatsRes(feedUrl, cacheFilename);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { feedUrl, index, totalSubs, fromCache };
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-24 15:26:07 +08:00
|
|
|
|
|
|
|
|
|
async function getResultAndUpdateREADME() {
|
2020-11-26 16:58:46 +08:00
|
|
|
|
const feedTable = table
|
|
|
|
|
.map((row, index) => row.push(index) && row) // row[5]: original table index
|
|
|
|
|
.filter(row => row[2]) // Have RSS
|
|
|
|
|
|
|
|
|
|
while(feedTable.length) {
|
|
|
|
|
const resPromise = [];
|
|
|
|
|
|
|
|
|
|
feedTable.splice(-20, 20).forEach(row => {
|
|
|
|
|
resPromise.push(getTotalSubs(row[2], row[5]));
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await Promise.allSettled(resPromise).then(responses => {
|
|
|
|
|
responses.forEach(res => {
|
|
|
|
|
if (res.status === 'fulfilled') { // succeeded
|
2020-11-26 18:20:40 +08:00
|
|
|
|
// console.debug(`INFO: ${JSON.stringify(res.value)}`);
|
2020-11-26 16:58:46 +08:00
|
|
|
|
table[res.value.index][4] = res.value.totalSubs;
|
2019-10-29 09:53:46 +08:00
|
|
|
|
}
|
2020-11-26 16:58:46 +08:00
|
|
|
|
if (res.status === 'rejected') { // failed
|
|
|
|
|
// no-op
|
2019-12-10 16:57:31 +08:00
|
|
|
|
}
|
2020-11-26 16:58:46 +08:00
|
|
|
|
})
|
|
|
|
|
})
|
2019-10-24 15:26:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-26 16:58:46 +08:00
|
|
|
|
// Sort by RSS subscribers count first, then by alphanumeric
|
|
|
|
|
table.sort((a, b) => (b[4] - a[4]) || (a[0] - b[0]));
|
2019-10-24 15:26:07 +08:00
|
|
|
|
|
|
|
|
|
const newTable = table.map(row => {
|
2019-12-10 16:57:31 +08:00
|
|
|
|
const subscribeCount = row[4] >= 1000 ? row[4] : (row[4] + '').replace(/\d/g, '*');
|
2020-11-26 16:58:46 +08:00
|
|
|
|
return [
|
|
|
|
|
row[4] >= 0 ? `[![](https://badgen.net/badge/icon/${subscribeCount}?icon=rss&label)](${row[2]})` : '',
|
|
|
|
|
row[0].replace(/\|/g, '|'),
|
|
|
|
|
row[1],
|
|
|
|
|
row[3]
|
|
|
|
|
]
|
2019-11-01 17:23:40 +08:00
|
|
|
|
});
|
2019-10-24 15:26:07 +08:00
|
|
|
|
|
|
|
|
|
// update README
|
2019-10-24 16:23:55 +08:00
|
|
|
|
const tableContentInMD = markdownTable([['RSS 订阅数', '简介', '链接', '标签'], ...newTable]);
|
2019-10-24 15:26:07 +08:00
|
|
|
|
|
2020-03-31 19:03:36 +08:00
|
|
|
|
const readmeContent = `
|
|
|
|
|
|
2021-10-25 13:47:59 +08:00
|
|
|
|
> Update: 社会化的 RSS 阅读器上线了,欢迎试用—— [Feeds Pub](https://feeds.pub)
|
2020-03-31 19:03:36 +08:00
|
|
|
|
|
|
|
|
|
# 中文独立博客列表
|
2019-10-24 15:26:07 +08:00
|
|
|
|
|
2020-08-18 10:30:22 +08:00
|
|
|
|
[![](https://badgen.net/badge/icon/Website?icon=chrome&label)](https://feeds.pub/cn-indie) [![](https://badgen.net/badge/icon/Telegram?icon=telegram&label)](https://t.me/indieBlogs) [![](https://badgen.net/badge/icon/Patrons/orange?icon=patreon&label)](https://www.patreon.com/timqian) [![](https://badgen.net/badge/icon/Blog?icon=chrome&label)](https://blog.t9t.io/cn-indie-blogs-2019-10-29/)
|
2019-10-24 15:26:07 +08:00
|
|
|
|
|
2021-09-22 15:40:57 +08:00
|
|
|
|
## Sponsors
|
|
|
|
|
|
|
|
|
|
<a href="https://bytebase.com/"> <img height="80px" src="https://i.v2ex.co/3VSATzOl.png" /> </a>
|
|
|
|
|
|
|
|
|
|
[Madao](https://madao.me/) | [SecondState - Rust and WebAssembly in Node.js](https://bit.ly/3gfWwps)
|
|
|
|
|
|
|
|
|
|
[Become a sponsor](https://www.patreon.com/timqian)
|
|
|
|
|
|
2019-10-24 15:26:07 +08:00
|
|
|
|
## 目录
|
|
|
|
|
|
|
|
|
|
- [博客列表](#博客列表)
|
|
|
|
|
- [什么是独立博客](#什么是独立博客)
|
|
|
|
|
- [如何提交](#如何提交)
|
|
|
|
|
- [为什么要收集这张列表](#为什么要收集这张列表)
|
|
|
|
|
|
|
|
|
|
## 博客列表
|
|
|
|
|
|
2021-10-25 13:47:59 +08:00
|
|
|
|
> 暂时根据各 RSS 服务订阅数据排了个先后顺序。 欢迎加入 [Telegram 群](https://t.me/indieBlogs) 讨论如何更好地组织和利用这个列表
|
2019-10-24 15:26:07 +08:00
|
|
|
|
|
|
|
|
|
${tableContentInMD}
|
|
|
|
|
|
|
|
|
|
## 什么是独立博客
|
|
|
|
|
|
|
|
|
|
- 拥有自己的域名
|
|
|
|
|
- 作者本人原创内容
|
|
|
|
|
|
|
|
|
|
### 如何提交
|
|
|
|
|
|
2021-10-25 13:47:59 +08:00
|
|
|
|
1. 在 [./blogs-original.csv](./blogs-original.csv) 中填入博客的 名称、URL、RSS以及标签
|
2019-10-24 15:26:07 +08:00
|
|
|
|
2. 提交 PR
|
2020-08-31 10:28:52 +08:00
|
|
|
|
3. (自动) PR 被 merge 之后 README 通过 [./script.js](./script.js) 生成
|
2019-10-24 15:26:07 +08:00
|
|
|
|
|
|
|
|
|
## 为什么要收集这张列表
|
|
|
|
|
|
2021-10-25 13:47:59 +08:00
|
|
|
|
不止一次听到有人说:“在中国, 独立博客的时代已经过去了”。确实,很多博主都转到了公众号,知乎专栏,小密圈,微博……
|
|
|
|
|
这些平台读者比较多、他们的推荐算法可以让你的内容被更多人看到。
|
|
|
|
|
|
|
|
|
|
但我还是更喜欢独立博客,因为有属于自己的域名,因为可以自由地排版,自由地说话。
|
2019-10-24 15:26:07 +08:00
|
|
|
|
|
2021-10-25 13:47:59 +08:00
|
|
|
|
不得不承认,独立博客在如何获取新读者方面确实存在问题。“酒香也怕巷子深”,同样的内容放在自己的博客和上述的“自媒体平台”上,哪怕有自己的主动宣传,读者的增长速度看起来也远不及自媒体平台上的增粉速度,对吧?
|
2019-10-24 15:26:07 +08:00
|
|
|
|
|
2021-10-25 13:47:59 +08:00
|
|
|
|
是否可以做一个工具,可以连接这些独立博主,在保持独立博客的自由的同时,组织一个独立博客的创作和读者群体,让独立博客们也有一个稳定的被发现的渠道。这个工具可能是一个带个性化推荐系统的 RSS 客户端,可能是一个类似微博、twitter 但是主要内容是独立博客的新东西,读者可以点赞,评论。可以知道我们 follow 的博主 follow 了谁……
|
2019-10-24 15:26:07 +08:00
|
|
|
|
|
2021-10-25 13:47:59 +08:00
|
|
|
|
这个列表是一个开始,先把独立博客们收集起来。欢迎加入 [Telegram 群](https://t.me/indieBlogs)一起思考和讨论如何构建这样一个工具。或者你也可以通过 email [订阅更新](https://mailchi.mp/7585311373a3/indieblogs)
|
2019-10-29 09:53:46 +08:00
|
|
|
|
|
|
|
|
|
## Thanks
|
|
|
|
|
|
|
|
|
|
- https://feedly.com
|
|
|
|
|
- t9t.io community: https://wewe.t9t.io/chat/t9t.io%20community%202 https://wewe.t9t.io/chat/t9t.io%20community
|
|
|
|
|
- https://github.com/DIYgod/RSSHub
|
|
|
|
|
- https://ohmyrss.com/
|
|
|
|
|
- https://github.com/tangqiaoboy/iOSBlogCN
|
2019-10-30 16:23:14 +08:00
|
|
|
|
- https://www.zhihu.com/question/19928148
|
2019-11-01 11:28:09 +08:00
|
|
|
|
|
|
|
|
|
## 博客构建工具推荐
|
|
|
|
|
|
2021-02-19 09:32:50 +08:00
|
|
|
|
- [blogdown](https://github.com/rstudio/blogdown)
|
2020-11-17 16:14:11 +08:00
|
|
|
|
- [Gatsby](https://gatsbyjs.org/)
|
|
|
|
|
- [Ghost](https://ghost.org/)
|
|
|
|
|
- [Gride](https://gridea.dev/)
|
|
|
|
|
- [Hexo](https://hexo.io/)
|
|
|
|
|
- [Hugo](https://gohugo.io/)
|
|
|
|
|
- [Jekyll](https://jekyllrb.com/)
|
2021-02-19 09:32:50 +08:00
|
|
|
|
- [Pelican](https://blog.getpelican.com/)
|
2020-11-17 16:14:11 +08:00
|
|
|
|
- [Saber](https://saber.land/)
|
|
|
|
|
- [Typecho](https://typecho.org)
|
|
|
|
|
- [Vuepress](https://vuepress.vuejs.org/)
|
|
|
|
|
- [Wordpress](https://wordpress.com/)
|
|
|
|
|
- [Wowchemy](https://wowchemy.com)
|
2021-03-22 09:54:10 +08:00
|
|
|
|
|
|
|
|
|
## 博客部署工具推荐
|
|
|
|
|
|
|
|
|
|
- [Netlify](https://www.netlify.com/)
|
|
|
|
|
- [Vercel](https://vercel.com/)
|
2019-11-01 11:28:09 +08:00
|
|
|
|
`
|
2019-10-24 15:26:07 +08:00
|
|
|
|
|
|
|
|
|
fs.writeFileSync('./README.md', readmeContent, 'utf8');
|
|
|
|
|
|
|
|
|
|
}
|
2019-10-23 12:11:47 +08:00
|
|
|
|
|
2019-10-24 15:26:07 +08:00
|
|
|
|
getResultAndUpdateREADME()
|