python 抓取小說網站,制作電子書。


分析目的(或者說要達到的效果)

實現一個小說下載器,輸入小說的名字然后抓取小說的全部章節,制作成文檔。
需要的知識:使用BeautifulSoup或正則解析網頁,使用requests下載網頁。

搜索小說

直接用小說的站內搜索

使用小說網站的搜索頁面:http://so.sanjiangge.com/cse/search 這個網站用的是百度的站內搜索
需要提交的數據有q:我們要搜索的文字,clik和entry顧名思義,而s應該是百度站內搜索的用戶碼吧。

import requests
def directory(name):
    params = {"q":name, "click":1, "entry":1, "s":781863708123447302, "nsid":""}
    url = "http://so.sanjiangge.com/cse/search"
    print(url)
    r = requests.get(url,params=params)
    r.encoding = "utf-8"
    return r.text

返回的是一個HTML文件。注意必須把網頁的編碼方式改為utf-8。

解析網頁


先把第一步的網頁搜索結果提取出來,使用BeautifulSoup模塊找到我們要找到書的鏈接。
先看看網頁的結構

<div class="result-item result-game-item">
            
            <div class="result-game-item-pic">
            <a cpos="img" href="http://www.dingdianzw.com/book/2430.html" class="result-game-item-pic-link" target="_blank" style="width:110px;height:150px;">
                <img src="http://www.dingdianzw.com/files/article/image/2/2430/2430s.jpg" alt="<em>一念</em><em>永恆</em>" class="result-game-item-pic-link-img"
                    onerror="$(this).attr('src', '/static/img/novel-noimg.jpg')" > 
            </a>
        </div>
        <div class="result-game-item-detail">
        <h3 class="result-item-title result-game-item-title">
            <a cpos="title" href="http://www.dingdianzw.com/book/2430.html" title="一念永恆" class="result-game-item-title-link" target="_blank">
                                    <em>一念</em><em>永恆</em>
                            </a>
        </h3>
                                                            <p class="result-game-item-desc"><em>一念</em>成滄海,<em>一念</em>化桑田。<em>一念</em>斬千魔,<em>一念</em>誅萬仙。唯我念……<em>永恆</em></p>
                <div class="result-game-item-info">
                                                                             
                        <p class="result-game-item-info-tag">
                            <span class="result-game-item-info-tag-title preBold">作者:</span>
                                                            <span>
                                                                            耳根
                                                                    </span>
                                                    </p>
                                                                                                            <p class="result-game-item-info-tag">
                            <span class="result-game-item-info-tag-title preBold">類型:</span>
                            <span class="result-game-item-info-tag-title">武俠修真</span>
                        </p>
                                                                                                                 
                        <p class="result-game-item-info-tag">
                            <span class="result-game-item-info-tag-title preBold">更新時間:</span>
                            <span class="result-game-item-info-tag-title">2016-11-10</span>
                        </p>
                                                                                     
                                                <p class="result-game-item-info-tag">
                            <span class="result-game-item-info-tag-title preBold">最新章節:</span>
                                                            <a cpos="newchapter" href="http://www.dingdianzw.com/chapter/2430_5101830.html" class="result-game-item-info-tag-item" target="_blank">第405章 送你上天……</a>
                                                    </p>
                                                                                </div>
    </div>
</div>

上面就包含了我們需要的所有信息(封面,書名,書的地址,最近章節等等)我們只需要排第一的搜索選項中的書名和書的地址,使用BeautfulSoup或正則表達式提取出來。

    if r.url == "http://www.baidu.com/search/error.html":
        return False
    else:
        bsObj = BeautifulSoup(r.text, "lxml").findAll("a", {"cpos":"title"})[0]
        return (bsObj["href"],bsObj.get_text()[1:-1])

加入搜索錯誤的檢測,如果錯誤頁面將會重定向到百度的錯誤頁面。並且把書的鏈接提取出來。
接着再來看看書頁面的源代碼

