點擊上方「Python高校」,關注
文末乾貨立馬到手
http://cuijiahua.com/blog/2017/10/spider_tutorial_1.html
建議:請在電腦的陪同下,閱讀本文。本文以實戰為主,閱讀過程如稍有不適,還望多加練習。網絡爬蟲簡介
網絡爬蟲,也叫網絡蜘蛛(Web Spider)。它根據網頁地址(URL)爬取網頁內容,而網頁地址(URL)就是我們在瀏覽器中輸入的網站連結。比如:https://www.baidu.com/,它就是一個 URL。
在講解爬蟲內容之前,我們需要先學習一項寫爬蟲的必備技能:審查元素(如果已掌握,可跳過此部分內容)。
1、審查元素在瀏覽器的地址欄輸入 URL 地址,在網頁處右鍵單擊,找到檢查。(不同瀏覽器的叫法不同,Chrome 瀏覽器叫做檢查,Firefox 瀏覽器叫做查看元素,但是功能都是相同的)
Python3 網絡爬蟲快速入門實戰解析我們可以看到,右側出現了一大推代碼,這些代碼就叫做 HTML。什麼是 HTML?舉個容易理解的例子:我們的基因決定了我們的原始容貌,伺服器返回的 HTML 決定了網站的原始容貌。
為啥說是原始容貌呢?因為人可以整容啊!扎心了,有木有?那網站也可以"整容"嗎?可以!請看下圖:
Python3 網絡爬蟲快速入門實戰解析我能有這麼多錢嗎?顯然不可能。我是怎麼給網站"整容"的呢?就是通過修改伺服器返回的 HTML 信息。我們每個人都是"整容大師",可以修改頁面信息。我們在頁面的哪個位置點擊審查元素,瀏覽器就會為我們定位到相應的 HTML 位置,進而就可以在本地更改 HTML 信息。
再舉個小例子:我們都知道,使用瀏覽器"記住密碼"的功能,密碼會變成一堆小黑點,是不可見的。可以讓密碼顯示出來嗎?可以,只需給頁面"動個小手術"!以淘寶為例,在輸入密碼框處右鍵,點擊檢查。
Python3 網絡爬蟲快速入門實戰解析可以看到,瀏覽器為我們自動定位到了相應的 HTML 位置。將下圖中的 password 屬性值改為 text 屬性值(直接在右側代碼處修改):
Python3 網絡爬蟲快速入門實戰解析我們讓瀏覽器記住的密碼就這樣顯現出來了:
說這麼多,什麼意思呢?瀏覽器就是作為客戶端從伺服器端獲取信息,然後將信息解析,並展示給我們的。我們可以在本地修改 HTML 信息,為網頁"整容",但是我們修改的信息不會回傳到伺服器,伺服器存儲的 HTML 信息不會改變。刷新一下界面,頁面還會回到原本的樣子。這就跟人整容一樣,我們能改變一些表面的東西,但是不能改變我們的基因。
網絡爬蟲的第一步就是根據 URL,獲取網頁的 HTML 信息。在 Python3 中,可以使用urllib.request和requests進行網頁爬取。
urllib 庫是 python 內置的,無需我們額外安裝,只要安裝了 Python 就可以使用這個庫。
requests 庫是第三方庫,需要我們自己安裝。這個庫強大好用,所以本文使用 requests 庫獲取網頁的 HTML 信息。requests 庫的 github 地址:https://github.com/requests/requests
(1)requests 安裝
在 cmd 中,使用如下指令安裝 requests:
1pip install requests
requests 庫的基礎方法如下:
Python3 網絡爬蟲快速入門實戰解析官方中文教程地址:http://docs.python-requests.org/zh_CN/latest/user/quickstart.html
requests 庫的開發者為我們提供了詳細的中文教程,查詢起來很方便。本文不會對其所有內容進行講解,摘取其部分使用到的內容,進行實戰說明。
首先,讓我們看下 requests.get()方法,它用於向伺服器發起 GET 請求,不了解 GET 請求沒有關係。我們可以這樣理解:get 的中文意思是得到、抓住,那這個 requests.get()方法就是從伺服器得到、抓住數據,也就是獲取數據。讓我們看一個例子(以 www.gitbook.cn 為例)來加深理解:
1# -*- coding:UTF-8 -*-
2import requests
3
4if __name__ == '__main__':
5 target = 'http://gitbook.cn/'
6 req = requests.get(url=target)
7 print(req.text)
requests.get()方法必須設置的一個參數就是 url,因為我們得告訴 GET 請求,我們的目標是誰,我們要獲取誰的信息。運行程序看下結果:
左側是我們程序獲得的結果,右側是我們在 www.gitbook.cn 網站審查元素獲得的信息。我們可以看到,我們已經順利獲得了該網頁的 HTML 信息。這就是一個最簡單的爬蟲實例,可能你會問,我只是爬取了這個網頁的 HTML 信息,有什麼用呢?客官稍安勿躁,接下來進入我們的實戰正文。
接下來我們來一次爬蟲實戰,爬取中網小說網站「筆趣看」上的文字。
(1)實戰背景
小說網站-筆趣看:
URL:http://www.biqukan.com/
筆趣看是一個盜版小說網站,這裡有很多起點中文網的小說,該網站小說的更新速度稍滯後於起點中文網正版小說的更新速度。並且該網站只支持在線瀏覽,不支持小說打包下載。因此,本次實戰就是從該網站爬取並保存一本名為《一念永恆》的小說,該小說是耳根正在連載中的一部玄幻小說。PS:本實例僅為交流學習,支持耳根大大,請上起點中文網訂閱。
(2)小試牛刀
我們先看下《一念永恆》小說的第一章內容,URL:http://www.biqukan.com/1_1094/5403177.html
Python3 網絡爬蟲快速入門實戰解析我們先用已經學到的知識獲取 HTML 信息試一試,編寫代碼如下:
1# -*- coding:UTF-8 -*-
2import requests
3
4if __name__ == '__main__':
5 target = 'http://www.biqukan.com/1_1094/5403177.html'
6 req = requests.get(url=target)
7 print(req.text)
運行代碼,可以看到如下結果:
Python3 網絡爬蟲快速入門實戰解析可以看到,我們很輕鬆地獲取了 HTML 信息。但是,很顯然,很多信息是我們不想看到的,我們只想獲得如右側所示的正文內容,我們不關心 div、br 這些 html 標籤。如何把正文內容從這些眾多的 html 標籤中提取出來呢?這就是本次實戰的主要內容。
(3)Beautiful Soup
爬蟲的第一步,獲取整個網頁的 HTML 信息,我們已經完成。接下來就是爬蟲的第二步,解析 HTML 信息,提取我們感興趣的內容。對於本小節的實戰,我們感興趣的內容就是文章的正文。提取的方法有很多,例如使用正則表達式、Xpath、Beautiful Soup 等。對於初學者而言,最容易理解,並且使用簡單的方法就是使用 Beautiful Soup 提取感興趣內容。
Beautiful Soup 的安裝方法和 requests 一樣,使用如下指令安裝(也是二選一):
pip install beautifulsoup4
easy_install beautifulsoup4
一個強大的第三方庫,都會有一個詳細的官方文檔。我們很幸運,Beautiful Soup 也是有中文的官方文檔。URL:
http://beautifulsoup.readthedocs.io/zh_CN/latest/
同理,我會根據實戰需求,講解 Beautiful Soup 庫的部分使用方法,更詳細的內容,請查看官方文檔。
現在,我們使用已經掌握的審查元素方法,查看一下我們的目標頁面,你會看到如下內容:
Python3 網絡爬蟲快速入門實戰解析不難發現,文章的所有內容都放在了一個名為 div 的「東西下面」,這個"東西"就是 html 標籤。HTML 標籤是 HTML 語言中最基本的單位,HTML 標籤是 HTML 最重要的組成部分。不理解,沒關係,我們再舉個簡單的例子:
一個女人的包包裡,會有很多東西,她們會根據自己的習慣將自己的東西進行分類放好。鏡子和口紅這些會經常用到的東西,會歸放到容易拿到的外側口袋裡。那些不經常用到,需要注意安全存放的證件會放到不容易拿到的裡側口袋裡。
html 標籤就像一個個「口袋」,每個「口袋」都有自己的特定功能,負責存放不同的內容。顯然,上述例子中的 div 標籤下存放了我們關心的正文內容。這個 div 標籤是這樣的:
1<div id="content", class="showtxt">
細心的朋友可能已經發現,除了 div 字樣外,還有 id 和 class。id 和 class 就是 div 標籤的屬性,content 和 showtxt 是屬性值,一個屬性對應一個屬性值。這東西有什麼用?它是用來區分不同的 div 標籤的,因為 div 標籤可以有很多,我們怎麼加以區分不同的 div 標籤呢?就是通過不同的屬性值。
仔細觀察目標網站一番,我們會發現這樣一個事實:class 屬性為 showtxt 的 div 標籤,獨一份!這個標籤裡面存放的內容,是我們關心的正文部分。
知道這個信息,我們就可以使用 Beautiful Soup 提取我們想要的內容了,編寫代碼如下:
1# -*- coding:UTF-8 -*-
2from bs4 import BeautifulSoup
3import requests
4if __name__ == "__main__":
5 target = 'http://www.biqukan.com/1_1094/5403177.html'
6 req = requests.get(url = target)
7 html = req.text
8 bf = BeautifulSoup(html)
9 texts = bf.find_all('div', class_ = 'showtxt')
10 print(texts)
在解析 html 之前,我們需要創建一個 Beautiful Soup 對象。BeautifulSoup 函數裡的參數就是我們已經獲得的 html 信息。然後我們使用find_all方法,獲得 html 信息中所有 class 屬性為 showtxt 的 div 標籤。find_all方法的第一個參數是獲取的標籤名,第二個參數class_是標籤的屬性,為什麼不是 class,而帶了一個下劃線呢?因為 python 中 class 是關鍵字,為了防止衝突,這裡使用class_表示標籤的 class 屬性,class_後面跟著的 showtxt 就是屬性值了。看下我們要匹配的標籤格式:
1<div id="content", class="showtxt">
這樣對應的看一下,是不是就懂了?可能有人會問了,為什麼不是find_all('div', id = 'content', class_ = 'showtxt')?這樣其實也是可以的,屬性是作為查詢時候的約束條件,添加一個class_='showtxt'條件,我們就已經能夠準確匹配到我們想要的標籤了,所以我們就不必再添加 id 這個屬性了。運行代碼查看我們匹配的結果:
我們可以看到,我們已經順利匹配到我們關心的正文內容,但是還有一些我們不想要的東西。比如 div 標籤名,br 標籤,以及各種空格。怎麼去除這些東西呢?我們繼續編寫代碼:
1# -*- coding:UTF-8 -*-
2from bs4 import BeautifulSoup
3import requests
4if __name__ == "__main__":
5 target = 'http://www.biqukan.com/1_1094/5403177.html'
6 req = requests.get(url = target)
7 html = req.text
8 bf = BeautifulSoup(html)
9 texts = bf.find_all('div', class_ = 'showtxt')
10 print(texts[0].text.replace('\xa0'*8,'\n\n'))
find_all 匹配的返回的結果是一個列表。提取匹配結果後,使用 text 屬性,提取文本內容,濾除 br 標籤。隨後使用 replace 方法,剔除空格,替換為回車進行分段。 在 html 中是用來表示空格的。replace('\xa0'*8,'\n\n')就是去掉下圖的八個空格符號,並用回車代替:
Python3 網絡爬蟲快速入門實戰解析程序運行結果如下:
Python3 網絡爬蟲快速入門實戰解析可以看到,我們很自然的匹配到了所有正文內容,並進行了分段。我們已經順利獲得了一個章節的內容,要想下載正本小說,我們就要獲取每個章節的連結。我們先分析下小說目錄:URL:http://www.biqukan.com/1_1094/
通過審查元素,我們發現可以發現,這些章節都存放在了 class 屬性為 listmain 的 div 標籤下,選取部分 html 代碼如下:
1<div class="listmain">
2<dl>
3<dt>《一念永恆》最新章節列表</dt>
4<dd><a href="/1_1094/15932394.html">第 1027 章 第十道門</a></dd>
5<dd><a href="/1_1094/15923072.html">第 1026 章 絕倫道法!</a></dd>
6<dd><a href="/1_1094/15921862.html">第 1025 章 長生燈!</a></dd>
7<dd><a href="/1_1094/15918591.html">第 1024 章 一目晶淵</a></dd>
8<dd><a href="/1_1094/15906236.html">第 1023 章 通天道門</a></dd>
9<dd><a href="/1_1094/15903775.html">第 1022 章 四大兇獸!</a></dd>
10<dd><a href="/1_1094/15890427.html">第 1021 章 鱷首!</a></dd>
11<dd><a href="/1_1094/15886627.html">第 1020 章 一觸即發!</a></dd>
12<dd><a href="/1_1094/15875306.html">第 1019 章 魁祖的氣息!</a></dd>
13<dd><a href="/1_1094/15871572.html">第 1018 章 絕望的魁皇城</a></dd>
14<dd><a href="/1_1094/15859514.html">第 1017 章 我還是恨你!</a></dd>
15<dd><a href="/1_1094/15856137.html">第 1016 章 從來沒有世界之門!</a></dd>
16<dt>《一念永恆》正文卷</dt> <dd><a href="/1_1094/5386269.html">外傳 1 柯父。</a></dd>
17<dd><a href="/1_1094/5386270.html">外傳 2 楚玉嫣。</a></dd> <dd><a href="/1_1094/5386271.html">外傳 3 鸚鵡與皮凍。</a></dd>
18<dd><a href="/1_1094/5403177.html">第一章 他叫白小純</a></dd> <dd><a href="/1_1094/5428081.html">第二章 火灶房</a></dd>
19<dd><a href="/1_1094/5433843.html">第三章 六句真言</a></dd> <dd><a href="/1_1094/5447905.html">第四章 煉靈</a></dd>
20</dl>
21</div>
在分析之前,讓我們先介紹一個概念:父節點、子節點、孫節點。<div>和</div>限定了<div>標籤的開始和結束的位置,他們是成對出現的,有開始位置,就有結束位置。我們可以看到,在<div>標籤包含<dl>標籤,那這個<dl>標籤就是<div>標籤的子節點,<dl>標籤又包含<dt>標籤和<dd>標籤,那麼<dt>標籤和<dd>標籤就是<div>標籤的孫節點。有點繞?那你記住這句話:誰包含誰,誰就是誰兒子!
他們之間的關係都是相對的。比如對於<dd>標籤,它的子節點是<a>標籤,它的父節點是<dl>標籤。這跟我們人是一樣的,上有老下有小。
看到這裡可能有人會問,這有好多<dd>標籤和<a>標籤啊!不同的<dd>標籤,它們是什麼關係啊?顯然,兄弟姐妹嘍!我們稱它們為兄弟結點。
好了,概念明確清楚,接下來,讓我們分析一下問題。我們看到每個章節的名字存放在了<a>標籤裡面。<a>標籤還有一個 href 屬性。這裡就不得不提一下<a> 標籤的定義了,<a> 標籤定義了一個超連結,用於從一張頁面連結到另一張頁面。<a> 標籤最重要的屬性是 href 屬性,它指示連結的目標。
我們將之前獲得的第一章節的 URL 和<a> 標籤對比看一下:
1http://www.biqukan.com/1_1094/5403177.html
不難發現,<a> 標籤中 href 屬性存放的屬性值/1_1094/5403177.html是章節 URLhttp://www.biqukan.com/1_1094/5403177.html的後半部分。其他章節也是如此!那這樣,我們就可以根據<a> 標籤的 href 屬性值獲得每個章節的連結和名稱了。
總結一下:小說每章的連結放在了 class 屬性為 listmain 的<div>標籤下的<a>標籤中。連結具體位置放在 html->body->div->dl->dd->a 的 href 屬性中。先匹配 class 屬性為 listmain 的<div>標籤,再匹配<a>標籤。編寫代碼如下:
1# -*- coding:UTF-8 -*-
2from bs4 import BeautifulSoup
3import requests
4if __name__ == "__main__":
5 target = 'http://www.biqukan.com/1_1094/'
6 req = requests.get(url = target)
7 html = req.text
8 div_bf = BeautifulSoup(html)
9 div = div_bf.find_all('div', class_ = 'listmain')
10 print(div[0])
還是使用 find_all 方法,運行結果如下:
Python3 網絡爬蟲快速入門實戰解析很順利,接下來再匹配每一個<a>標籤,並提取章節名和章節文章。如果我們使用 Beautiful Soup 匹配到了下面這個<a>標籤,如何提取它的 href 屬性和<a>標籤裡存放的章節名呢?
1<a href="/1_1094/5403177.html">第一章 他叫白小純</a>
方法很簡單,對 Beautiful Soup 返回的匹配結果 a,使用 a.get('href')方法就能獲取 href 的屬性值,使用 a.string 就能獲取章節名,編寫代碼如下:
1# -*- coding:UTF-8 -*-
2from bs4 import BeautifulSoup
3import requests
4if __name__ == "__main__":
5 server = 'http://www.biqukan.com/'
6 target = 'http://www.biqukan.com/1_1094/'
7 req = requests.get(url = target) html = req.text
8 div_bf = BeautifulSoup(html)
9 div = div_bf.find_all('div', class_ = 'listmain')
10 a_bf = BeautifulSoup(str(div[0]))
11 a = a_bf.find_all('a')
12 for each in a:
13 print(each.string, server + each.get('href'))
因為 find_all 返回的是一個列表,裡邊存放了很多的<a>標籤,所以使用 for 循環遍歷每個<a>標籤並列印出來,運行結果如下:
Python3 網絡爬蟲快速入門實戰解析最上面匹配的一千多章的內容是最新更新的 12 章節的連結。這 12 章內容會和下面的重複,所以我們要濾除,除此之外,還有那 3 個外傳,我們也不想要。這些都簡單地剔除就好。
(3)整合代碼
每個章節的連結、章節名、章節內容都有了。接下來就是整合代碼,將獲得內容寫入文本文件存儲就好了。編寫代碼如下:
1# -*- coding:UTF-8 -*-
2from bs4 import BeautifulSoup
3import requests, sys
4
5class downloader(object):
6 def __init__(self):
7 self.server = 'http://www.biqukan.com/'
8 self.target = 'http://www.biqukan.com/1_1094/'
9 self.names = [] #存放章節名
10 self.urls = [] #存放章節連結
11 self.nums = 0 #章節數
12
13 def get_download_url(self):
14 req = requests.get(url = self.target)
15 html = req.text
16 div_bf = BeautifulSoup(html)
17 div = div_bf.find_all('div', class_ = 'listmain')
18 a_bf = BeautifulSoup(str(div[0]))
19 a = a_bf.find_all('a')
20 self.nums = len(a[15:]) #剔除不必要的章節,並統計章節數
21 for each in a[15:]:
22 self.names.append(each.string)
23 self.urls.append(self.server + each.get('href'))
24
25 """
26 函數說明:獲取章節內容
27 Parameters:
28 target - 下載連接(string)
29 Returns:
30 texts - 章節內容(string)
31 """
32 def get_contents(self, target):
33 req = requests.get(url = target)
34 html = req.text
35 bf = BeautifulSoup(html)
36 texts = bf.find_all('div', class_ = 'showtxt')
37 texts = texts[0].text.replace('\xa0'*8,'\n\n')
38 return texts
39
40 """
41 函數說明:將爬取的文章內容寫入文件
42 Parameters:
43 name - 章節名稱(string)
44 path - 當前路徑下,小說保存名稱(string)
45 text - 章節內容(string)
46 Returns:
47 無
48 """
49 def writer(self, name, path, text):
50 write_flag = True
51 with open(path, 'a', encoding='utf-8') as f:
52 f.write(name + '\n')
53 f.writelines(text)
54 f.write('\n\n')
55
56if __name__ == "__main__":
57 dl = downloader()
58 dl.get_download_url()
59 print('《一年永恆》開始下載:')
60 for i in range(dl.nums):
61 dl.writer(dl.names[i], '一念永恆.txt', dl.get_contents(dl.urls[i]))
62 sys.stdout.write(" 已下載:%.3f%%" % float(i/dl.nums*100) + '\r')
63 sys.stdout.flush()
64 print('《一年永恆》下載完成')
很簡單的程序,單進程跑,沒有開進程池。下載速度略慢,喝杯茶休息休息吧。代碼運行效果如下圖所示:
Python3 網絡爬蟲快速入門實戰解析以上就是一次爬蟲實戰。
掃描下方二維碼,添加Gao微信,可領 Python和AI 學習大禮包,想發推文、廣告、砍價小程序的敬請繞道!一定記得備註「交流學習」。
人生苦短,我選在看