筆趣看小說(shuō)Python3爬蟲抓取

2021-03-12 14:28 更新

第一個(gè) Python3 爬蟲實(shí)戰(zhàn)讓我們先來(lái)試下文字的抓取,本實(shí)戰(zhàn)將針對(duì)筆趣看小說(shuō)網(wǎng)進(jìn)行爬蟲設(shè)置,從而實(shí)現(xiàn)小說(shuō)文字的抓取下載。

(1) 實(shí)戰(zhàn)背景

小說(shuō)網(wǎng)站-筆趣看:URL:http://www.biqukan.com/

筆趣看是一個(gè)盜版小說(shuō)網(wǎng)站,這里有很多起點(diǎn)中文網(wǎng)的小說(shuō),該網(wǎng)站小說(shuō)的更新速度稍滯后于起點(diǎn)中文網(wǎng)正版小說(shuō)的更新速度。并且該網(wǎng)站只支持在線瀏覽,不支持小說(shuō)打包下載。因此,本次實(shí)戰(zhàn)就是從該網(wǎng)站爬取并保存一本名為《一念永恒》的小說(shuō),該小說(shuō)是耳根正在連載中的一部玄幻小說(shuō)。PS:本實(shí)例僅為交流學(xué)習(xí),支持耳根大大,請(qǐng)上起點(diǎn)中文網(wǎng)訂閱。

(2) 小試牛刀

我們先看下《一念永恒》小說(shuō)的第一章內(nèi)容,URL:http://www.biqukan.com/1_1094/5403177.html

20170928143125108

我們先用已經(jīng)學(xué)到的知識(shí)獲取 HTML 信息試一試,編寫代碼如下:

# -*- coding:UTF-8 -*-
import requests

if __name__ == '__main__':
    target = 'http://www.biqukan.com/1_1094/5403177.html'
    req = requests.get(url=target)
    print(req.text)

運(yùn)行代碼,可以看到如下結(jié)果:

2

可以看到,我們很輕松地獲取了 HTML 信息。但是,很顯然,很多信息是我們不想看到的,我們只想獲得如右側(cè)所示的正文內(nèi)容,我們不關(guān)心 div、br 這些 html 標(biāo)簽。如何把正文內(nèi)容從這些眾多的 html 標(biāo)簽中提取出來(lái)呢?這就是本次實(shí)戰(zhàn)的主要內(nèi)容。

(3)Beautiful Soup

爬蟲的第一步,獲取整個(gè)網(wǎng)頁(yè)的 HTML 信息,我們已經(jīng)完成。接下來(lái)就是爬蟲的第二步,解析 HTML 信息,提取我們感興趣的內(nèi)容。對(duì)于本小節(jié)的實(shí)戰(zhàn),我們感興趣的內(nèi)容就是文章的正文。提取的方法有很多,例如使用正則表達(dá)式、Xpath、Beautiful Soup 等。對(duì)于初學(xué)者而言,最容易理解,并且使用簡(jiǎn)單的方法就是使用 Beautiful Soup 提取感興趣內(nèi)容。

Beautiful Soup 的安裝方法和 requests 一樣,使用如下指令安裝(也是二選一):

  • pip install beautifulsoup4
  • easy_install beautifulsoup4

一個(gè)強(qiáng)大的第三方庫(kù),都會(huì)有一個(gè)詳細(xì)的官方文檔。我們很幸運(yùn),Beautiful Soup 也是有中文的官方文檔:http://beautifulsoup.readthedocs.io/zh_CN/latest/

同理,我會(huì)根據(jù)實(shí)戰(zhàn)需求,講解 Beautiful Soup 庫(kù)的部分使用方法,更詳細(xì)的內(nèi)容,請(qǐng)查看官方文檔。

現(xiàn)在,我們使用已經(jīng)掌握的審查元素方法,查看一下我們的目標(biāo)頁(yè)面,你會(huì)看到如下內(nèi)容:

3

