一键同步mdnice文章到本地.md

2022/6/13 实用工具

大家好,我是米洛,求三连!关注米洛的测开日记,每天解锁一个小知识!

# mdnice

官网: https://mdnice.com/ (opens new window)

mdnice是一款非常好看的Markdown编辑器,它为大家提供了很漂亮显示效果。但是非常遗憾的是,对于typora用户,或者习惯本地编写.md文件的朋友们来说,这个web版的编辑器,始终好像缺了点什么。

回首望去,这半年的时间,本卷魔已经肝了很多文章了,用一个汽车品牌形容我就是日产

所以无敌卷魔也积累了许多文章,但自己的文章都放到别人那里,始终是不太放心,如果能定期同步到本地,那将是极好的。

# 那么该怎么做呢?

其实mdnice一眼看上去,是可以看到文章列表和文章内容的。

首先我们得先拿到文章列表,再拿到每一篇文章里面的内容,根据文章标题+文本内容,就可以写入到我们本地了。

先来看看mdnice的关键接口:

# 文章列表

打开F12,鼠标在左侧文章滚动几下,可以看到F12里面请求文章列表的接口。

一目了然

可以看到他们的url是https://api.mdnice.com/articles?currentPage=2&pageSize=20, 敏锐的我们可以知道,后面的currentPagepageSize分别是一页的数量和当前的页数。

仔细检查接口返回,发现并没有total这样的字段,所以我们请求的时候,一次性拉个500,1000条的,基本上就能把文章拉完。

# 获取文本内容

因为文章列表接口里面没有带上文本内容信息,所以我们继续暗中观察

当我们切换文章时,会调用具体的文本接口

后面的一串是文章的id

# 绕过登录部分

由于每个人文章都不一样,所以他的获取文章列表接口必须是要登录的。

根据我们的直觉,在他请求的headers里面发现了Auth相关的信息

拿到postman进行测试,测试成功

但是我们要做免登陆还算麻烦,因为他是微信扫码登录,没有提供直接的登录接口,所以我们的脚本还得用户输入一下authorization

# 话不投机,干就完事

    1. 初始化Markdown类

    初始化一个markdown类,把用户要保存的目录和认证信息传进来。

并且存放2个关键接口

    1. 编写获取文章列表方法

通过aiohttp来执行操作

我们用aiohttp加快读取速度,如果不看async with,他和requests.get没啥区别,大家不要想的太难了。

    1. 编写写入文本内容的方法

利用os.path.join拼接目录,给文件加上md后缀。

    1. 编写获取文本内容并写入的方法

挨个儿获取文本内容,并写入到文件里面去。整个过程一气呵成,轻松写意。

这边文本列表的每一条数据,文章名称是title,outid则用来查询文本具体内容。

    1. 编写主方法

建立N个协程,大家一起齐心协力

因为获取文件列表的数据在data字段里面,所以这边定义了data变量。

    1. 编写获取参数的方法

最上面哪一行不加的话会报event loop的错

获取输入的文件目录+auth信息,组成最强战斗机

# 看看时间和效果

刷新一下有点慢,老爷机见谅一下

可以看到文本内容齐全,一共拿到了100多文件

帅就完事儿了~

# 脚本给有需要的人

记得安装aiohttpaiofiles

import asyncio
import os
import sys
import time

import aiofiles
import aiohttp
import requests


class Markdown(object):
    article_list_url = "https://api.mdnice.com/articles?currentPage=1&pageSize=1000"
    article_detail = "https://api.mdnice.com/articles/{}"

    def __init__(self, authorization, directory='./'):
        self.directory = directory
        self.headers = {"Authorization": authorization}

    async def write_file(self, filename, content):
        filepath = os.path.join(self.directory, f"{filename}.md")
        async with aiofiles.open(filepath, 'w', encoding='utf-8') as f:
            await f.write(content)

    async def get_articles(self):
        try:
            async with aiohttp.ClientSession(headers=self.headers) as session:
                async with session.get(self.article_list_url, headers=self.headers) as response:
                    return await response.json(encoding='utf-8')
        except Exception as e:
            print(f"获取用户文章列表失败")
            raise e

    async def get_article_content(self, title, article_id):
        url = self.article_detail.format(article_id)
        try:
            async with aiohttp.ClientSession() as session:
                async with session.get(url, headers=self.headers) as response:
                    data = await response.json(encoding='utf-8')
                    await self.write_file(title.replace("/", ""), data.get("data").get("markdown"))
        except Exception as e:
            print(f"写入文章: {title} 失败, error: {e}")


async def main(auth, directory_path):
    before = time.perf_counter()
    m = Markdown(auth, directory_path)
    articles = await m.get_articles()
    data = articles.get("data")
    await asyncio.gather(*(m.get_article_content(x.get("title"), x.get("outId")) for x in data))
    print(time.perf_counter() - before)

if __name__ == "__main__":
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    directory = input("请输入要写入的目录, 不输入则为当前目录:")
    directory_path = directory if directory else "./"
    auth = input("请输入mdnice官网headers中的Authorization: \n")
    asyncio.run(main(auth, directory_path))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58