保存極客時間文章到本地

保存極客時間文章到本地

背景需求

自己極客時間買的專欄。很多文章很好,也很有深度,想要反復閱讀。所以想打印成紙質版,閱讀更方便。

實踐過程

目標就是將網(wǎng)頁正文內容保存到PDF

想到的方法:

  1. 后端爬蟲 直接放棄,話說這么有價值的東西。要是爬蟲能簡單就爬到數(shù)據(jù),還做什么計算機技術知識內容?
  2. 用chrome保存到PDF 簡單粗暴,成功率高。就是內容多了費事,所以上按鍵精靈。
  3. 前端JS爬蟲 有跨域、登錄驗證等其他問題,復雜度過高
  4. 保存文章到本地 極客空間的網(wǎng)頁內容一看就是markdown生成的,所以再反轉回去存入markdown文件。相當于拿到原文件就可以隨便整了

網(wǎng)頁直接保存到PDF文件

chrome有好多插件可以將網(wǎng)頁保存到PDF

  • 捕捉網(wǎng)頁截圖 - 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

給文章名加上編號,并刪除不要的字符,使其按名字排序后為文章的發(fā)表順序

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. 觀察網(wǎng)站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 = "";

    // 等待網(wǎng)頁加載完成再執(zhí)行。但由于網(wǎng)頁是用js動態(tài)生成的,所以這個不行。
    // 這種情況有討論就是等待一個關鍵元素,感覺太麻煩
    // https://stackoverflow.com/questions/12897446/userscript-to-wait-for-page-to-load-before-executing-code-techniques
    
    // window.addEventListener('load', (event) => {
    //     GetArticleInfos();
    // });
    
    // 延遲執(zhí)行,簡單粗暴
    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();
            }
        }

        //網(wǎng)頁中的高亮和代碼用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發(fā)送到本地服務,本地服務保存

保存文件到本地的服務

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. 開啟保存服務,瀏覽到第一篇文章,刷新頁面。過段時間文件就會出現(xiàn)在本地。如果中間網(wǎng)頁出錯刷新一下會繼續(xù)
  2. markdown中還是有一些格式不對的,用python寫個腳本處理處理就好。如:圖片文件保存到本地,標題不合適等。

拿到完整的md文件就好了么?轉為一個好看的PDF,又一個折磨的過程開始了。。。

將Markdown文件轉為PDF文件

  1. 文檔轉換當然用PanDoc,然而發(fā)現(xiàn)這這個坑也不淺,是一個龐大的工程
  2. 用vscode的markdown pdf 還不錯,就是有頁眉頁腳,沒有目錄書簽,邊距太小,文章開始不在下一頁是連續(xù)的,還要繼續(xù)配置折騰

==未完待續(xù)==

結束句

本來是想學東西,結果被帶偏,搞了幾天還是沒有達到自己的預期。有這個時間文章都可能看了一遍了。這就是我還是個低端碼農(nóng)的原因吧。以上路就不知道跑哪里去。。。

參考

  1. 跨域

  2. pandoc 使用

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 想象一下,你早上7:00起床,匆匆洗漱,趕9:00前到公司打卡,埋頭工作。中午叫個外賣湊合一下,吃完繼續(xù)埋...
    婷婷yu立閱讀 310評論 0 0
  • 2019年4月28日是我(孫帥)的日精進行動第235天,和大家分享我今天的進步,我們互相勉勵,攜手前行。每天進步一...
    微笑調調閱讀 186評論 0 0
  • 荒蕪也罷 綠野 粗糲的戈壁荒灘上,塵土掩埋著塵土,砂石擠著砂石,尖銳擠著尖銳,這般刺痛腳掌前行,身影撫摸砂石,內...
    西部綠野閱讀 248評論 0 0
  • 舊年山中月,似與今時同。 一片蛙聲里,清光映院中。 起身獨徘徊,孤鴻舞長空。 明年蟾宮桂,為我斂芳容。
    梅光華閱讀 263評論 3 3
  • 彭婆小學 王利格 這學期我們班轉來一個男孩,他長得高高大大,白白凈凈,虎頭虎腦的。...
    60e57ccab38a閱讀 425評論 0 0

友情鏈接更多精彩內容