<dd><a href="23410363.html" title="外傳1 柯父。">外傳1 柯父。</a></dd><dd><a href="23410364.html" title="外傳2 楚玉嫣。">外傳2 楚玉嫣。</a></dd><dd><a href="23410365.html" title="外傳3 鸚鵡與皮凍。">外傳3 鸚鵡與皮凍。</a></dd><dd><a href="23410366.html" title="第一章 他叫白小純">第一章 他叫白小純</a></dd>
<dd><a href="23410367.html" title="第二章 火灶房">第二章 火灶房</a></dd><dd><a href="23410369.html" title="第三章 六句真言">第三章 六句真言</a></dd><dd><a href="23410370.html" title="第四章 煉靈">第四章 煉靈</a></dd><dd><a href="23410371.html" title="第五章 萬一丟了小命咋辦">第五章 萬一丟了小命咋辦</a></dd>
<dd><a href="23410372.html" title="第六章 靈氣上頭">第六章 靈氣上頭</a></dd><dd><a href="23410373.html" title="第七章 龜紋認主">第七章 龜紋認主</a></dd><dd><a href="23410374.html" title="第八章 我和你拼了!">第八章 我和你拼了!</a></dd><dd><a href="23410375.html" title="第九章 延年益壽丹">第九章 延年益壽丹</a></dd>
<dd><a href="23410376.html" title="第十章 師兄別走">第十章 師兄別走</a></dd><dd><a href="23410377.html" title="第十一章 侯小妹">第十一章 侯小妹</a></dd><dd><a href="23410379.html" title="第十二章 籬笆牆上">第十二章 籬笆牆上</a></dd><dd><a href="23410380.html" title="第十三章 你也來吧!">第十三章 你也來吧!</a></dd>
<dd><a href="23410382.html" title="第十四章 三師兄?三師姐?">第十四章 三師兄?三師姐?</a></dd><dd><a href="23411581.html" title="第十五章 不死長生功!">第十五章 不死長生功!</a></dd><dd><a href="23411582.html" title="第十六章 心細入微">第十六章 心細入微</a></dd><dd><a href="23411583.html" title="第十七章 小烏龜">第十七章 小烏龜</a></dd>
<dd><a href="23411584.html" title="第十八章 引領氣氛!">第十八章 引領氣氛!</a></dd><dd><a href="23411585.html" title="第十九章 白鼠狼的傳說">第十九章 白鼠狼的傳說</a></dd><dd><a href="23411586.html" title="第二十章 一地雞毛">第二十章 一地雞毛</a></dd><dd><a href="23411587.html" title="第二十一章 小純哥哥……">第二十一章 小純哥哥……</a></dd>
<dd><a href="23411588.html" title="第二十二章 師姐放心!">第二十二章 師姐放心!</a></dd><dd><a href="23411589.html" title="第二十三章 偷雞狂魔">第二十三章 偷雞狂魔</a></dd><dd><a href="23411590.html" title="第二十四章 你是誰">第二十四章 你是誰</a></dd><dd><a href="23411591.html" title="第二十五章 不死鐵皮!">第二十五章 不死鐵皮!</a></dd>
<dd><a href="23411592.html" title="第二十六章 靈尾雞好吃么?">第二十六章 靈尾雞好吃么?</a></dd><dd><a href="23411593.html" title="第二十七章 這……這是竹子?">第二十七章 這……這是竹子?</a></dd><dd><a href="23411594.html" title="第二十八章 壓力才是動力">第二十八章 壓力才是動力</a></dd><dd><a href="23411595.html" title="第二十九章 舉重若輕">第二十九章 舉重若輕</a></dd>
<dd><a href="23411596.html" title="第三十章 來吧!">第三十章 來吧!</a></dd><dd><a href="23411597.html" title="第三十一章 恥辱啊!">第三十一章 恥辱啊!</a></dd><dd><a href="23411598.html" title="第三十二章 運氣逆天">第三十二章 運氣逆天</a></dd><dd><a href="23411599.html" title="第三十三章 打倒白小純!">第三十三章 打倒白小純!</a></dd>
<dd><a href="23411600.html" title="第三十四章 草木碾壓">第三十四章 草木碾壓</a></dd><dd><a href="23411601.html" title="第三十五章 又見許寶財">第三十五章 又見許寶財</a></dd><dd><a href="23411602.html" title="第三十六章 小烏龜稱霸!">第三十六章 小烏龜稱霸!</a></dd><dd><a href="23411603.html" title="第三十七章 舉輕若重">第三十七章 舉輕若重</a></dd>

目標是把書的章節名和章節鏈接提取出來

def contents(url, b_name):
    link = url[:-10]
    print (link)
    links = []
    r = requests.get(url)
    r.encoding = "utf-8"
    bsObj = BeautifulSoup(r.text, "lxml").findAll("dd")
    for i in bsObj:
        i = link + i.a["href"] #把內鏈合並成完整的鏈接。
        links.append(i)

現在讓我們來看看章節頁面的源代碼是怎樣的吧

<div class="kwei"></div><div class="nrad250left"><script>show_kw1();</script></div>
<div class="kwei2"></div><div class="nrad250right"><script>show_kw2();</script></div>
<div class="kwei3"></div><div class="nrad250left2"><script>show_kw3();</script></div>
<!--章節內容開始-->&nbsp;&nbsp;&nbsp;&nbsp;帽兒山,位於東林山脈中,山下有一個村子,民風淳朴,以耕田為生,與世隔絕。<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;清晨,村庄的大門前,整個村子里的鄉親,正為一個十五六歲少年送別,這少年瘦弱,但卻白白凈凈,看起來很是乖巧,衣着盡管是尋常的青衫,可卻洗的泛白,穿在這少年的身上,與他目中的純凈搭配在一起,透出一股子靈動。<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;他叫白小純。<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;

