保存极客时间文章到本地

保存极客时间文章到本地

背景需求

自己极客时间买的专栏。很多文章很好,也很有深度,想要反复阅读。所以想打印成纸质版,阅读更方便。

实践过程

目标就是将网页正文内容保存到PDF

想到的方法:

  1. 后端爬虫 直接放弃,话说这么有价值的东西。要是爬虫能简单就爬到数据,还做什么计算机技术知识内容?
  2. 用chrome保存到PDF 简单粗暴,成功率高。就是内容多了费事,所以上按键精灵。
  3. 前端JS爬虫 有跨域、登录验证等其他问题,复杂度过高
  4. 保存文章到本地 极客空间的网页内容一看就是markdown生成的,所以再反转回去存入markdown文件。相当于拿到原文件就可以随便整了

网页直接保存到PDF文件

chrome有好多插件可以将网页保存到PDF

  • 捕捉网页截图 - FireShot —— 比较麻烦,不能简单的保存正文
  • Print Friendly & PDF —— 很方便,会自动过滤掉无用的内容,而且也很方便删除
  1. 先将单独文章用Print Friendly & PDF配合按键精灵单独保存到pdf中
  2. Adobe Acrobat X Pro将单独文件合并成一个单独的PDF文件,不错还带书签

遇到问题

1.文件是按照名字排序的,但实际文章不是按名字顺序的,所以合并后的文章顺不对。。。

解:python文章按时间排序,(保存的时候是按文章顺序排序保存的)

# dirPath 目录路径,只处理目录下的文件
# sort 0 默认按名称排序;1,按时间正序;2,按时间逆序
def getSortFile(dirPath,sort=0):
    fileList = os.listdir(dirPath)
    if sort != 0:
        r = False if sort == 1 else True
        fileList = sorted(fileList, key=lambda x: os.path.getmtime(
            os.path.join(dirPath, x)), reverse=r)
    return fileList

给文章名加上编号,并删除不要的字符,使其按名字排序后为文章的发表顺序

def rename(path, pattern, replace, iExt=True):
    "将path路径下的文件按rxeg正则表达式重命名,iExt是否忽略扩展名"

    pRe = re.compile(pattern)
    for root, _, files in os.walk(path):
        # print("root:{0},dirs:{1}".format(root,dirs))
        for file in files:
            newFile = ""
            if iExt:
                nameInfo = os.path.splitext(file)
                newFile = pRe.sub(replace, nameInfo[0])+nameInfo[1]
            else:
                newFile = pRe.sub(replace, file)
            newPath = os.path.join(root, newFile)
            oldPath = os.path.join(root, file)
            print("{0} rename to {1}".format(oldPath, newPath))
            os.rename(oldPath, newPath)

2.PDF里有一些Print Friendly & PDF自己添加的东西,而且字体变成了黑体,字间距也不合适

解:无解

要修改PDF简直不可能,搜半天没一个点好办法。转为worl、html、再修改也是不可能的,各种乱七八糟的东西。无奈只能放弃。。。

将文章直接保持到本地Markdown

工具:Chrome浏览器Tampermonkey 油猴插件

步骤

  1. 观察网站HTML接口,找到关键点,编写油猴脚本
// ==UserScript==
// @name         bcjksjmd
// @namespace    ssqf.site
// @version      0.1
// @description  保存极客时间的文字为markdown
// @author       tako
// @match        https://time.geekbang.org/*
// @grant        none
// @require      https://code.jquery.com/jquery-3.4.0.min.js
// @require      https://unpkg.com/turndown/dist/turndown.js
// @run-at       document-idle
// ==/UserScript==


