筆趣閣爬蟲(2020重製版),貼心的操作,誰用誰知道

支持正版,一切只爲學習

之前寫的筆趣閣爬蟲有不少同學說不能爬了,我後來發現是網站改版的緣故,前些日子把書裏的爬蟲項目都整的差不多了,現在又有些不知道爬什麼好了,剛好在這段時間把筆趣閣爬蟲代碼重寫一下。
(PS:我看自己之前寫的代碼感覺寫的好醜哦,官方吐槽,最爲致命,以前的鏈接

做了以下改進:

  1. 將爬蟲代碼寫入對象中,把用戶選擇判斷的語句寫在外部,使得爬蟲本身更加簡潔;
  2. 優化互動的相關代碼,操作更舒適;
  3. 採用多進程爬取小說各章節的內容,速度更快;
  4. 加入UA代理,儘可能反反爬蟲,安全性更高 。

前期準備,需要在cmd裏下載安裝以下第三方庫:
pip install requests
pip install python-docx

一、基本思路

以下是爬取筆趣閣小說的主體思路,爬蟲代碼在這個思路上擴展構建。

Created with Raphaël 2.2.0開始輸入書名查詢小說是否存在跳轉頁面至小說主頁獲取小說目錄URL解析小說內容並保存至word結束yesno

二、構建爬蟲

筆趣閣的網站衆多,這是我爬取的筆趣閣網址:http://www.biquge.tv/
我將爬蟲寫到 biqugeCrawl 類中。
在這裏插入圖片描述

1、借東風,借用網站搜索書籍的功能

以搜索聖墟爲例,網站的搜索是靠這個searchkey這個參數。在biqugeCrawl類中定義一個search_book() 函數專用於查找小說功能。
在這裏插入圖片描述
1.1、 大家看到這個%什麼的或許會很陌生,這是因爲URL 只允許一部分 ASCII 字符(數字字母和部分符號),其他的字符(如漢字)是不符合 URL 標準的,所以我們這裏使用 urllib庫parse.quote() 將其轉換爲URL編碼。

轉換後還是不符合要求,懷疑是編碼格式的問題,去網頁的源碼中查看,如下顯示爲GBK編碼,所以我們輸入的書名還要先轉換爲GBK格式。
在這裏插入圖片描述

name = input("請輸入書名:")
name = parse.quote(name.encode('gbk'))

1.2、 接下來我們拼接搜索的url,並請求它,得到相應的搜索結果的html網頁。

__random_ua()是隨機返回user-agent的函數,這樣在一定程度上可以避免被反爬。代碼很簡單,自己看一下。

serach_url = "http://www.biquge.tv/modules/article/search.php?searchkey={}"
url = serach_url.format(name)
response = requests.get(url=url, headers=self.__random_ua())

1.3、 如果搜到了書籍,也不直接下載,因爲好多小說名字相似趁熱度的,所以我這裏獲取第一個小說名及作者名返回讓用戶確認是否下載。另外,因爲搜索不當,可能沒有書籍,也可能是關鍵字太短了,我用try-except 另行處理,返回None值,並提示沒有該書。

# 搜索書籍
root = etree.HTML(response.content)

try:
     book_name = root.xpath('//*[@id="nr"]/td[1]/a/text()')[0]

     book_author = root.xpath('//*[@id="nr"]/td[3]/text()')[0]

     book_url = root.xpath('//*[@id="nr"]/td[1]/a/@href')[0]

     return [book_name, book_author, book_url]

 except:
     return None
# 用戶確認
book = biquge.search_book()
if book is not None:
    flag = input("搜索到的書名爲《{}》,作者名爲{},請確認是否下載【Y/N】".format(book[0], book[1]))
    while 1:
        if flag == 'Y' or flag == 'y':
            biquge.download(book[0], book[2])
            break
        elif flag == 'N' or flag == 'n':
            break
        else:
            flag = input("請正確輸入【Y/N】!")
else:
    print("查無此書")

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

2、遇明主,用戶確認下載小說

上一步寫得search_book() 函數在搜到小說得情況下返回了三個數據:小說名、作者名、小說得url,經由用戶確認,調用biqugeCrawl.download() ,獲取小說章節鏈接,並採用多線程下載內容。

2.1、 請求網頁,獲取鏈接,因爲它有九個最新章節得在裏面,所以我們使用xpath解析出來得列表要略去這九個,直接用切片切掉。
在這裏插入圖片描述

response = requests.get(url=url, headers=self.__random_ua())
root = etree.HTML(response.content)

# 拼接完整得章節url 
content_urls = list(map(lambda x: "http://www.biquge.tv/" + x, root.xpath('//*[@id="list"]/dl/dd/a/@href')[9:]))
content_names = root.xpath('//*[@id="list"]/dl/dd/a/text()')[9:]

 # 創建以書名命名的文件夾
path = '《' + book_name + '》'
if not os.path.exists(path):
	os.mkdir(path)

2.2、 有了相應得鏈接,這裏就要使用多進程了,速度大大的增加。

utls_names = [i for i in zip(content_urls, content_names)]

# 同時進行得進程數量
pool = Pool(processes=5)

# 將utls_names中得元素分配給parse()函數
pool.map(self.parse, utls_names)

3、取荊州,下載小說內容,保存到docx文檔中

上面一步是獲取到了小說章節的目錄,並採用多進程的方法調用了biqugeCrawl.parse() 函數,這個函數的作用就是下載並保存小說內容。
3.1、 解析出小說的主體內容,觀察網頁源碼,可以發現,小說的內容都在<div id="content"></div>html標籤中,我用正則表達式將其解析出來,並對文本做了一些處理。
在這裏插入圖片描述

response = requests.get(url=url, headers=self.__random_ua())
html = response.content.decode('gbk')
content = "".join(re.findall('<div id="content">(.*?)</div>', html, re.S))
content = re.sub("<.*?>", "", re.sub("&nbsp;&nbsp;", " ", content))

3.2、 獲取到的小說內容肯定是要保存起來的,我這裏保存到了docx文檔文件中。這裏又用xapth解析出了小說名,作爲文本保存路徑的一部分。

我後面想想那小說章節名也可以不傳進來,在這裏解析就是了,算了,無傷大雅

root = etree.HTML(response.content)
book_name = root.xpath('//div[@class="con_top"]/a[2]/text()')[0]

# 創建document對象
document = Document()

# 將文本寫入文檔
document.add_paragraph(content)

document.save("《{}》/《{}》.docx".format(book_name, name))

print('《' + name + '》已下載完成')

爬蟲的思路都理清楚了,還有一些旁支末節詳見完整代碼,有什麼不清楚也可以直接來問我。

三、運行測試

做了一些簡單的測試,按照提示來操作都是沒問題的,就算不按照提示來,整些奇奇怪怪的操作也是沒問題的,很舒服,而且速度也快,我爬取《聖墟》一千五百多章大概耗時三分鐘,也可以更快,不過我們只是學習爬蟲的技術又不是搞破壞,太快的話對服務器的壓力也很大的,還有被封IP的可能,可以參考【構建簡單IP代理池】,使用代理ip,這樣更安全。

IP代理池我自己是改了好幾版了,用起來還是很舒服的,不用擔心被封ip的事情了,把ip數據存到數據庫中,上手簡單,建議學習搭建。

在這裏插入圖片描述

四、總結

這爬取筆趣閣本來就是個小爬蟲,我感覺很適合讓剛入爬蟲的同學學習的,雖然相比那種只爬取單個網頁的入門學習方式要多些困難,不過克服困難後還是會有很大的進步的,這是我整理的【爬蟲實戰項目集合】,有興趣的可以看看,由淺入深的學習爬蟲,最簡單的和較爲複雜的都有。

對於這個爬筆趣閣的項目,我認爲有一個很大的槽點,它是多進程爬取出來的,文件按時間排序不行,按名稱排序又是下面這樣,emm,太難看了,還是之前老版本的非多進程好看,但就是慢些,沒有思路解決這個問題,求教。
在這裏插入圖片描述
另外,我想在爬蟲里加上暫定下載的功能,以及斷網處理的功能(這兩個功能是一個原理,搞一個任務隊列),這爬取筆趣閣小爬蟲是小爬蟲,但小事情多做幾次,就會越發的精細,好了,也沒啥講的了,祝大家寫程序沒有bug,天天開心。

啊,對啦對啦,點個贊,點個關注吧,蟹蟹支持。

五、完整代碼

import os
from urllib import parse
import requests
from docx import Document
from lxml import etree
import re
import random
from multiprocessing import Pool

import time


class biqugeCrawl():

    m_url = "http://www.biquge.tv/"

	 # 查詢書籍函數
    def search_book(self):
        serach_url = "http://www.biquge.tv/modules/article/search.php?searchkey={}"

        name = input("請輸入書名:")

        name = parse.quote(name.encode('gbk'))

        url = serach_url.format(name)

        response = requests.get(url=url, headers=self.__random_ua())

        root = etree.HTML(response.content)

        try:
            book_name = root.xpath('//*[@id="nr"]/td[1]/a/text()')[0]

            book_author = root.xpath('//*[@id="nr"]/td[3]/text()')[0]

            book_url = root.xpath('//*[@id="nr"]/td[1]/a/@href')[0]

            return [book_name, book_author, book_url]

        except:
            return None

    # 下載函數
    def download(self, book_name, url):
        response = requests.get(url=url, headers=self.__random_ua())
        root = etree.HTML(response.content)

        content_urls = list(map(lambda x: "http://www.biquge.tv/" + x, root.xpath('//*[@id="list"]/dl/dd/a/@href')[9:]))
        content_names = root.xpath('//*[@id="list"]/dl/dd/a/text()')[9:]

        # 創建以書名命名的文件夾
        path = '《' + book_name + '》'
        if not os.path.exists(path):
            os.mkdir(path)

        utls_names = [i for i in zip(content_urls, content_names)]

        pool = Pool(processes=5)
        pool.map(self.parse, utls_names)

        print("《{}》下載完畢!".format(book_name))


    # 爬取文章內容函數
    def parse(self, url_name):
        url = url_name[0]
        name = url_name[1]

        try:
            response = requests.get(url=url, headers=self.__random_ua())
            html = response.content.decode('gbk')
    
            root = etree.HTML(response.content)
            book_name = root.xpath('//div[@class="con_top"]/a[2]/text()')[0]
    
            content = "".join(re.findall('<div id="content">(.*?)</div>', html, re.S))
    
            content = re.sub("<.*?>", "", re.sub("&nbsp;&nbsp;", " ", content))

            # 創建document對象
            document = Document()

            # 將文本寫入文檔
            document.add_paragraph(content)

            document.save("《{}》/《{}》.docx".format(book_name, name))

            print('《' + name + '》已下載完成')

        except Exception as e:
            with open("./log.txt", "a+", encoding="utf-8") as file:
                file.write("*"*30+"\n"+str(e))
            print("出現異常,下載中斷,請查看log文件!")
            pass

    # 隨機UA
    def __random_ua(self):
        UA = ["Mozilla/5.0 (Windows NT 5.1; rv:7.0.1) Gecko/20100101 Firefox/7.0.1",
              "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:54.0) Gecko/20100101 Firefox/54.0",
              "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1",
              "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0",
              "Mozilla/5.0 (X11; U; Linux Core i7-4980HQ; de; rv:32.0; compatible; JobboerseBot;Gecko/20100101 Firefox/38.0",
              "Mozilla/5.0 (Windows NT 5.1; rv:36.0) Gecko/20100101 Firefox/36.0",
              "Mozilla/5.0 (Windows NT 5.1; rv:33.0) Gecko/20100101 Firefox/33.0",
              "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0",
              "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0",
              "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0",
              "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0",
              "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0",
              "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.12) Gecko/20050915 Firefox/1.0.7",
              "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:17.0) Gecko/20100101 Firefox/17.0",
              "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:54.0) Gecko/20100101 Firefox/54.0",
              "Mozilla/5.0 (Windows NT 6.0; rv:34.0) Gecko/20100101 Firefox/34.0",
              "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0",
              "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0",
              "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0",
              "Mozilla/5.0 (Windows NT 5.1; rv:40.0) Gecko/20100101 Firefox/40.0",
              "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0",
              "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:42.0) Gecko/20100101 Firefox/42.0",
              "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5) Gecko/20041107 Firefox/1.0",
              "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0",
              "Mozilla/5.0 (Windows NT 6.1; rv:17.0) Gecko/20100101 Firefox/20.6.14",
              "Mozilla/5.0 (Windows NT 5.1; rv:30.0) Gecko/20100101 Firefox/30.0",
              "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10; rv:33.0) Gecko/20100101 Firefox/33.0",
              "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/29.0",
              "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0",
              "Mozilla/5.0 (Windows NT 6.1; rv:52.0) Gecko/20100101 Firefox/52.0",
              "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0",
              "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0",
              "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0",
              "Mozilla/5.0 (X11; U; Linux Core i7-4980HQ; de; rv:32.0; compatible; JobboerseBot; Gecko/20100101 Firefox/38.0",
              "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0"
              ]

        headers = {
            "User-Agent": {}
        }

        headers["User-Agent"] = random.choice(UA)

        return headers

   

if __name__ == '__main__':
    biquge = biqugeCrawl()
    print("----筆趣閣小說爬蟲----")
    print("1-------------搜索小說")
    print("2----------------退出")

    flag = input("請輸入指令選擇相應功能:")

    while 1:
        error_str = ""

        if flag == "1":
            book = biquge.search_book()

            if book is not None:
                flag = input("搜索到的書名爲《{}》,作者名爲{},請確認是否下載【Y/N】".format(book[0], book[1]))
                while 1:
                    if flag == 'Y' or flag == 'y':
                        biquge.download(book[0], book[2])
                        break
                    elif flag == 'N' or flag == 'n':
                        break
                    else:
                        flag = input("請正確輸入【Y/N】!")
            else:
                print("查無此書")

        elif flag == "2":
            exit(1)

        else:
            error_str = "指令錯誤,"
        flag = input("{}請重新輸入指令選擇相應功能【1.搜索;2.退出】:".format(error_str))

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