提取出一個章節的全部文字,返回一個包含全部文字的字符串

def section(url):
    r = requests.get(url)
    r.encoding = "gbk"
    bsObj = BeautifulSoup(r.text, "lxml").find(id="content")
    print (bsObj)
    
結果:
<div id="content" name="content">
<div></div>
<div class="kwei"></div><div class="nrad250left"><script>show_kw1();</script></div>
<div class="kwei2"></div><div class="nrad250right"><script>show_kw2();</script></div>
<div class="kwei3"></div><div class="nrad250left2"><script>show_kw3();</script></div>
<!--章節內容開始-->    帽兒山,位於東林山脈中,山下有一個村子,民風淳朴,以耕田為生,與世隔絕。<br/><br/>。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。略   D410211<!--章節內容結束-->
<div>
</div>
<script src="/js/cpc2.js" type="text/javascript"></script>
<div class="bottem">
<div class="adhtml"><script>show_htm2();</script></div>
<p>小技巧:按 回車[Enter]鍵 返回章節目錄,按 ←鍵 回到上一章,按 →鍵 進入下一章。</p>
<a href="23410365.html">上一章 </a>← <a href="index.html" title="一念永恆最新章節目錄">章節目錄</a> → 
<a href="23410367.html">下一章</a>
<p> </p>
<div class="adhtml"><script>show_htm3();</script></div>
</div>
</div>

這里有一個問題,返回的內容包含太多不需要的東西,接下來配合使用正則表達式我覺得會容易一些

def section(url):
    r = requests.get(url)
    r.encoding = "gbk"
    bsObj = BeautifulSoup(r.text, "lxml").find(id="content")
    print (str(bsObj))

    content = re.compile(r"<!--章節內容開始-->(.*)<!--章節內容結束-->").search(str(bsObj))#把章節內容提取出來
    content = re.compile("<br/><br/>").sub("\n", content.group(1))#把網頁的<br/><br/>替換成換行符
    return content

然后匯總全部章節內容。寫入TXT文件。

def contents_load(books, f_name = "電子書"):
    contents = []
    contents.append(f_name+"\n")
    for i in books:
        contents.append(i[1]+"\n")
        contents.append(section(i[0]))
        print(i[1], "下載成功")
    f = open(f_name+".txt", "w", encoding="utf-8")
    f.writelines(contents)
    
效果:
第一章 一覺醒來整個世界都變了 下載成功
第二章 果然我穿越的方式不對嗎? 下載成功
第三章 孫思邈是我“道弟” 下載成功
第四章 原來穿越者也可以被打臉 下載成功
第五章 一覺醒來世界又變了 下載成功
。。。。。

現在爬蟲在網絡良好的狀況下基本可以正常運行了,不過下載速度有點慢,接下來我們使用多線程加快速度,再使用面對對象的程序設計。注意文件編碼方式需要指定utf-8。
多線程參看菜鳥教程

import requests
from bs4 import BeautifulSoup
import re
import threading

class load_book(object):
    def __init__(self,book_name):
        self.b_name = book_name
        self.b_link = self.b_link_load()
        self.b_directory = self.directory()
        self.content = {}

    def b_link_load(self):
        params = {"q": self.b_name, "click": 1, "entry": 1, "s": 7818637081234473025, "nsid": ""}
        url = "http://so.sanjiangge.com/cse/search"
        r = requests.get(url, params=params)
        r.encoding = "utf-8"
        if r.url == "http://www.baidu.com/search/error.html":
            return False
        else:
            bsObj = BeautifulSoup(r.text, "lxml").findAll("a", {"cpos": "title"})[0]
        return bsObj["href"]

    def directory(self):
        link = self.b_link[:-10]
        links = []
        r = requests.get(self.b_link)
        r.encoding = "gbk"
        bsObj = BeautifulSoup(r.text, "lxml").findAll("dd")
        for i in bsObj:
            url = link + i.a["href"]
            links.append((url, i.get_text()))
        return links

    def section_load(self, links):
        for i in links:
            r = requests.get(i[0])
            r.encoding = "gbk"
            bsObj = BeautifulSoup(r.text, "lxml").find(id="content")

            content = re.compile(r"<!--章節內容開始-->(.*)<!--章節內容結束-->").search(str(bsObj))  # 把章節內容提取出來
            content = re.compile("<br/><br/>").sub("\n", content.group(1))  # 把網頁的<br/><br/>替換成換行符
            self.content[i[1]]=content[:-8]

    def contents_load(self,loops=10):
        threads = []
        nloops = range(loops)
        task_list = []
        task_size = len(self.b_directory)//loops+1
        for i in nloops:#分割任務
            try:
                task_list.append(self.b_directory[i*task_size:(i+1)*task_size])
            except:
                task_list.append(self.b_directory[i*task_size:])

        for i in nloops:
            t = threading.Thread(target=self.section_load, args=([task_list[i]]))
            threads.append(t)

        for i in nloops:
            threads[i].start()

        for i in nloops:
            threads[i].join()

    def write_txt(self):
        f = open(self.b_name, "w",encoding="utf-8")
        f.write(" "*20+self.b_name+"\n")
        for i in self.b_directory:
            title = "\n"+" "*20+i[1]+"\n"
            f.write(title)
            f.write(self.content[i[1]])
        f.close()