不難發(fā)現(xiàn),文章的所有內(nèi)容都放在了一個(gè)名為 div 的“東西下面”,這個(gè)”東西”就是 html 標(biāo)簽。HTML 標(biāo)簽是 HTML 語(yǔ)言中最基本的單位,HTML 標(biāo)簽是 HTML 最重要的組成部分。不理解,沒(méi)關(guān)系,我們?cè)倥e個(gè)簡(jiǎn)單的例子:

一個(gè)女人的包包里,會(huì)有很多東西,她們會(huì)根據(jù)自己的習(xí)慣將自己的東西進(jìn)行分類放好。鏡子和口紅這些會(huì)經(jīng)常用到的東西,會(huì)歸放到容易拿到的外側(cè)口袋里。那些不經(jīng)常用到,需要注意安全存放的證件會(huì)放到不容易拿到的里側(cè)口袋里。

html 標(biāo)簽就像一個(gè)個(gè)“口袋”,每個(gè)“口袋”都有自己的特定功能,負(fù)責(zé)存放不同的內(nèi)容。顯然,上述例子中的div標(biāo)簽下存放了我們關(guān)心的正文內(nèi)容。這個(gè) div 標(biāo)簽是這樣的:

<div id="content", class="showtxt">

細(xì)心的朋友可能已經(jīng)發(fā)現(xiàn),除了 div 字樣外,還有 id 和 class。id 和 class 就是 div 標(biāo)簽的屬性,content 和 showtxt 是屬性值,一個(gè)屬性對(duì)應(yīng)一個(gè)屬性值。這東西有什么用?它是用來(lái)區(qū)分不同的div標(biāo)簽的,因?yàn)?div 標(biāo)簽可以有很多,我們?cè)趺醇右詤^(qū)分不同的 div 標(biāo)簽?zāi)??就是通過(guò)不同的屬性值。

仔細(xì)觀察目標(biāo)網(wǎng)站一番,我們會(huì)發(fā)現(xiàn)這樣一個(gè)事實(shí):class 屬性為 showtxt 的 div 標(biāo)簽,獨(dú)一份!這個(gè)標(biāo)簽里面存放的內(nèi)容,是我們關(guān)心的正文部分。

知道這個(gè)信息,我們就可以使用 Beautiful Soup 提取我們想要的內(nèi)容了,編寫代碼如下:

# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
     target = 'http://www.biqukan.com/1_1094/5403177.html'
     req = requests.get(url = target)
     html = req.text
     bf = BeautifulSoup(html)
     texts = bf.find_all('div', class_ = 'showtxt') 
     print(texts)

在解析 html 之前,我們需要?jiǎng)?chuàng)建一個(gè) Beautiful Soup 對(duì)象。BeautifulSoup 函數(shù)里的參數(shù)就是我們已經(jīng)獲得的 html 信息。然后我們使用 find_all 方法,獲得 html 信息中所有 class 屬性為 showtxt 的 div 標(biāo)簽。find_all 方法的第一個(gè)參數(shù)是獲取的標(biāo)簽名,第二個(gè)參數(shù) class_ 是標(biāo)簽的屬性,為什么不是 class,而帶了一個(gè)下劃線呢?因?yàn)?python 中 class 是關(guān)鍵字,為了防止沖突,這里使用class_表示標(biāo)簽的 class 屬性,class_ 后面跟著的 showtxt 就是屬性值了??聪挛覀円ヅ涞臉?biāo)簽格式:

<div id="content", class="showtxt">

這樣對(duì)應(yīng)的看一下,是不是就懂了?可能有人會(huì)問(wèn)了,為什么不是find_all(‘div’, id = ‘content’, class_ = ‘showtxt’)?這樣其實(shí)也是可以的,屬性是作為查詢時(shí)候的約束條件,添加一個(gè)class_=’showtxt’條件,我們就已經(jīng)能夠準(zhǔn)確匹配到我們想要的標(biāo)簽了,所以我們就不必再添加 id 這個(gè)屬性了。運(yùn)行代碼查看我們匹配的結(jié)果:

4

我們可以看到,我們已經(jīng)順利匹配到我們關(guān)心的正文內(nèi)容,但是還有一些我們不想要的東西。比如 div 標(biāo)簽名,br 標(biāo)簽,以及各種空格。怎么去除這些東西呢?我們繼續(xù)編寫代碼:

# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
     target = 'http://www.biqukan.com/1_1094/5403177.html'
     req = requests.get(url = target) 
     html = req.text
     bf = BeautifulSoup(html)
     texts = bf.find_all('div', class_ = 'showtxt')
     print(texts[0].text.replace('\xa0'*8,'\n\n'))

find_all 匹配的返回的結(jié)果是一個(gè)列表。提取匹配結(jié)果后,使用 text 屬性,提取文本內(nèi)容,濾除 br 標(biāo)簽。隨后使用 replace 方法,剔除空格,替換為回車進(jìn)行分段。 在 html 中是用來(lái)表示空格的。replace(‘\xa0’*8,’\n\n’)就是去掉下圖的八個(gè)空格符號(hào),并用回車代替:

5

程序運(yùn)行結(jié)果如下:

6

可以看到,我們很自然的匹配到了所有正文內(nèi)容,并進(jìn)行了分段。我們已經(jīng)順利獲得了一個(gè)章節(jié)的內(nèi)容,要想下載正本小說(shuō),我們就要獲取每個(gè)章節(jié)的鏈接。我們先分析下小說(shuō)目錄:

7

通過(guò)審查元素,我們發(fā)現(xiàn)可以發(fā)現(xiàn),這些章節(jié)都存放在了 class 屬性為 listmain 的 div 標(biāo)簽下,選取部分 html 代碼如下:

<div class="listmain">
<dl>
<dt>《一念永恒》最新章節(jié)列表</dt>
<dd><a href="/1_1094/15932394.html">第1027章 第十道門</a></dd>
<dd><a href="/1_1094/15923072.html">第1026章 絕倫道法!</a></dd>
<dd><a href="/1_1094/15921862.html">第1025章 長(zhǎng)生燈!</a></dd>
<dd><a href="/1_1094/15918591.html">第1024章 一目晶淵</a></dd>
<dd><a href="/1_1094/15906236.html">第1023章 通天道門</a></dd>
<dd><a href="/1_1094/15903775.html">第1022章 四大兇獸!</a></dd>
<dd><a href="/1_1094/15890427.html">第1021章 鱷首!</a></dd>
<dd><a href="/1_1094/15886627.html">第1020章 一觸即發(fā)!</a></dd>
<dd><a href="/1_1094/15875306.html">第1019章 魁祖的氣息!</a></dd>
<dd><a href="/1_1094/15871572.html">第1018章 絕望的魁皇城</a></dd>
<dd><a href="/1_1094/15859514.html">第1017章 我還是恨你!</a></dd>
<dd><a href="/1_1094/15856137.html">第1016章 從來(lái)沒(méi)有世界之門!</a></dd>
<dt>《一念永恒》正文卷</dt> <dd><a href="/1_1094/5386269.html">外傳1 柯父。</a></dd>
<dd><a href="/1_1094/5386270.html">外傳2 楚玉嫣。</a></dd> <dd><a href="/1_1094/5386271.html">外傳3 鸚鵡與皮凍。</a></dd>
<dd><a href="/1_1094/5403177.html">第一章 他叫白小純</a></dd> <dd><a href="/1_1094/5428081.html">第二章 火灶房</a></dd>
<dd><a href="/1_1094/5433843.html">第三章 六句真言</a></dd> <dd><a href="/1_1094/5447905.html">第四章 煉靈</a></dd>
</dl>
</div>

在分析之前,讓我們先介紹一個(gè)概念:父節(jié)點(diǎn)、子節(jié)點(diǎn)、孫節(jié)點(diǎn)。<div></div>限定了<div>標(biāo)簽的開始和結(jié)束的位置,他們是成對(duì)出現(xiàn)的,有開始位置,就有結(jié)束位置。我們可以看到,在<div>標(biāo)簽包含<dl>標(biāo)簽,那這個(gè)<dl>標(biāo)簽就是<div>標(biāo)簽的子節(jié)點(diǎn),<dl>標(biāo)簽又包含<dt>標(biāo)簽和<dd>標(biāo)簽,那么<dt>標(biāo)簽和<dd>標(biāo)簽就是<div>標(biāo)簽的孫節(jié)點(diǎn)。有點(diǎn)繞?那你記住這句話:誰(shuí)包含誰(shuí),誰(shuí)就是誰(shuí)兒子!

他們之間的關(guān)系都是相對(duì)的。比如對(duì)于<dd>標(biāo)簽,它的子節(jié)點(diǎn)是<a>標(biāo)簽,它的父節(jié)點(diǎn)是<dl>標(biāo)簽。這跟我們?nèi)耸且粯拥模嫌欣舷掠行 ?/p>

看到這里可能有人會(huì)問(wèn),這有好多<dd>標(biāo)簽和<a>標(biāo)簽??!不同的<dd>標(biāo)簽,它們是什么關(guān)系???顯然,兄弟姐妹嘍!我們稱它們?yōu)樾值芙Y(jié)點(diǎn)。 好了,概念明確清楚,接下來(lái),讓我們分析一下問(wèn)題。我們看到每個(gè)章節(jié)的名字存放在了<a>標(biāo)簽里面。<a>標(biāo)簽還有一個(gè) href 屬性。這里就不得不提一下<a>標(biāo)簽的定義了,<a>標(biāo)簽定義了一個(gè)超鏈接,用于從一張頁(yè)面鏈接到另一張頁(yè)面。<a> 標(biāo)簽最重要的屬性是 href 屬性,它指示鏈接的目標(biāo)。

我們將之前獲得的第一章節(jié)的 URL 和<a> 標(biāo)簽對(duì)比看一下:

http://www.biqukan.com/1_1094/5403177.html
<a href="/1_1094/5403177.html">第一章 他叫白小純</a>

不難發(fā)現(xiàn),<a> 標(biāo)簽中 href 屬性存放的屬性值/1_1094/5403177.html 是章節(jié)URLhttp://www.biqukan.com/1_1094/5403177.html的后半部分。其他章節(jié)也是如此!那這樣,我們就可以根據(jù)<a>標(biāo)簽的href屬性值獲得每個(gè)章節(jié)的鏈接和名稱了。

總結(jié)一下:小說(shuō)每章的鏈接放在了 class 屬性為 listmain 的<div>標(biāo)簽下的<a>標(biāo)簽中。鏈接具體位置放在 html->body->div->dl->dd->a 的 href 屬性中。先匹配 class 屬性為 listmain 的<div>標(biāo)簽,再匹配<a>標(biāo)簽。編寫代碼如下:

# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
     target = 'http://www.biqukan.com/1_1094/'
     req = requests.get(url = target)
     html = req.text
     div_bf = BeautifulSoup(html)
     div = div_bf.find_all('div', class_ = 'listmain')
     print(div[0])

還是使用 find_all 方法,運(yùn)行結(jié)果如下:

8

很順利,接下來(lái)再匹配每一個(gè)<a>標(biāo)簽,并提取章節(jié)名和章節(jié)文章。如果我們使用 Beautiful Soup 匹配到了下面這個(gè)<a>標(biāo)簽,如何提取它的 href 屬性和<a>標(biāo)簽里存放的章節(jié)名呢?

<a href="/1_1094/5403177.html">第一章 他叫白小純</a>

方法很簡(jiǎn)單,對(duì) Beautiful Soup 返回的匹配結(jié)果 a,使用 a.get(‘href’)方法就能獲取 href 的屬性值,使用 a.string 就能獲取章節(jié)名,編寫代碼如下:

# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
     server = 'http://www.biqukan.com/'
     target = 'http://www.biqukan.com/1_1094/'
     req = requests.get(url = target) 
     html = req.text
     div_bf = BeautifulSoup(html)
     div = div_bf.find_all('div', class_ = 'listmain')
     a_bf = BeautifulSoup(str(div[0]))
     a = a_bf.find_all('a')
     for each in a:
          print(each.string, server + each.get('href'))

因?yàn)?find_all 返回的是一個(gè)列表,里邊存放了很多的<a>標(biāo)簽,所以使用 for 循環(huán)遍歷每個(gè)<a>標(biāo)簽并打印出來(lái),運(yùn)行結(jié)果如下。

9

最上面匹配的一千多章的內(nèi)容是最新更新的12章節(jié)的鏈接。這12章內(nèi)容會(huì)和下面的重復(fù),所以我們要濾除,除此之外,還有那3個(gè)外傳,我們也不想要。這些都簡(jiǎn)單地剔除就好。

3)整合代碼

每個(gè)章節(jié)的鏈接、章節(jié)名、章節(jié)內(nèi)容都有了。接下來(lái)就是整合代碼,將獲得內(nèi)容寫入文本文件存儲(chǔ)就好了。編寫代碼如下:

# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests, sys

"""
類說(shuō)明:下載《筆趣看》網(wǎng)小說(shuō)《一念永恒》
Parameters:
    無(wú)
Returns:
    無(wú)
Modify:
    2017-09-13
"""
class downloader(object):

    def __init__(self):
        self.server = 'http://www.biqukan.com/'
        self.target = 'http://www.biqukan.com/1_1094/'
        self.names = []            #存放章節(jié)名
        self.urls = []            #存放章節(jié)鏈接
        self.nums = 0            #章節(jié)數(shù)

    """
    函數(shù)說(shuō)明:獲取下載鏈接
    Parameters:
        無(wú)
    Returns:
        無(wú)
    Modify:
        2017-09-13
    """
    def get_download_url(self):
        req = requests.get(url = self.target)
        html = req.text
        div_bf = BeautifulSoup(html)
        div = div_bf.find_all('div', class_ = 'listmain')
        a_bf = BeautifulSoup(str(div[0]))
        a = a_bf.find_all('a')
        self.nums = len(a[13:])                                #剔除不必要的章節(jié),并統(tǒng)計(jì)章節(jié)數(shù)
        for each in a[13:]:
            self.names.append(each.string)
            self.urls.append(self.server + each.get('href'))

    """
    函數(shù)說(shuō)明:獲取章節(jié)內(nèi)容
    Parameters:
        target - 下載連接(string)
    Returns:
        texts - 章節(jié)內(nèi)容(string)
    Modify:
        2017-09-13
    """
    def get_contents(self, target):
        req = requests.get(url = target)
        html = req.text
        bf = BeautifulSoup(html)
        texts = bf.find_all('div', class_ = 'showtxt')
        texts = texts[0].text.replace('\xa0'*8,'\n\n')
        return texts

    """
    函數(shù)說(shuō)明:將爬取的文章內(nèi)容寫入文件
    Parameters:
        name - 章節(jié)名稱(string)
        path - 當(dāng)前路徑下,小說(shuō)保存名稱(string)
        text - 章節(jié)內(nèi)容(string)
    Returns:
        無(wú)
    Modify:
        2017-09-13
    """
    def writer(self, name, path, text):
        write_flag = True
        with open(path, 'a', encoding='utf-8') as f:
            f.write(name + '\n')
            f.writelines(text)
            f.write('\n\n')

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.flush()
    print('《一年永恒》下載完成')

很簡(jiǎn)單的程序,單進(jìn)程跑,沒(méi)有開進(jìn)程池。下載速度略慢,喝杯茶休息休息吧。代碼運(yùn)行效果如下圖所示:

10


原文作者:Jack-Cui

原文地址:https://blog.csdn.net/c406495762/article/details/78123502


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)