文泉書局的電子書下載方法

昨天在文泉書局的網站上買了本電子書,買完之後才發現竟然只能在線看不能下載,頓時發現被坑了,幾十塊錢只買了個閱讀權限,那還不如直接買紙質版的嘛!心中突然有了千萬只草泥馬在奔騰!於是抱着一絲絲希望找客服理論,希望能退款或者發給我一份電子版(後來想了想他們應該不會發電子版,避免用戶四處分享,要保護作者版權)。毫無意外的,客服拒絕了我的請求,一直回覆說他們有在頁面上提醒用戶(那麼小的字,那麼不顯眼的位置,鬼才看得到)。之後在百度上搜了下,發現好幾個人都跟我一樣被坑了(這難道不是欺騙消費者?可惜我不是學法律的,不然找他們理論會更有依據一點)。事情總不能就這麼算了吧,心裏有點不甘心,總想着能不能把電子書下載下來。幸好所學的技術沒白費,分析了他們在線閱讀器的前端代碼之後,哎?發現有機可乘,用一個爬蟲就可以把電子書的圖片一張張爬下來。在這裏分享出來,希望跟我一樣被坑的童鞋至少能下載下來電子書,不至於太虧。有幸沒有中坑的童鞋權當看個故事,以後儘量避免入坑。

-----------------------------我是寫作背景分割線-----------------------------------------

方法應用前提:

1、如果你買了網站上的電子書,那麼可以用本方法實現電子書的下載

2、如果沒有買電子書,那麼只能用本方法實現電子書試讀部分的下載

電子書下載思路:

分析了網站上的那個在線閱讀器的前端請求之後發現,所有的電子書內容都是以圖片進行傳輸和顯示的,那麼只要能獲取圖片的url或者直接獲取圖片內容就可以把圖片保存下來。但是,在圖片的url請求地址中包含有一個key,不要這個key就只能獲取到一張非常模糊的圖片,只有key值對上了才能請求到清晰的圖片,而且這個請求地址好像還是有時間限制的,估計是在key裏面有個時間戳。解析key值的含義看上去有點麻煩,所以就換到分析圖片內容的思路上了。在Dom中的img標籤上發現src屬性裏直接是圖片內容。hmm,那麼只要用爬蟲爬出每張圖片對應的img標籤就可以搞定啦。

整理一下我實現上面思路的代碼:

有了上面的思路,剩下的就是怎麼寫爬蟲的問題了,我比較熟悉C#,所以就寫了個C#的爬蟲,對C#比較熟悉的童鞋可以直接用我的代碼,不熟悉C#的話,也可以用別的編程語言實現上面的思路。

我用到了兩個C#庫,都可以用Nuget獲取。

CefSharp,用來模擬鼠標滾動,因爲電子書的圖片是動態加載的,必須滾動鼠標才加載後面的內容。Nuget上CefSharp包含兩個庫,CefSharp.Common和CefSharp.WinForms,也不知道這兩個是不是都用上了,反正我是都下載安裝了。

HtmlAgilityPack,用來解析Dom元素,就這一個庫,直接安裝就行。

程序界面非常簡單,上面一個panel放開始執行的按鈕,底下一個panel在程序運行之後加載CefSharp庫中的ChromiumWebBrowser。

下面是Winform窗口的代碼:

public partial class Form1 : Form
    {
        private ChromiumWebBrowser browser;//CefSharp瀏覽器
        private HtmlNode imgBox;
        private int nodeCount;
        private int curNode = 0;
        private System.Timers.Timer t;

        public Form1()
        {
            InitializeComponent();

            browser = new ChromiumWebBrowser("https://wqbook.wqxuetang.com/read/pdf/3211731")
            {
                Dock = DockStyle.Fill,
            };
            this.panel2.Controls.Add(browser);
        }

        public void theout(object source, System.Timers.ElapsedEventArgs e)
        {
            bool isValid = false;
            var box = imgBox.ChildNodes[curNode];
            if (box.FirstChild == null)
            {
                t.Stop();
                return;
            }
            var img = box.FirstChild.Attributes["src"];
            if (img != null && img.Value.Substring(0, 4) != "data")
            {
                isValid = true;
            }
            else
            {
                if (img != null)
                {
                    var base64Str = img.Value.Replace("data:image/png;base64,", "")
.Replace("data:image/jgp;base64,", "")
.Replace("data:image/jpg;base64,", "")
.Replace("data:image/jpeg;base64,", "");
                    byte[] bt = Convert.FromBase64String(base64Str);
                    System.IO.MemoryStream stream = new System.IO.MemoryStream(bt);
                    Bitmap bitmap = new Bitmap(stream);
                    if (bitmap.Width > 1000)
                    {
                        string fileName = curNode.ToString() + ".jpg";
                        bitmap.Save(fileName);
                        isValid = true;
                    }
                    bitmap.Dispose();
                    stream.Close();
                }
            }
            if (isValid)
            {
                curNode++;
            }

            var host = browser.GetBrowser().GetHost();
            host.SendMouseWheelEvent(0, 0, 0, -100, CefEventFlags.None);

            String html = browser.GetSourceAsync().Result;
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(html);
            imgBox = doc.GetElementbyId("pagebox");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            String html = browser.GetSourceAsync().Result;
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(html);
            imgBox = doc.GetElementbyId("pagebox");
            nodeCount = imgBox.ChildNodes.Count;

            t = new System.Timers.Timer(1000);//實例化Timer類,設置間隔時間爲1000毫秒;
            t.Elapsed += new System.Timers.ElapsedEventHandler(theout);//到達時間的時候執行事件;
            t.AutoReset = true;//設置是執行一次(false)還是一直執行(true);
            t.Enabled = true;//是否執行System.Timers.Timer.Elapsed事件;
            t.Start();
        }
    }

代碼中用了一個定時器,定時滾動頁面來加載電子書的圖片。每次滾動之後,判斷當前需要下載的圖片是否加載成功,在判斷的時候額外添加了一個圖片寬度的判斷,因爲閱讀器加載圖片的順序是先加載模糊圖片,再加載清晰圖片。模糊的不要,清晰的保存。喲西,搞定。

額外需要注意的事項:

1、在點開始按鈕前,如果是買了完整電子書的用戶,需要先登錄賬號,跟普通網站的操作一樣。

2、雖然設置了定時器自動滾動,但是如果網速不太好,滾動完之後還沒加載出來就可能一直自動滾動,沒保存圖片。所以,可以定時查看一下,如果出現以上情況就手動用鼠標滾動到之前沒加載的圖片上。呃,不知道說清楚沒,反正就是自動的出問題了,就手動滾動一下頁面。

3、自動滾動的速度和每次滾動的幅度都可以在代碼裏設置,自己研究代碼吧,應該比較簡單。儘量不要滾動的太快,不然來不及加載頁面就跳過去了。

存在的問題和優化方法:

1、只是下載了圖片,需要進一步把這些圖片合成電子書,同時還可以加上目錄和書籤。這部分內容參見另一篇博客:PDF目錄的自動生成

2、下載的電子書是圖片的,不能複製出文字。這個沒辦法解決,因爲爬蟲爬的時候就是圖片。可以OCR識別一下,在上面合成電子書的那篇博客裏面也介紹了OCR識別文字的方法。

3、只能算是半自動的下載,因爲需要偶爾手動輔助一下。另外,下載的速度比較慢。呃,沒辦法,用模擬閱讀行爲的方式就是這麼慢。可以考慮解析圖片請求地址的那個key,然後直接發送請求獲取並下載圖片,這樣會快很多(我是沒解析出來,大家加油)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章