if __name__ == '__main__':
    book = load_book("游仙鏡")
    book.contents_load(20)
    book.write_txt()

運行效果;

嗯,具體運行過程中hui出現網絡異常,以及部分章節為空頁的情況,為了使程序正常運行還需要進行錯誤的處理。

import requests
from bs4 import BeautifulSoup
import re
import threading
import time

class load_book(object):
    def __init__(self,book_name):
        self.b_name = book_name
        self.b_link = self.b_link_load()
        self.b_directory = self.directory()
        self.content = {}

    def b_link_load(self):
        params = {"q": self.b_name, "click": 1, "entry": 1, "s": 7818637081234473025, "nsid": ""}
        url = "http://so.sanjiangge.com/cse/search"
        try:
            r = requests.get(url, params=params)
            r.encoding = "utf-8"
            if r.url == "http://www.baidu.com/search/error.html":
                return False
            else:
                bsObj = BeautifulSoup(r.text, "lxml").findAll("a", {"cpos": "title"})[0]
                return bsObj["href"]
        except:
            print("獲取目錄失敗,一秒后重試,失敗鏈接:",url,params)
            time.sleep(1)
            self.b_link_load()

    def directory(self):
        link = self.b_link[:-10]
        links = []
        r = requests.get(self.b_link)
        r.encoding = "gbk"
        bsObj = BeautifulSoup(r.text, "lxml").findAll("dd")
        for i in bsObj:
            try:
                url = link + i.a["href"]
                links.append((url, i.get_text()))
            except TypeError:
                print(i,"獲取章節鏈接錯誤")
        return links

    def section_load(self, links):
        for i in links:
            try:
                r = requests.get(i[0])
                r.encoding = "gbk"
                bsObj = BeautifulSoup(r.text, "lxml").find(id="content")

                content = re.compile(r"<!--章節內容開始-->(.*)<!--章節內容結束-->").search(str(bsObj))  # 把章節內容提取出來
                content = re.compile("<br/><br/>").sub("\n", content.group(1))  # 把網頁的<br/><br/>替換成換行符
                self.content[i[1]] = content[:-8]
                print(i[1],"抓取完成")
            except (TypeError,AttributeError):
                print("*"*10,"%s章節錯漏"% i[1])
            except :
                print("*"*10,"%s抓取錯誤,重試中"% i[1])
                links.insert(0,i)

    def contents_load(self,loops=10):
        threads = []
        nloops = range(loops)
        task_list = []
        task_size = len(self.b_directory)//loops+1
        for i in nloops:#分割任務
            try:
                task_list.append(self.b_directory[i*task_size:(i+1)*task_size])
            except:
                task_list.append(self.b_directory[i*task_size:])

        for i in nloops:
            t = threading.Thread(target=self.section_load, args=([task_list[i]]))
            threads.append(t)

        for i in nloops:
            threads[i].start()

        for i in nloops:
            threads[i].join()

    def write_txt(self):
        print("開始制作txt文檔")
        f = open(self.b_name+".txt", "w", encoding="utf-8")
        f.write(" " * 20 + self.b_name + "\n")
        for i in self.b_directory:
            try:
                title = "\n\n" + " " * 20 + i[1] + "\n"
                f.write(title)
                f.write(self.content[i[1]])
            except KeyError:
                print("*" * 10, "缺失章節為:", i[1])
        f.close()


if __name__ == '__main__':
    book = load_book("美食供應商")
    book.contents_load(10)
    book.write_txt()

好了現在基本可以正常使用了,不過還有很多不足。希望可以和更多的人交流。

可以改進的功能

  1. 使用多個站點進行搜索
  2. 缺失章節使用其他站點數據嘗試彌補缺失
  3. 制作圖形界面
  4. 加入進度條
  5. 加入發送郵件功能,使制作的電子書可以直接發送到kindle上


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM