1. 程式人生 > >python爬蟲入門之爬取小說.md

python爬蟲入門之爬取小說.md

新手教學:用Python爬取小說

我們在學習Python之餘總想著讓其更具趣味性,可以更好地學習。下面我將講解如何去從網站中爬取我們想看的小說。讓我們枯燥無聊的學習生涯稍微多些趣味。 需要只是一點點對requests庫、Beautiful庫及python基礎知識的瞭解。 Python版本:Python3.X 執行平臺:Windows IDE:PyCharm 瀏覽器:Chrome 本次講解所有程式碼已放入GitHub上託管。詳細瞭解請點選

瞭解爬蟲

我們一直口口聲聲說著用爬蟲去爬取某某資料。 那麼問題來了,什麼是爬蟲呢? 網路爬蟲,別稱網路蜘蛛,它是一段程式(一個指令碼),能夠模擬瀏覽器自動的瀏覽網頁,來自動地批量地採集我們需要的資源。 廢話不多說,開始今天的爬蟲之旅。

爬取章節內容

俗話說得好,飯要一口一口吃,路要一步一步走。我們要爬取每一章內容,須先知曉如何爬取一章內容。 本次爬取網站:筆趣閣 本次爬取小說:《修真四萬年》 第一步,根據URL獲取網頁的HTML資訊 在python3中可以用requests庫進行網頁爬取。

import requests
if __name__=='__main__':	
	target='http://www.biquge.cm/6/6217/3638117.html'
	req=requests.get(url=target)
	print(req.text)
	

執行結果如下: 我們輕鬆地獲得了HTML資訊,但為什麼會出現亂碼呢? 事實上這是由於字元編碼問題引起的。往往用requests庫抓取中文網頁時容易出現亂碼問題。那如何解決呢? 下面介紹兩種方法: (1)我們可以在呼叫response.text()之前使用response.encoding=‘編碼格式’ 這個方法需要我們進入所爬去網站去檢視編碼格式。 點選滑鼠右鍵,點選檢查,得到如下介面:

點選右側head標籤得到如圖: 可以很顯然看到編碼格式為‘gbk’。 所以我們可以寫出程式碼來解決亂碼問題:

import requests
if __name__=='__main__':
	target='http://www.biquge.cm/6/6217/3638117.html'
	req=requests.get(url=target)
	req.encoding='gbk'
	print(req.text)

執行得到: (2)使用req.content返回bytes型資料,然後將bytes型資料轉化為str資料。 程式碼如下:

import requests
if __name__=='__main__':
	target='http://www.biquge.cm/6/6217/3638117.html'
	req=requests.get(url=target)
	html=req.content
	html_doc=str(html,'gbk')
	print(html_doc)

解決完亂碼問題後我們得到了我們需要的能理解看懂的html資訊,但是,有很多資訊是我們不想看到的,我們只想要小說正文內容。我們不關心div、br這些html標籤。 那如何把正文內容從這些眾多的html標籤中提取出來呢? 這就用到了BeautifulSoup庫。 同理按上面方法我們檢視第一章目標網頁: 我們可以看到文章的所有內容都放在了一個名為div的東西下面,這個東西就是html標籤。 HTML標籤是HTML語言中最基本的單位,HTML標籤是HTML最重要的組成部分。 我們可以看到,我們所需的文字內容在一個div標籤下,這個標籤是這樣的:

div id=“content”

知道了這個內容,我們就可以用BeautifulSoup來提取我們想要的內容了。編寫程式碼如下:

from bs4 import BeautifulSoup
import requests
if __name__=='__main__':
	target='http://www.biquge.cm/6/6217/3638117.html'
	req=requests.get(url=target)
	html=req.content
	html_doc=str(html,'gbk')
	bf=BeautifulSoup(html_doc)
	texts=bf.find_all('div',id="content")
	print(texts)

在解析html之前,先建立一個BeautifulSoup物件,BeautifulSoup函式裡面的引數就是我們獲得的html資訊。然後使用find_all方法,獲取html資訊中所有id屬性為content的div標籤。 執行程式碼檢視我們匹配結果: 我們可以看到,我們已經順利匹配到我們關心的正文內容,但是還有一些我們不想要的東西。比如div標籤名,br標籤,以及各種空格。怎麼去除這些東西呢?我們繼續編寫程式碼:

from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
     target = 'http://www.biquge.cm/6/6217/3638117.html'
     req = requests.get(url = target)
     html = req.content
     html_doc=str(html,'gbk')
     bf = BeautifulSoup(html_doc)
     texts = bf.find_all('div', id="content")
     print(texts[0].text)

find_all匹配的返回的結果是一個列表。提取匹配結果後,使用text屬性,提取文字內容,濾除br標籤。得到:

我們已經順利獲得了一個章節的內容,要想下載正本小說,我們就要獲取每個章節的連結。我們先分析下小說目錄:http://www.biquge.cm/6/6217/

在網址進行審查得到:

我們看到每個章節的名字存放在了<a>標籤裡面。<a>標籤還有一個href屬性。<a>標籤定義了一個超連結,用於從一張頁面連結到另一張頁面。<a> 標籤最重要的屬性是 href 屬性,它指示連結的目標。

我們將之前獲得的第一章節的URL和<a> 標籤對比看一下:

我們可以看到<a> 標籤中href屬性存放的屬性值/1_1094/5403177.html是章節URLhttp://www.biqukan.com/1_1094/5403177.html的後半部分。那這樣,我們就可以根據<a>標籤的href屬性值獲得每個章節的連結和名稱了。

編寫程式碼如下:

from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
     target = 'http://www.biquge.cm/6/6217/'
     req = requests.get(url = target)
     html = req.content
     html_doc=str(html,'gbk')
     div_bf = BeautifulSoup(html_doc)
     div = div_bf.find_all('div', id='list' )
     print(div[0])

執行結果:

接下來再匹配每一個<a>標籤,並用a.get(‘href’)方法獲取href的屬性值,使用a.string方法獲取章節名。程式碼如下:

from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
     server = 'http://www.biquge.cm/'
     target =  'http://www.biquge.cm/6/6217/'
     req = requests.get(url = target)
     html = req.content
     html_doc=str(html,'gbk')
     div_bf = BeautifulSoup(html_doc)
     div = div_bf.find_all('div', id='list'  )
     a_bf = BeautifulSoup(str(div[0]))
     a = a_bf.find_all('a')
     for each in a:
          print(each.string, server + each.get('href'))

find_all返回的是一個列表,裡邊存放了很多的<a>標籤,所以使用for迴圈遍歷每個<a>標籤並打印出來,執行結果如下:

整合程式碼:

# -*- coding:UTF-8 -*-

from bs4 import BeautifulSoup
import requests, sys


class downloader(object):
    # 類說明下載筆趣閣修真四萬年,self只有在類的方法中才會有,
    # 獨立的函式或方法是不必帶有self的。self在定義類的方法時是必須有的,
    # 雖然在呼叫時不必傳入相應的引數。

    def __init__(self):  # 初始化變數
        # 爬取圖書網站,self.server='http://www.biquge.cm/' 的意思
        # 就是把外部傳來的引數server的值賦值給類downloader自己的屬性
        # 變數self.server,下面函式可共用
        self.server = 'http://www.biquge.cm/'
        self.target = 'http://www.biquge.cm/6/6217/'
        # 爬取圖書主目錄所在網址,用來獲取每一章節url
        self.names = []  # 存放章節名
        self.urls = []  # 存放章節連結
        self.nums = 0  # 章節數

    def get_download_url(self):  # 獲取下載連結
        req = requests.get(url=self.target)  # 獲得該網頁的html資訊
        html = req.content  # #獲得html檔案
        html_doc=str(html,'gbk')
        div_bf = BeautifulSoup(html_doc)  # 建立一個BeautifulSoup物件
        div = div_bf.find_all('div', id='list')
        # 匹配div標籤,獲取id屬性list的內容,用find_all是因為獲得的是列表
        a_bf = BeautifulSoup(str(div[0]))  # 以list中的內容構建Beautiful物件
        a = a_bf.find_all('a')  # 找到所有a標籤,a標籤中為每一章網址
        self.nums = len(a[:])  # 索引從第一章到最後一章,記數
        for each in a[:]:
            self.names.append(each.string)  # 新增a標籤下的字串即章節名
            self.urls.append(self.server + each.get('href'))
        # 獲取每一章連結,用get()方法獲取a標籤中href屬性

    def get_contents(self, target):  # 獲取章節內容
        req = requests.get(url=target)
        html = req.content
        html_doc=str(html,'gbk')
        bf = BeautifulSoup(html_doc)
        texts = bf.find_all('div', id="content")
        # 獲取div標籤id屬性content的內容
        texts = texts[0].text.replace('\xa0'*8,'\n')
        # 剔除"'div'標籤及'id=content'"和br/,
        # 提取texts內容中text文字
        return texts

    def writer(self, name, path, text):  # 將爬取的文章內容寫入檔案
        with open(path, 'a' ,encoding='utf-8')as f:
            # ‘a’表示追加到檔案,encoding='utf-8'表示以utf-8進行編碼 
            f.write(name + '\n')  # write方法寫入章節名並換行
            f.writelines(text)  # writelines方法寫入文章內容
            f.write('\n')  # 換行寫完一章


# 當.py檔案被直接執行時,if __name__ == '__main__'之下的程式碼塊將被執行
if __name__ == "__main__":
    dl = downloader()
    dl.get_download_url()
    print('《修真四萬年》開始下載:')
    for i in range(dl.nums):
        dl.writer(dl.names[i], '修真四萬年.txt', dl.get_contents(dl.urls[i]))
        sys.stdout.write("  已下載:%.3f%%" % float(i / dl.nums) + '\r')
        # sys.stdout是對映到開啟指令碼的視窗
        sys.stdout.flush()
    # 在Linux系統下,必須加入sys.stdout.flush()才能一秒輸一個數字
    # 在Windows系統下,加不加sys.stdout.flush()都能一秒輸出一個數字
    print('《修真四萬年》下載完成')