/*
元素说明
第一个h1即标题
紧接着的div1是作者
下一个div2 是正文
    div2.1 开头图片
    div2.2 音频 可能不存在,不存在就往前移
    div2.3 正文
        div2.3.1-1p宣传连接生成
        div2.3.1-2p宣传图
    div2.4 版权
再下来div3是评论框
div3评论内容


图片 自定义标签
TI = tilte image
A = audio
V = video
*/
(function () {
    'use strict';
    var tm = 5 * 1000
    //var title = "";
    var author = "";
    var date = "";
    var h1Title = ""
    var headImg = "";
    var audio = "";
    var teller = "";
    var content = "";

    // 等待网页加载完成再执行。但由于网页是用js动态生成的,所以这个不行。
    // 这种情况有讨论就是等待一个关键元素,感觉太麻烦
    // https://stackoverflow.com/questions/12897446/userscript-to-wait-for-page-to-load-before-executing-code-techniques
    
    // window.addEventListener('load', (event) => {
    //     GetArticleInfos();
    // });
    
    // 延迟执行,简单粗暴
    setTimeout(function () {
       GetArticleInfos();
    }, tm)

    //获取文字必要的信息并以josn 字符串形式返回
    function GetArticleInfos() {
        //title = $("title").text();
        var audioElem = $("audio");
        var isAudio = (audioElem.length > 0) ? true : false;
        //var h1 = $("h1:frist");
        var h1 = $("h1").first();
        var authorDiv = h1.next();
        var mainDiv = authorDiv.next();
        var div2ch1 = mainDiv.children();
        var imgDiv = div2ch1.eq(0);
        var audioDiv
        var contentDiv

        if (isAudio) {
            audioDiv = div2ch1.eq(1);
            contentDiv = div2ch1.eq(2).children().eq(0);
        } else {
            contentDiv = div2ch1.eq(1).children().eq(0);
        }

        h1Title = h1.text();
        var authorch = authorDiv.children();
        author = authorch.eq(0).text();
        date = authorch.eq(1).text();
        headImg = imgDiv.find("img").attr("src");
        if (isAudio) {
            audio = audioDiv.find("audio").attr("src");
            teller = audioDiv.find("span").first().text();
        }

        // 移除广告每页可能不太一样
        var adLink = contentDiv.children().last();
        var adImg = adLink.prev();
        if (adLink.find("img").length == 1) {
            adLink.remove();
        } else {
            if (adLink.find("a").length == 1 && adImg.find("img").length == 1) {
                adLink.remove();
                adImg.remove();
            }
        }

        //网页中的高亮和代码用code和table处理的,需要简化使其可以正确转换会markdown
        var code = contentDiv.find("code")
        code.each(function (i) {
            var parentElem = $(this).parent()
            if (parentElem.is("pre")) { //代码块
                var trList = $(this).find("tr")
                var codeTxt = ""
                trList.each(function (i) {
                    var trTxt = $(this).text()
                    if (trTxt.length > 0) {
                        codeTxt += trTxt + "\n"
                    }
                })
                $(this).empty();
                $(this).text(codeTxt);
            } else {
                var txt = $(this).text();
                $(this).empty();
                $(this).text(txt);
            }

        });

        content = formatContent(contentDiv);

        var mdTxt = ""
        mdTxt += "# " + h1Title + "\n\n"
        mdTxt += "作者:" + author +"    日期:" + date + "\n\n"
        mdTxt += "![TI](" + headImg + ")\n\n"
        if (audio != "") {
            mdTxt += "![A](" + audio + ")" + "\n\n"
            mdTxt += teller  + "\n\n"
        }
        mdTxt += content
        // var jsonStr = JSON.stringify(infos);
        console.log("markdown text:" + mdTxt);
        var fileName = h1Title.replace(/[/\\?*<>:"|]/g,""); //文件名中的特殊字符提出掉
        SaveInfoToFile(fileName+".md",mdTxt)
        return mdTxt;
    }

    //处理正文内容
    function formatContent(ctt) {
        var html = $(ctt).html();
        // turndown一个将html转为markdown的js库 https://github.com/domchristie/turndown
        var turndownService = new TurndownService({ codeBlockStyle: 'fenced' ,headingStyle:"atx"})
        return turndownService.turndown(html)
    }

    //保存文件
    //由于跨域问题不能传json,就只能用from-data方式

    function SaveInfoToFile(filename,data) {
        $.ajax({
            type: "POST",
            url: "http://localhost:8282/savemd",
            // data: JSON.stringify({filePath:path,fileData:data}),
            // contentType:"application/json",
            data: {fileName:filename,fileData:data},
            success: nextPage, //成功跳到下一页
            dataType: "text"
          });
    }

    //跳转到下一页
    function nextPage(){
        //h1的父的父的弟的5子即为下一页按钮
        var nextBtn = $("h1").first().parent().parent().next().children().eq(5);
        if (nextBtn.lenght >0){
            nextBtn.click();
            setTimeout(function () {
                GetArticleInfos();
             }, tm)
        }
    }

})();

  1. 保存到本地markdown文件
    • 直接用油猴保存——js不可能简单通过浏览器操作本地文件,放弃
    • ajax发送到本地服务,本地服务保存

保存文件到本地的服务

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

const servAddr = ":8282"
const dir = "D:\\保存目录\\" //需要目录存在

func main() {
    log.Printf("Start savemd serv!\n")
    http.HandleFunc("/savemd", saveMD)
    log.Fatalf("HTTP Serv error:%v\n", http.ListenAndServe(servAddr, nil))
}

func saveMD(w http.ResponseWriter, r *http.Request) {
    fileName := r.FormValue("fileName")
    fileData := r.FormValue("fileData")
    filePath := dir + fileName
    if filePath == "" {
        log.Printf("filePath is empty!\n")
        w.WriteHeader(http.StatusBadRequest)
        w.Write([]byte(fmt.Sprintf("request error:filePath is empty!")))
        return
    }

    err := ioutil.WriteFile(filePath, []byte(fileData), 0644)
    if err != nil {
        log.Printf("ioutil write file[%s] error:%v\n", filePath, err)
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte(fmt.Sprintf("server error:%v", err)))
        return
    }
    log.Printf("Save [%s] ok\n", filePath)
}

  1. 开启保存服务,浏览到第一篇文章,刷新页面。过段时间文件就会出现在本地。如果中间网页出错刷新一下会继续
  2. markdown中还是有一些格式不对的,用python写个脚本处理处理就好。如:图片文件保存到本地,标题不合适等。

拿到完整的md文件就好了么?转为一个好看的PDF,又一个折磨的过程开始了。。。

将Markdown文件转为PDF文件

  1. 文档转换当然用PanDoc,然而发现这这个坑也不浅,是一个庞大的工程
  2. 用vscode的markdown pdf 还不错,就是有页眉页脚,没有目录书签,边距太小,文章开始不在下一页是连续的,还要继续配置折腾

==未完待续==

结束句

本来是想学东西,结果被带偏,搞了几天还是没有达到自己的预期。有这个时间文章都可能看了一遍了。这就是我还是个低端码农的原因吧。以上路就不知道跑哪里去。。。

参考

  1. 跨域

  2. pandoc 使用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • 想象一下,你早上7:00起床,匆匆洗漱,赶9:00前到公司打卡,埋头工作。中午叫个外卖凑合一下,吃完继续埋...
    婷婷yu立阅读 275评论 0 0
  • 2019年4月28日是我(孙帅)的日精进行动第235天,和大家分享我今天的进步,我们互相勉励,携手前行。每天进步一...
    微笑调调阅读 165评论 0 0
  • 荒芜也罢 绿野 粗粝的戈壁荒滩上,尘土掩埋着尘土,砂石挤着砂石,尖锐挤着尖锐,这般刺痛脚掌前行,身影抚摸砂石,内...
    西部绿野阅读 211评论 0 0
  • 旧年山中月,似与今时同。 一片蛙声里,清光映院中。 起身独徘徊,孤鸿舞长空。 明年蟾宫桂,为我敛芳容。
    梅光华阅读 228评论 3 3
  • 彭婆小学 王利格 这学期我们班转来一个男孩,他长得高高大大,白白净净,虎头虎脑的。...
    60e57ccab38a阅读 392评论 0 0