1. 程式人生 > >Python爬蟲系列:爬取小說並寫入txt檔案

Python爬蟲系列:爬取小說並寫入txt檔案

Python爬蟲系列

——爬取小說並寫入txt檔案



    文章介紹瞭如何從網站中爬取小說並寫入txt檔案中,實現了單章節寫取,整本寫取,多執行緒多本寫取。爬蟲使用的python版本為python3,有些系統使用python指令執行本指令碼,可能出現錯誤,此時可以試一試使用python3執行本指令碼。
    本文是一個教程,一步步介紹瞭如何爬取批量小說內容以及儲存這是txt檔案中,以下是專案原始碼地址。
爬蟲原始碼地址:https://git.oschina.net/XPSWorld/get_txt.git

1.使用到庫檔案
  • requests:用於get請求
  • threading:多執行緒
  • bs4:網頁解析
  • re:正則表示式
  • os:系統相關操作
  • time:獲取時間

    以下是整個爬蟲所用的庫檔案,如若沒有對應庫檔案,可以使用pip獲取,例如獲取threading庫檔案:pip install threading

import  requests
import threading
from bs4 import BeautifulSoup
import re
import os
import time
2.對網頁檔案結構進行分析(PS:瀏覽器使用的是谷歌瀏覽器)

    通過對 的檔案結構進行分析,知道了每一本小說的目錄地址為該地址加上book/,再加上對應的小說編號,如編號為1的小說地址為,在瀏覽器開啟該網址,就可以看到如下類似的介面
顯示介面


    以此類推就可以知道每一本的小說地址。


3.獲取網頁的請求標頭檔案

    我們以編號為1的小說地址為例(),開啟谷歌的開發者工具,選擇Network,會出現如下介面,如果沒有對應的列表資訊,重新整理一下網頁即可。
這裡寫圖片描述


    然後點選1/,出現以下資訊:
這裡寫圖片描述

    我們需要的是第二個方框中的內容(Request Headers),將該目錄下的資訊取出,存放到字典中,其中每一個項所代表的意義如果感興趣可自行網上搜索(HTTP Header 詳解)。

#請求頭字典
req_header={
'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
, 'Accept-Encoding':'gzip, deflate', 'Accept-Language':'zh-CN,zh;q=0.8', 'Cookie':'__cfduid=d577ccecf4016421b5e2375c5b446d74c1499765327; UM_distinctid=15d30fac6beb80-0bdcc291c89c17-9383666-13c680-15d30fac6bfa28; CNZZDATA1261736110=1277741675-1499763139-null%7C1499763139; tanwanhf_9821=1; Hm_lvt_5ee23c2731c7127c7ad800272fdd85ba=1499612614,1499672399,1499761334,1499765328; Hm_lpvt_5ee23c2731c7127c7ad800272fdd85ba=1499765328; tanwanpf_9817=1; bdshare_firstime=1499765328088', 'Host':'www.qu.la', 'Proxy-Connection':'keep-alive', 'Referer':'http://www.qu.la/book/1265/765108.html', 'Upgrade-Insecure-Requests':'1', 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36' }
4.分析每章小說網頁結構

    每一本小說多有對應的章節網頁,也就說每一張都有對應的網頁,我們以編號為1的小說中任意章節為例,其地址資訊為,其中“260824.html”就是該章節的網頁名稱,點選開發者工具中的 Element 選項,以下是對應的截圖資訊,通過分析,小說章節資訊的路徑為:#wrapper .content_read .box_con (PS:其中“#wrapper”號表示id為wrapper的項,“.content_read”表示class為content_read的項,按照此順序放在一起就表示id為wrapper的項中的class為content_read的項中的class為con_box的相關資訊,三者為樹形關係。)
    在該路徑下,我們需要的資訊主要有三項,以下是對應的class內容以及對應的說明:

  • bookname:可獲取章節名稱
  • content:可獲取章節內容
  • bottom2:可獲取下一章節地址

這裡寫圖片描述


5.獲取單獨一章內容

    對於BeautifulSoup不是特別瞭解的,可以先閱讀以下幾篇文章:

    以下是獲取單章章節內容的部分程式碼,需將文章上所述的庫檔案以及請求標頭檔案貼上方可執行以下程式碼(PS【重要】:python想要使用漢字,需要在指令碼最前面新增 #coding:utf-8,漢字使用的編碼為utf-8,否則會出現錯誤):

req_url_base='http://www.qu.la/book/'           #小說主地址
req_url=req_url_base+"1/"                       #單獨一本小說地址
txt_section='260824.html'                       #某一章頁面地址

#請求當前章節頁面  params為請求引數
r=requests.get(req_url+str(txt_section),params=req_header) 
#soup轉換
soup=BeautifulSoup(r.text,"html.parser")    
#獲取章節名稱                                    
section_name=soup.select('#wrapper .content_read .box_con .bookname h1')[0]        
#獲取章節文字
section_text=soup.select('#wrapper .content_read .box_con #content')[0].text             
for ss in section_text.select("script"):                #刪除無用項
    ss.decompose()
#按照指定格式替換章節內容,運用正則表示式
section_text=re.sub( '\s+', '\r\n\t', section_text.text).strip('\r\n')          

print('章節名:'+section_name)
print("章節內容:\n"+section_text)

執行效果截圖:
文字輸出


6.將獲取的文字資訊寫入txt檔案中

    在實際操作之前,如果大家對於檔案操作以及編碼轉換不是很瞭解的,可以先看看以下兩篇文章:

    以下是相關原始碼以及註釋(PS:在原有程式碼的基礎上新增即可):

fo = open('1.txt', "ab+")         #開啟小說檔案
# 以二進位制寫入章節題目 需要轉換為utf-8編碼,否則會出現亂碼
fo.write(('\r' + section_name + '\r\n').encode('UTF-8'))  
# 以二進位制寫入章節內容
fo.write((section_text).encode('UTF-8'))  
fo.close()        #關閉小說檔案
7.獲取整本小說

    通過前面幾個步驟,我們知道了如何獲取單章小說相關資訊寫入txt中,接下來獲取整本小說內容就是在其基礎上進行改進的,我們將通過一個函式來實現獲取整本內容,以下是函式程式碼:

#小說下載函式
#id:小說編號
#txt字典項介紹
# title:小說題目
# first_page:第一章頁面
# txt_section:章節地址
# section_name:章節名稱
# section_text:章節正文
# section_ct:章節頁數
def get_txt(txt_id):
    txt={}
    txt['title']=''
    txt['id']=str(txt_id)
    try:
        print("請輸入需要下載的小說編號:")
        txt['id']=input()
        req_url=req_url_base+ txt['id']+'/'                        #根據小說編號獲取小說URL
        print("小說編號:"+txt['id'])
        res=requests.get(req_url,params=req_header)             #獲取小說目錄介面
        soups=BeautifulSoup(res.text,"html.parser")           #soup轉化
        #獲取小說題目
        txt['title']=soups.select('#wrapper .box_con #maininfo #info h1')[0].text     
        txt['author']=soups.select('#wrapper .box_con #maininfo #info p')
        #獲取小說最近更新時間
        txt['update']=txt['author'][2].text                                                       
        #獲取最近更新章節名稱
        txt['lately'] = txt['author'][3].text                                                     
        #獲取小說作者
        txt['author']=txt['author'][0].text                                                       
        #獲取小說簡介
        txt['intro']=soups.select('#wrapper .box_con #maininfo #intro')[0].text.strip()            
        print("編號:"+'{0:0>8}   '.format(txt['id'])+  "小說名:《"+txt['title']+"》  開始下載。")
        print("正在尋找第一章頁面。。。")
        #獲取小說所有章節資訊
        first_page=soups.select('#wrapper .box_con #list dl dd a')                          
        #獲取小說總章頁面數
        section_ct=len(first_page)                                                                  
        #獲取小說第一章頁面地址
        first_page = first_page[0]['href'].split('/')[3]                                        
        print("小說章節頁數:"+str(section_ct))
        print("第一章地址尋找成功:"+ first_page)
        #設定現在下載小說章節頁面
        txt_section=first_page                                                                  
        #開啟小說檔案寫入小說相關資訊
        fo = open('{0:0>8}-{1}.txt.download'.format(txt['id'],txt['title']), "ab+")         
        fo.write((txt['title']+"\r\n").encode('UTF-8'))
        fo.write((txt['author'] + "\r\n").encode('UTF-8'))
        fo.write((txt['update'] + "\r\n").encode('UTF-8'))
        fo.write((txt['lately'] + "\r\n").encode('UTF-8'))
        fo.write(("*******簡*******\r\n").encode('UTF-8'))
        fo.write(("\t"+txt['intro'] + "\r\n").encode('UTF-8'))
        fo.write(("******************\r\n").encode('UTF-8'))
        #進入迴圈,寫入每章內容
        while(1):
            try:
                #請求當前章節頁面
                r=requests.get(req_url+str(txt_section),params=req_header)                      
                #soup轉換
                soup=BeautifulSoup(r.text,"html.parser")       
                #獲取章節名稱                                 
                section_name=soup.select('#wrapper .content_read .box_con .bookname h1')[0]
                section_text=soup.select('#wrapper .content_read .box_con #content')[0]
                for ss in section_text.select("script"):   #刪除無用項
                    ss.decompose()
                #獲取章節文字
                section_text=re.sub( '\s+', '\r\n\t', section_text.text).strip('\r\n')#
                #獲取下一章地址
                txt_section=soup.select('#wrapper .content_read .box_con .bottem2 #A3')[0]['href']       
                #判斷是否最後一章,當為最後一章時,會跳轉至目錄地址,最後一章則跳出迴圈               
                if(txt_section=='./'):                                                          
                    print("編號:"+'{0:0>8}   '.format(txt['id'])+  "小說名:《"+txt['title']+"》 下載完成")
                    break
                #以二進位制寫入章節題目
                fo.write(('\r'+section_name.text+'\r\n').encode('UTF-8'))                                
                #以二進位制寫入章節內容
                fo.write((section_text).encode('UTF-8'))                        
                print(txt['title']+' 章節:'+section_name.text+'     已下載')
                #print(section_text.text.encode('UTF-8'))
            except:
                print("編號:"+'{0:0>8}   '.format(txt['id'])+  "小說名:《"+txt['title']+"》 章節下載失敗,正在重新下載。")
        fo.close()
        os.rename('{0:0>8}-{1}.txt.download'.format(txt['id'],txt['title']), '{0:0>8}-{1}.txt'.format(txt['id'],txt['title']))
    except:     #出現錯誤會將錯誤資訊寫入dowload.log檔案,同時答應出來
        fo_err = open('dowload.log', "ab+")
        try:
            fo_err.write(('['+time.strftime('%Y-%m-%d %X', time.localtime())+"]:編號:" + '{0:0>8}   '.format(txt['id']) + "小說名:《" + txt['title'] + "》 下載失敗。\r\n").encode('UTF-8'))
            print('['+time.strftime('%Y-%m-%d %X', time.localtime())+"]:編號:"+'{0:0>8}   '.format(txt['id'])+  "小說名:《"+txt['title']+"》 下載失敗。")
            os.rename('{0:0>8}'.format(txt['id']) + '-' + txt['title'] + '.txt.download',
                  '{0:0>8}'.format(txt['id']) + '-' + txt['title'] + '.txt.error')
        except:     
            fo_err.write(('['+time.strftime('%Y-%m-%d %X', time.localtime())+"]:編號:"+'{0:0>8}   '.format(txt['id'])+"下載失敗。\r\n").encode('UTF-8'))
            print('['+time.strftime('%Y-%m-%d %X', time.localtime())+"]:編號:"+'{0:0>8}   '.format(txt['id'])+"下載失敗。")
        finally: #關閉檔案
            fo_err.close()

    如果有需要爬取的相關小說,只需要在該網站找到小說編號,然後呼叫該函式就可以將小說下載至本電腦,如需下載編號為6666的小說,則呼叫get_txt(6666)即可,在下載過程中,檔案字尾為“.txt.download”,下載完成後會將檔案字尾變為“.txt”。
執行效果
執行效果


8.多執行緒爬取多本小說

    同樣的,在此之前如果對於python執行緒不瞭解的可以閱讀以下文章:

    關於多執行緒的程式碼就不過多介紹了,在專案原始碼中會有相關的使用方法。(PS:通過實驗,每次同步下載100本小說最好,太多的話程序可能會被殺死)


9.最終效果

    最終的原始碼實現如下效果:

  • 每次同步爬取100本小說
  • 會生成一個關於小說的介紹文件,文件介紹了每次爬取的100本小說
  • 顯示對應進度資訊
  • 小說還未下載完檔案字尾為“.txt.download”,下載完成後會將檔案字尾變為“.txt”

以下是執行效果圖:
執行效果圖

執行結果圖

    在執行結果圖中,標號為1的部分是已經爬取完成的小說;編號為2的為還在下載的小說;編號為3的檔案是下載錯誤日誌,當不存在相關編號小說,則會記錄在該檔案中,下圖為檔案內容;編號為4的為每100本小說的簡介,在我們通過該指令碼,就可以知道所爬取的小說有哪些,通過然後通過編號就可以找到對應小說,下圖同樣展示其相關內容。

download.log檔案內容:
download.log檔案內容

小說簡介檔案內容:
小說簡介檔案內容

10.其他(教程原始碼)

    鑑於有朋友說提供的專案原始碼(多執行緒多本)與教程(單執行緒單本)不符,所將以上教程中單本小說下載的原始碼貼上,大家可以直接複製執行。

#coding:utf-8
import  requests
import threading
from bs4 import BeautifulSoup
import re
import os
import time
import sys
req_header={
'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Encoding':'gzip, deflate',
'Accept-Language':'zh-CN,zh;q=0.8',
'Cookie':'__cfduid=d577ccecf4016421b5e2375c5b446d74c1499765327; UM_distinctid=15d30fac6beb80-0bdcc291c89c17-9383666-13c680-15d30fac6bfa28; CNZZDATA1261736110=1277741675-1499763139-null%7C1499763139; tanwanhf_9821=1; Hm_lvt_5ee23c2731c7127c7ad800272fdd85ba=1499612614,1499672399,1499761334,1499765328; Hm_lpvt_5ee23c2731c7127c7ad800272fdd85ba=1499765328; tanwanpf_9817=1; bdshare_firstime=1499765328088',
'Host':'www.qu.la',
'Proxy-Connection':'keep-alive',
'Referer':'http://www.qu.la/book/',
'Upgrade-Insecure-Requests':'1',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36'
}


req_url_base='http://www.qu.la/book/'           #小說主地址

#小說下載函式
#txt_id:小說編號
#txt字典項介紹
#id:小說編號
# title:小說題目
# first_page:第一章頁面
# txt_section:章節地址
# section_name:章節名稱
# section_text:章節正文
# section_ct:章節頁數
def get_txt(txt_id):
    txt={}
    txt['title']=''
    txt['id']=str(txt_id)
    try:
        #print("請輸入需要下載的小說編號:")
        #txt['id']=input()
        req_url=req_url_base+ txt['id']+'/'                        #根據小說編號獲取小說URL
        print("小說編號:"+txt['id'])
        res=requests.get(req_url,params=req_header)             #獲取小說目錄介面
        soups=BeautifulSoup(res.text,"html.parser")           #soup轉化
        #獲取小說題目
        txt['title']=soups.select('#wrapper .box_con #maininfo #info h1')[0].text
        txt['author']=soups.select('#wrapper .box_con #maininfo #info p')
        #獲取小說最近更新時間
        txt['update']=txt['author'][2].text
        #獲取最近更新章節名稱
        txt['lately'] = txt['author'][3].text
        #獲取小說作者
        txt['author']=txt['author'][0].text
        #獲取小說簡介
        txt['intro']=soups.select('#wrapper .box_con #maininfo #intro')[0].text.strip()
        print("編號:"+'{0:0>8}   '.format(txt['id'])+  "小說名:《"+txt['title']+"》  開始下載。")
        print("正在尋找第一章頁面。。。")
        #獲取小說所有章節資訊
        first_page=soups.select('#wrapper .box_con #list dl dd a')
        #獲取小說總章頁面數
        section_ct=len(first_page)
        #獲取小說第一章頁面地址
        first_page = first_page[0]['href'].split('/')[3]
        print("小說章節頁數:"+str(section_ct))
        print("第一章地址尋找成功:"+ first_page)
        #設定現在下載小說章節頁面
        txt_section=first_page
        #開啟小說檔案寫入小說相關資訊
        fo = open('{0:0>8}-{1}.txt.download'.format(txt['id'],txt['title']), "ab+")
        fo.write((txt['title']+"\r\n").encode('UTF-8'))
        fo.write((txt['author'] + "\r\n").encode('UTF-8'))
        fo.write((txt['update'] + "\r\n").encode('UTF-8'))
        fo.write((txt['lately'] + "\r\n").encode('UTF-8'))
        fo.write(("*******簡*******\r\n").encode('UTF-8'))
        fo.write(("\t"+txt['intro'] + "\r\n").encode('UTF-8'))
        fo.write(("******************\r\n").encode('UTF-8'))
        #進入迴圈,寫入每章內容
        while(1):
            try:
                #請求當前章節頁面
                r=requests.get(req_url+str(txt_section),params=req_header)
                #soup轉換
                soup=BeautifulSoup(r.text,"html.parser")
                #獲取章節名稱
                section_name=soup.select('#wrapper .content_read .box_con .bookname h1')[0]
                section_text=soup.select('#wrapper .content_read .box_con #content')[0]
                for ss in section_text.select("script"):   #刪除無用項
                    ss.decompose()
                #獲取章節文字
                section_text=re.sub( '\s+', '\r\n\t', section_text.text).strip('\r\n')#
                #獲取下一章地址
                txt_section=soup.select('#wrapper .content_read .box_con .bottem2 #A3')[0]['href']
                #判斷是否最後一章,當為最後一章時,會跳轉至目錄地址,最後一章則跳出迴圈
                if(txt_section=='./'):
                    print("編號:"+'{0:0>8}   '.format(txt['id'])+  "小說名:《"+txt['title']+"》 下載完成")
                    break
                #以二進位制寫入章節題目
                fo.write(('\r'+section_name.text+'\r\n').encode('UTF-8'))
                #以二進位制寫入章節內容
                fo.write((section_text).encode('UTF-8'))
                print(txt['title']+' 章節:'+section_name.text+'     已下載')
                #print(section_text.text.encode('UTF-8'))
            except:
                print("編號:"+'{0:0>8}   '.format(txt['id'])+  "小說名:《"+txt['title']+"》 章節下載失敗,正在重新下載。")
        fo.close()
        os.rename('{0:0>8}-{1}.txt.download'.format(txt['id'],txt['title']), '{0:0>8}-{1}.txt'.format(txt['id'],txt['title']))
    except:     #出現錯誤會將錯誤資訊寫入dowload.log檔案,同時答應出來
        fo_err = open('dowload.log', "ab+")
        try:
            fo_err.write(('['+time.strftime('%Y-%m-%d %X', time.localtime())+"]:編號:" + '{0:0>8}   '.format(txt['id']) + "小說名:《" + txt['title'] + "》 下載失敗。\r\n").encode('UTF-8'))
            print('['+time.strftime('%Y-%m-%d %X', time.localtime())+"]:編號:"+'{0:0>8}   '.format(txt['id'])+  "小說名:《"+txt['title']+"》 下載失敗。")
            os.rename('{0:0>8}'.format(txt['id']) + '-' + txt['title'] + '.txt.download',
                  '{0:0>8}'.format(txt['id']) + '-' + txt['title'] + '.txt.error')
        except:
            fo_err.write(('['+time.strftime('%Y-%m-%d %X', time.localtime())+"]:編號:"+'{0:0>8}   '.format(txt['id'])+"下載失敗。\r\n").encode('UTF-8'))
            print('['+time.strftime('%Y-%m-%d %X', time.localtime())+"]:編號:"+'{0:0>8}   '.format(txt['id'])+"下載失敗。")
        finally: #關閉檔案
            fo_err.close()

#此處為需要下載小說的編號,編號獲取方法在上文中已經講過。
get_txt(1150)

    文章有那塊不對的地方,希望大家幫忙指正改進。