聲明:本文講解的實戰(zhàn)內(nèi)容谍椅,均僅用于學(xué)習(xí)交流误堡,請勿用于任何商業(yè)用途!
一雏吭、前言
強烈建議:請在電腦的陪同下锁施,閱讀本文。本文以實戰(zhàn)為主杖们,閱讀過程如稍有不適悉抵,還望多加練習(xí)。
本文的實戰(zhàn)內(nèi)容有:
網(wǎng)絡(luò)小說下載(靜態(tài)網(wǎng)站)
優(yōu)美壁紙下載(動態(tài)網(wǎng)站)
愛奇藝VIP視頻下載
二摘完、網(wǎng)絡(luò)爬蟲簡介
網(wǎng)絡(luò)爬蟲姥饰,也叫網(wǎng)絡(luò)蜘蛛(Web Spider)。它根據(jù)網(wǎng)頁地址(URL)爬取網(wǎng)頁內(nèi)容描焰,而網(wǎng)頁地址(URL)就是我們在瀏覽器中輸入的網(wǎng)站鏈接媳否。比如:https://www.baidu.com/,它就是一個URL荆秦。
在講解爬蟲內(nèi)容之前,我們需要先學(xué)習(xí)一項寫爬蟲的必備技能:審查元素(如果已掌握力图,可跳過此部分內(nèi)容)步绸。
1. 審查元素
在瀏覽器的地址欄輸入URL地址,在網(wǎng)頁處右鍵單擊吃媒,找到檢查瓤介,如下圖所示:(不同瀏覽器的叫法不同吕喘,Chrome瀏覽器叫做檢查,F(xiàn)irefox瀏覽器叫做查看元素刑桑,但是功能都是相同的)
我們可以看到氯质,右側(cè)出現(xiàn)了一大推代碼,這些代碼就叫做HTML祠斧。什么是HTML闻察?舉個容易理解的例子:我們的基因決定了我們的原始容貌,服務(wù)器返回的HTML決定了網(wǎng)站的原始容貌琢锋。
為啥說是原始容貌呢辕漂?因為人可以整容啊吴超!扎心了钉嘹,有木有?那網(wǎng)站也可以"整容"嗎鲸阻?可以跋涣!請看下圖:
我能有這么多錢嗎?顯然不可能鸟悴。我是怎么給網(wǎng)站"整容"的呢陈辱?就是通過修改服務(wù)器返回的HTML信息。我們每個人都是"整容大師"遣臼,可以修改頁面信息性置。我們在頁面的哪個位置點擊審查元素,瀏覽器就會為我們定位到相應(yīng)的HTML位置揍堰,進而就可以在本地更改HTML信息鹏浅。
再舉個小例子:我們都知道,使用瀏覽器"記住密碼"的功能屏歹,密碼會變成一堆小黑點隐砸,是不可見的◎簦可以讓密碼顯示出來嗎季希?可以,只需給頁面"動個小手術(shù)"幽纷!以淘寶為例式塌,在輸入密碼框處右鍵,點擊檢查友浸。
可以看到峰尝,瀏覽器為我們自動定位到了相應(yīng)的HTML位置。將下圖中的password屬性值改為text屬性值(直接在右側(cè)代碼處修改):
就這樣收恢,瀏覽器"記住的密碼"顯現(xiàn)出來了:
說這么多武学,什么意思呢祭往?瀏覽器就是作為客戶端從服務(wù)器端獲取信息,然后將信息解析火窒,并展示給我們的硼补。我們可以在本地修改HTML信息,為網(wǎng)頁"整容"熏矿,但是我們修改的信息不會回傳到服務(wù)器已骇,服務(wù)器存儲的HTML信息不會改變。刷新一下界面曲掰,頁面還會回到原本的樣子疾捍。這就跟人整容一樣,我們能改變一些表面的東西栏妖,但是不能改變我們的基因乱豆。
2. 簡單實例
網(wǎng)絡(luò)爬蟲的第一步就是根據(jù)URL,獲取網(wǎng)頁的HTML信息吊趾。在Python3中宛裕,可以使用urllib.request和requests進行網(wǎng)頁爬取。
urllib庫是python內(nèi)置的论泛,無需我們額外安裝揩尸,只要安裝了Python就可以使用這個庫。
requests庫是第三方庫屁奏,需要我們自己安裝岩榆。
requests庫強大好用,所以本文使用requests庫獲取網(wǎng)頁的HTML信息坟瓢。requests庫的github地址:https://github.com/requests/requests
(1)requests安裝
在學(xué)習(xí)使用requests庫之前勇边,我們需要在電腦中安裝好requests庫。在cmd中折联,使用如下指令安裝requests庫:
pip install requests
easy_install requests
使用pip和easy_install都可以安裝粒褒,二選一即可。
(2)簡單實例
安裝好requests庫之后诚镰,我們先來大體瀏覽一下requests庫的基礎(chǔ)方法:
官方中文教程地址:http://docs.python-requests.org/zh_CN/latest/user/quickstart.html
requests庫的開發(fā)者為我們提供了詳細的中文教程奕坟,查詢起來很方便。本文不會對其所有內(nèi)容進行講解清笨,摘取其部分使用到的內(nèi)容月杉,進行實戰(zhàn)說明。
首先抠艾,讓我們看下requests.get()方法沙合,它用于向服務(wù)器發(fā)起GET請求,不了解GET請求沒有關(guān)系跌帐。我們可以這樣理解:get的中文意思是得到首懈、抓住,那這個requests.get()方法就是從服務(wù)器得到谨敛、抓住數(shù)據(jù)究履,也就是獲取數(shù)據(jù)。讓我們看一個例子(以 www.gitbook.cn? 為例)來加深理解:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'http://gitbook.cn/'
req = requests.get(url=target)
print(req.text)
requests.get()方法必須設(shè)置的一個參數(shù)就是url脸狸,因為我們得告訴GET請求最仑,我們的目標是誰,我們要獲取誰的信息炊甲。我們將GET請求獲得的響應(yīng)內(nèi)容存放到req變量中泥彤,然后使用req.text就可以獲得HTML信息了。運行結(jié)果如下:
左側(cè)是我們程序獲得的結(jié)果卿啡,右側(cè)是我們在www.gitbook.cn? 網(wǎng)站審查元素獲得的信息吟吝。我們可以看到,我們已經(jīng)順利獲得了該網(wǎng)頁的HTML信息颈娜。這就是一個最簡單的爬蟲實例剑逃,可能你會問,我只是爬取了這個網(wǎng)頁的HTML信息官辽,有什么用呢蛹磺?客官稍安勿躁,接下來進入我們的實戰(zhàn)正文同仆。
三萤捆、爬蟲實戰(zhàn)
實戰(zhàn)內(nèi)容由簡單到復(fù)雜,難度逐漸增加俗批,但均屬于入門級難度俗或。下面開始我們的第一個實戰(zhàn)內(nèi)容:網(wǎng)絡(luò)小說下載。
1. 小說下載
(1)實戰(zhàn)背景
小說網(wǎng)站《筆趣看》URL:http://www.biqukan.com/
《筆趣看》是一個盜版小說網(wǎng)站扶镀,這里有很多起點中文網(wǎng)的小說蕴侣,該網(wǎng)站小說的更新速度稍滯后于起點中文網(wǎng)正版小說的更新速度。并且該網(wǎng)站只支持在線瀏覽臭觉,不支持小說打包下載昆雀。因此,本次實戰(zhàn)就是從該網(wǎng)站爬取并保存一本名為《一念永恒》的小說蝠筑,該小說是耳根正在連載中的一部玄幻小說狞膘。PS:本實例僅為交流學(xué)習(xí),支持耳根大大什乙,請上起點中文網(wǎng)訂閱挽封。
(2)小試牛刀
我們先看下《一念永恒》小說的第一章內(nèi)容,URL:http://www.biqukan.com/1_1094/5403177.html
用已經(jīng)學(xué)到的知識獲取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)
運行代碼辅愿,可以看到如下結(jié)果:
可以看到智亮,我們很輕松地獲取了HTML信息。但是点待,很顯然阔蛉,很多信息是我們不想看到的,我們只想獲得如右側(cè)所示的正文內(nèi)容癞埠,我們不關(guān)心那些看著眼暈的英文字母状原。如何把正文內(nèi)容從這些眾多的HTML信息中提取出來呢?這就是本小節(jié)實戰(zhàn)的主要內(nèi)容苗踪。
(3)Beautiful Soup
爬蟲的第一步颠区,獲取整個網(wǎng)頁的HTML信息,我們已經(jīng)完成通铲。接下來就是爬蟲的第二步毕莱,解析HTML信息,提取我們感興趣的內(nèi)容测暗。對于本小節(jié)的實戰(zhàn)央串,我們感興趣的內(nèi)容就是文章的正文。提取的方法有很多碗啄,例如使用正則表達式质和、Xpath、Beautiful Soup等稚字。對于初學(xué)者而言饲宿,最容易理解,并且使用簡單的方法就是使用Beautiful Soup提取感興趣內(nèi)容瘫想。
Beautiful Soup的安裝方法和requests一樣国夜,使用如下指令安裝(也是二選一):
pip install beautifulsoup4
easy_install beautifulsoup4
一個強大的第三方庫,都會有一個詳細的官方文檔车吹。我們很幸運醋闭,Beautiful Soup也是有中文的官方文檔窄驹。URL:http://beautifulsoup.readthedocs.io/zh_CN/latest/
同理乐埠,我會根據(jù)實戰(zhàn)需求瑞眼,講解Beautiful Soup庫的部分使用方法负拟,更詳細的內(nèi)容,請查看官方文檔秸歧。
現(xiàn)在,我們使用已經(jīng)掌握的審查元素方法经备,查看一下我們的目標頁面,你會看到如下內(nèi)容:
不難發(fā)現(xiàn)纷闺,文章的所有內(nèi)容都放在了一個名為div的“東西下面”,這個"東西"就是html標簽浸卦。HTML標簽是HTML語言中最基本的單位限嫌,HTML標簽是HTML最重要的組成部分。不理解裆熙,沒關(guān)系蛤奥,我們再舉個簡單的例子:一個女人的包包里凡桥,會有很多東西缅刽,她們會根據(jù)自己的習(xí)慣將自己的東西進行分類。鏡子和口紅這些會經(jīng)常用到的東西啡省,回歸放到容易拿到的外側(cè)口袋里。那些不經(jīng)常用到结序,需要注意安全存放的證件會被放到不容易拿到的里側(cè)口袋里。
html標簽就像一個個“口袋”嗜浮,每個“口袋”都有自己的特定功能,負責(zé)存放不同的內(nèi)容庆杜。顯然钮追,上述例子中的div標簽下存放了我們關(guān)心的正文內(nèi)容轧叽。這個div標簽是這樣的:
細心的朋友可能已經(jīng)發(fā)現(xiàn)待逞,除了div字樣外识樱,還有id和class。id和class就是div標簽的屬性休雌,content和showtxt是屬性值驰凛,一個屬性對應(yīng)一個屬性值趣钱。這東西有什么用首有?它是用來區(qū)分不同的div標簽的,因為div標簽可以有很多烙常,我們怎么加以區(qū)分不同的div標簽?zāi)兀烤褪峭ㄟ^不同的屬性值驼鞭。
仔細觀察目標網(wǎng)站一番译隘,我們會發(fā)現(xiàn)這樣一個事實:class屬性為showtxt的div標簽,獨一份玻驻!這個標簽里面存放的內(nèi)容,是我們關(guān)心的正文部分嗤锉。
知道這個信息,我們就可以使用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之前,我們需要創(chuàng)建一個Beautiful Soup對象涡相。BeautifulSoup函數(shù)里的參數(shù)就是我們已經(jīng)獲得的html信息。然后我們使用find_all方法生逸,獲得html信息中所有class屬性為showtxt的div標簽。find_all方法的第一個參數(shù)是獲取的標簽名遍尺,第二個參數(shù)class_是標簽的屬性迂苛,為什么不是class,而帶了一個下劃線呢念搬?因為python中class是關(guān)鍵字,為了防止沖突爷恳,這里使用class_表示標簽的class屬性,class_后面跟著的showtxt就是屬性值了。看下我們要匹配的標簽格式:
這樣對應(yīng)的看一下,是不是就懂了魄幕?可能有人會問了,為什么不是find_all('div', id = 'content', class_ = 'showtxt')?這樣其實也是可以的,屬性是作為查詢時候的約束條件阴颖,添加一個class_='showtxt'條件钾菊,我們就已經(jīng)能夠準確匹配到我們想要的標簽了,所以我們就不必再添加id這個屬性了滞详。運行代碼查看我們匹配的結(jié)果:
我們可以看到瘦棋,我們已經(jīng)順利匹配到我們關(guān)心的正文內(nèi)容凰狞,但是還有一些我們不想要的東西。比如div標簽名逾冬,br標簽,以及各種空格。怎么去除這些東西呢她按?我們繼續(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é)果是一個列表。提取匹配結(jié)果后,使用text屬性授霸,提取文本內(nèi)容显设,濾除br標簽。隨后使用replace方法指攒,剔除空格,替換為回車進行分段隙弛。 在html中是用來表示空格的萍启。replace('\xa0'*8,'\n\n')就是去掉下圖的八個空格符號姚淆,并用回車代替:
程序運行結(jié)果如下:
可以看到超埋,我們很自然的匹配到了所有正文內(nèi)容,并進行了分段来庭。我們已經(jīng)順利獲得了一個章節(jié)的內(nèi)容肴盏,要想下載正本小說厉萝,我們就要獲取每個章節(jié)的鏈接章母。我們先分析下小說目錄乳怎,URL:http://www.biqukan.com/1_1094/
通過審查元素博杖,我們發(fā)現(xiàn)可以發(fā)現(xiàn)哩盲,這些章節(jié)都存放在了class屬性為listmain的div標簽下苗傅,選取部分html代碼如下:
《一念永恒》最新章節(jié)列表
《一念永恒》正文卷
在分析之前,讓我們先介紹一個概念:父節(jié)點、子節(jié)點茄袖、孫節(jié)點聂薪。
和
限定了
標簽的開始和結(jié)束的位置仁锯,他們是成對出現(xiàn)的,有開始位置翔悠,就有結(jié)束位置业崖。我們可以看到,在
標簽包含
標簽蓄愁,那這個
標簽就是
標簽的子節(jié)點,
標簽又包含標簽和
標簽撮抓,那么
標簽和
標簽就是
標簽的孫節(jié)點妇斤。有點繞?那你記住這句話:誰包含誰丹拯,誰就是誰兒子站超!
他們之間的關(guān)系都是相對的。比如對于
標簽乖酬,它的子節(jié)點是標簽死相,它的父節(jié)點是
標簽。這跟我們?nèi)耸且粯拥慕P蹋嫌欣舷掠行 ?/p>
看到這里可能有人會問媳纬,這有好多
標簽和標簽啊施掏!不同的
標簽钮惠,它們是什么關(guān)系啊七芭?顯然素挽,兄弟姐妹嘍!我們稱它們?yōu)樾值芙Y(jié)點狸驳。
好了预明,概念明確清楚,接下來耙箍,讓我們分析一下問題撰糠。我們看到每個章節(jié)的名字存放在了標簽里面。標簽還有一個href屬性辩昆。這里就不得不提一下標簽的定義了阅酪,標簽定義了一個超鏈接,用于從一張頁面鏈接到另一張頁面。標簽最重要的屬性是 href 屬性术辐,它指示鏈接的目標砚尽。
我們將之前獲得的第一章節(jié)的URL和標簽對比看一下:
http://www.biqukan.com/1_1094/5403177.html
不難發(fā)現(xiàn),標簽中href屬性存放的屬性值/1_1094/5403177.html是章節(jié)URLhttp://www.biqukan.com/1_1094/5403177.html的后半部分辉词。其他章節(jié)也是如此必孤!那這樣,我們就可以根據(jù)標簽的href屬性值獲得每個章節(jié)的鏈接和名稱了瑞躺。
總結(jié)一下:小說每章的鏈接放在了class屬性為listmain的
標簽下的標簽中敷搪。鏈接具體位置放在html->body->div->dl->dd->a的href屬性中。先匹配class屬性為listmain的
# -*- 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方法,運行結(jié)果如下:
很順利嘱么,接下來再匹配每一個標簽狮含,并提取章節(jié)名和章節(jié)文章。如果我們使用Beautiful Soup匹配到了下面這個標簽曼振,如何提取它的href屬性和標簽里存放的章節(jié)名呢几迄?
方法很簡單,對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'))
因為find_all返回的是一個列表甲雅,里邊存放了很多的標簽解孙,所以使用for循環(huán)遍歷每個標簽并打印出來,運行結(jié)果如下抛人。
最上面匹配的一千多章的內(nèi)容是最新更新的12章節(jié)的鏈接弛姜。這12章內(nèi)容會和下面的重復(fù),所以我們要濾除妖枚,除此之外廷臼,還有那3個外傳,我們也不想要绝页。這些都簡單地剔除就好荠商。
(3)整合代碼
每個章節(jié)的鏈接、章節(jié)名续誉、章節(jié)內(nèi)容都有了莱没。接下來就是整合代碼,將獲得內(nèi)容寫入文本文件存儲就好了酷鸦。編寫代碼如下:
# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests, sys
"""
類說明:下載《筆趣看》網(wǎng)小說《一念永恒》
Parameters:
無
Returns:
無
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ù)說明:獲取下載鏈接
Parameters:
無
Returns:
無
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[15:]) #剔除不必要的章節(jié)饰躲,并統(tǒng)計章節(jié)數(shù)
for each in a[15:]:
self.names.append(each.string)
self.urls.append(self.server + each.get('href'))
"""
函數(shù)說明:獲取章節(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ù)說明:將爬取的文章內(nèi)容寫入文件
Parameters:
name - 章節(jié)名稱(string)
path - 當前路徑下,小說保存名稱(string)
text - 章節(jié)內(nèi)容(string)
Returns:
無
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('《一年永恒》下載完成')
很簡單的程序朴译,單進程跑,沒有開進程池属铁。下載速度略慢,喝杯茶休息休息吧躬翁。代碼運行效果如下圖所示:
2. 優(yōu)美壁紙下載
(1)實戰(zhàn)背景
已經(jīng)會爬取文字了焦蘑,是不是感覺爬蟲還是蠻好玩的呢?接下來盒发,讓我們進行一個進階實戰(zhàn)例嘱,了解一下反爬蟲。URL:https://unsplash.com/
看一看這些優(yōu)美的壁紙宁舰,這個網(wǎng)站的名字叫做Unsplash拼卵,免費高清壁紙分享網(wǎng)是一個堅持每天分享高清的攝影圖片的站點,每天更新一張高質(zhì)量的圖片素材蛮艰,全是生活中的景象作品腋腮,清新的生活氣息圖片可以作為桌面壁紙也可以應(yīng)用于各種需要的環(huán)境。
看到這么優(yōu)美的圖片壤蚜,我的第一反應(yīng)就是想收藏一些即寡,作為知乎文章的題圖再好不過了。每張圖片我都很喜歡袜刷,批量下載吧聪富,不多爬,就下載50張好了著蟹。
(2)實戰(zhàn)進階
我們已經(jīng)知道了每個html標簽都有各自的功能墩蔓。標簽存放一下超鏈接,圖片存放在哪個標簽里呢萧豆?html規(guī)定奸披,圖片統(tǒng)統(tǒng)給我放到
標簽中!既然這樣炕横,我們截取就Unsplash網(wǎng)站中的一個
need-to-insert-img
標簽源内,分析一下:
need-to-insert-img
可以看到,
標簽有很多屬性份殿,有alt膜钓、src、class卿嘲、style屬性颂斜,其中src屬性存放的就是我們需要的圖片保存地址,我們根據(jù)這個地址就可以進行圖片的下載拾枣。
need-to-insert-img
那么沃疮,讓我們先捋一捋這個過程:
使用requeusts獲取整個網(wǎng)頁的HTML信息盒让;
使用Beautiful Soup解析HTML信息,找到所有
標簽司蔬,提取src屬性邑茄,獲取圖片存放地址;
need-to-insert-img
根據(jù)圖片存放地址俊啼,下載圖片肺缕。
我們信心滿滿地按照這個思路爬取Unsplash試一試,編寫代碼如下:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'https://unsplash.com/'
req = requests.get(url=target)
print(req.text)
按照我們的設(shè)想授帕,我們應(yīng)該能找到很多
標簽同木。但是我們發(fā)現(xiàn),除了一些
need-to-insert-img
答案就是跛十,這個網(wǎng)站的所有圖片都是動態(tài)加載的彤路!網(wǎng)站有靜態(tài)網(wǎng)站和動態(tài)網(wǎng)站之分,上一個實戰(zhàn)爬取的網(wǎng)站是靜態(tài)網(wǎng)站芥映,而這個網(wǎng)站是動態(tài)網(wǎng)站洲尊,動態(tài)加載有一部分的目的就是為了反爬蟲。
對于什么是動態(tài)加載屏轰,你可以這樣理解:
我們知道化妝術(shù)學(xué)的好颊郎,賊厲害,可以改變一個人的容貌霎苗。相應(yīng)的姆吭,動態(tài)加載用的好,也賊厲害唁盏,可以改變一個網(wǎng)站的容貌内狸。
動態(tài)網(wǎng)站使用動態(tài)加載常用的手段就是通過調(diào)用JavaScript來實現(xiàn)的。怎么實現(xiàn)JavaScript動態(tài)加載厘擂,我們不必深究昆淡,我們只要知道,動態(tài)加載的JavaScript腳本刽严,就像化妝術(shù)需要用的化妝品昂灵,五花八門。有粉底舞萄、口紅眨补、睫毛膏等等,它們都有各自的用途倒脓。動態(tài)加載的JavaScript腳本也一樣撑螺,一個動態(tài)加載的網(wǎng)站可能使用很多JavaScript腳本,我們只要找到負責(zé)動態(tài)加載圖片的JavaScript腳本崎弃,不就找到我們需要的鏈接了嗎甘晤?
對于初學(xué)者含潘,我們不必看懂JavaScript執(zhí)行的內(nèi)容是什么,做了哪些事情线婚,因為我們有強大的抓包工具遏弱,它自然會幫我們分析。這個強大的抓包工具就是Fiddler塞弊。URL:http://www.telerik.com/fiddler
PS:也可以使用瀏覽器自帶的Networks腾窝,但是我更推薦這個軟件,因為它操作起來更高效居砖。
安裝方法很簡單,傻瓜式安裝驴娃,一直下一步即可奏候,對于經(jīng)常使用電腦的人來說,應(yīng)該沒有任何難度唇敞。
這個軟件的使用方法也很簡單蔗草,打開軟件,然后用瀏覽器打開我們的目標網(wǎng)站疆柔,以Unsplash為例咒精,抓包結(jié)果如下:
我們可以看到,上圖左側(cè)紅框處是我們的GET請求的地址旷档,就是網(wǎng)站的URL模叙,右下角是服務(wù)器返回的信息,我們可以看到鞋屈,這些信息也是我們上一個程序獲得的信息范咨。這個不是我們需要的鏈接,我們繼續(xù)往下看厂庇。
我們發(fā)現(xiàn)上圖所示的就是一個JavaScript請求渠啊,看右下側(cè)服務(wù)器返回的信息是一個json格式的數(shù)據(jù)。這里面权旷,就有我們需要的內(nèi)容替蛉。我們局部放大看一下:
這是Fiddler右側(cè)的信息,上面是請求的Headers信息拄氯,包括這個Javascript的請求地 址:http://unsplash.com/napi/feeds/home躲查,其他信息我們先不管,我們看看下面的內(nèi)容坤邪。里面有很多圖片的信息熙含,包括圖片的id,圖片的大小艇纺,圖片的鏈接怎静,還有下一頁的地址邮弹。這個腳本以json格式存儲傳輸?shù)臄?shù)據(jù),json格式是一種輕量級的數(shù)據(jù)交換格式蚓聘,起到封裝數(shù)據(jù)的作用腌乡,易于人閱讀和編寫,同時也易于機器解析和生成夜牡。這么多鏈接与纽,可以看到圖片的鏈接有很多,根據(jù)哪個鏈接下載圖片呢塘装?先別急急迂,讓我們繼續(xù)分析:
在這個網(wǎng)站,我們可以按這個按鈕進行圖片下載蹦肴。我們抓包分下下這個動作僚碎,看看發(fā)送了哪些請求。
https://unsplash.com/photos/1PrQ2mHW-Fo/download?force=true
https://unsplash.com/photos/JX7nDtafBcU/download?force=true
https://unsplash.com/photos/HCVbP3zqX4k/download?force=true
通過Fiddler抓包阴幌,我們發(fā)現(xiàn)勺阐,點擊不同圖片的下載按鈕,GET請求的地址都是不同的矛双。但是它們很有規(guī)律渊抽,就是中間有一段代碼是不一樣的,其他地方都一樣议忽。中間那段代碼是不是很熟悉懒闷?沒錯,它就是我們之前抓包分析得到j(luò)son數(shù)據(jù)中的照片的id栈幸。我們只要解析出每個照片的id毛雇,就可以獲得圖片下載的請求地址,然后根據(jù)這個請求地址侦镇,我們就可以下載圖片了灵疮。那么,現(xiàn)在的首要任務(wù)就是解析json數(shù)據(jù)了壳繁。
json格式的數(shù)據(jù)也是分層的震捣。可以看到next_page里存放的是下一頁的請求地址闹炉,很顯然Unsplash下一頁的內(nèi)容蒿赢,也是動態(tài)加載的。在photos下面的id里渣触,存放著圖片的id羡棵,這個就是我們需要獲得的圖片id號。
怎么編程提取這些json數(shù)據(jù)呢嗅钻?我們也是分步完成:
獲取整個json數(shù)據(jù)
解析json數(shù)據(jù)
編寫代碼皂冰,嘗試獲取json數(shù)據(jù):
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'http://unsplash.com/napi/feeds/home'
req = requests.get(url=target)
print(req.text)
很遺憾店展,程序報錯了,問題出在哪里秃流?通過錯誤信息赂蕴,我們可以看到SSL認證錯誤,SSL認證是指客戶端到服務(wù)器端的認證舶胀。一個非常簡單的解決這個認證錯誤的方法就是設(shè)置requests.get()方法的verify參數(shù)概说。這個參數(shù)默認設(shè)置為True,也就是執(zhí)行認證嚣伐。我們將其設(shè)置為False糖赔,繞過認證不就可以了?
有想法就要嘗試轩端,編寫代碼如下:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'http://unsplash.com/napi/feeds/home'
req = requests.get(url=target, verify=False)
print(req.text)
認證問題解決了挂捻,又有新問題了:
可以看到,我們GET請求又失敗了船万,這是為什么?這個網(wǎng)站反爬蟲的手段除了動態(tài)加載骨田,還有一個反爬蟲手段耿导,那就是驗證Request Headers。接下來态贤,讓我們分析下這個Requests Headers:
我截取了Fiddler的抓包信息舱呻,可以看到Requests Headers里又很多參數(shù),有Accept悠汽、Accept-Encoding箱吕、Accept-Language、DPR柿冲、User-Agent茬高、Viewport-Width、accept-version跑杭、Referer普监、x-unsplash-client剔宪、authorization、Connection熏瞄、Host。它們都是什么意思呢谬以?
專業(yè)的解釋能說的太多强饮,我挑重點:
User-Agent:這里面存放瀏覽器的信息∥瑁可以看到上圖的參數(shù)值邮丰,它表示我是通過Windows的Chrome瀏覽器行您,訪問的這個服務(wù)器。如果我們不設(shè)置這個參數(shù)柠座,用Python程序直接發(fā)送GET請求邑雅,服務(wù)器接受到的User-Agent信息就會是一個包含python字樣的User-Agent。如果后臺設(shè)計者驗證這個User-Agent參數(shù)是否合法妈经,不讓帶Python字樣的User-Agent訪問淮野,這樣就起到了反爬蟲的作用。這是一個最簡單的吹泡,最常用的反爬蟲手段骤星。
Referer:這個參數(shù)也可以用于反爬蟲,它表示這個請求是從哪發(fā)出的爆哑《茨眩可以看到我們通過瀏覽器訪問網(wǎng)站,這個請求是從https://unsplash.com/揭朝,這個地址發(fā)出的队贱。如果后臺設(shè)計者,驗證這個參數(shù)潭袱,對于不是從這個地址跳轉(zhuǎn)過來的請求一律禁止訪問柱嫌,這樣就也起到了反爬蟲的作用。
authorization:這個參數(shù)是基于AAA模型中的身份驗證信息允許訪問一種資源的行為屯换。在我們用瀏覽器訪問的時候编丘,服務(wù)器會為訪問者分配這個用戶ID。如果后臺設(shè)計者彤悔,驗證這個參數(shù)嘉抓,對于沒有用戶ID的請求一律禁止訪問,這樣就又起到了反爬蟲的作用晕窑。
Unsplash是根據(jù)哪個參數(shù)反爬蟲的呢抑片?根據(jù)我的測試,是authorization杨赤。我們只要通過程序手動添加這個參數(shù)蓝丙,然后再發(fā)送GET請求,就可以順利訪問了望拖。怎么什么設(shè)置呢渺尘?還是requests.get()方法,我們只需要添加headers參數(shù)即可说敏。編寫代碼如下:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'http://unsplash.com/napi/feeds/home'
headers = {'authorization':'your Client-ID'}
req = requests.get(url=target, headers=headers, verify=False)
print(req.text)
headers參數(shù)值是通過字典傳入的鸥跟。記得將上述代碼中your Client-ID換成諸位自己抓包獲得的信息。代碼運行結(jié)果如下:
皇天不負有心人,可以看到我們已經(jīng)順利獲得json數(shù)據(jù)了医咨,里面有next_page和照片的id枫匾。接下來就是解析json數(shù)據(jù)。根據(jù)我們之前分析可知拟淮,next_page放在了json數(shù)據(jù)的最外側(cè)干茉,照片的id放在了photos->id里。我們使用json.load()方法解析數(shù)據(jù)很泊,編寫代碼如下:
# -*- coding:UTF-8 -*-
import requests, json
if __name__ == '__main__':
target = 'http://unsplash.com/napi/feeds/home'
headers = {'authorization':'your Client-ID'}
req = requests.get(url=target, headers=headers, verify=False)
html = json.loads(req.text)
next_page = html['next_page']
print('下一頁地址:',next_page)
for each in html['photos']:
print('圖片ID:',each['id'])
解析json數(shù)據(jù)很簡單角虫,跟字典操作一樣,就是字典套字典委造。json.load()里面的參數(shù)是原始的json格式的數(shù)據(jù)戳鹅。程序運行結(jié)果如下:
圖片的ID已經(jīng)獲得了,再通過字符串處理一下昏兆,就生成了我們需要的圖片下載請求地址枫虏。根據(jù)這個地址,我們就可以下載圖片了爬虱。下載方式隶债,使用直接寫入文件的方法。
(3)整合代碼
每次獲取鏈接加一個1s延時跑筝,因為人在瀏覽頁面的時候死讹,翻頁的動作不可能太快。我們要讓我們的爬蟲盡量友好一些继蜡。
# -*- coding:UTF-8 -*-
import requests, json, time, sys
from contextlib import closing
class get_photos(object):
def __init__(self):
self.photos_id = []
self.download_server = 'https://unsplash.com/photos/xxx/download?force=trues'
self.target = 'http://unsplash.com/napi/feeds/home'
self.headers = {'authorization':'your Client-ID'}
"""
函數(shù)說明:獲取圖片ID
Parameters:
無
Returns:
無
Modify:
2017-09-13
"""
def get_ids(self):
req = requests.get(url=self.target, headers=self.headers, verify=False)
html = json.loads(req.text)
next_page = html['next_page']
for each in html['photos']:
self.photos_id.append(each['id'])
time.sleep(1)
for i in range(4):
req = requests.get(url=next_page, headers=self.headers, verify=False)
html = json.loads(req.text)
next_page = html['next_page']
for each in html['photos']:
self.photos_id.append(each['id'])
time.sleep(1)
"""
函數(shù)說明:圖片下載
Parameters:
無
Returns:
無
Modify:
2017-09-13
"""
def download(self, photo_id, filename):
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36'}
target = self.download_server.replace('xxx', photo_id)
with closing(requests.get(url=target, stream=True, verify = False, headers = self.headers)) as r:
with open('%d.jpg' % filename, 'ab+') as f:
for chunk in r.iter_content(chunk_size = 1024):
if chunk:
f.write(chunk)
f.flush()
if __name__ == '__main__':
gp = get_photos()
print('獲取圖片連接中:')
gp.get_ids()
print('圖片下載中:')
for i in range(len(gp.photos_id)):
print(' ?正在下載第%d張圖片' % (i+1))
gp.download(gp.photos_id[i], (i+1))
下載速度還行,有的圖片下載慢是因為圖片太大逛腿∠〔ⅲ可以看到右側(cè)也打印了一些警報信息,這是因為我們沒有進行SSL驗證单默。
學(xué)會了爬取圖片碘举,簡單的動態(tài)加載的網(wǎng)站也難不倒你了。趕快試試國內(nèi)的一些圖片網(wǎng)站吧搁廓!
3. 愛奇藝VIP視頻下載
(1)實戰(zhàn)背景
愛奇藝的VIP視頻只有會員能看引颈,普通用戶只能看前6分鐘。比如加勒比海盜5的URL:http://www.iqiyi.com/v_19rr7qhfg0.html#vfrm=19-9-0-1
我們怎么免費看VIP視頻呢境蜕?一個簡單的方法蝙场,就是通過旋風(fēng)視頻VIP解析網(wǎng)站。URL:http://api.xfsub.com/
這個網(wǎng)站為我們提供了免費的視頻解析粱年,它的通用解析方式是:
http://api.xfsub.com/index.php?url=[播放地址或視頻id]
比如售滤,對于繡春刀這個電影,我們只需要在瀏覽器地址欄輸入:
http://api.xfsub.com/index.php?url=http://www.iqiyi.com/v_19rr7qhfg0.html#vfrm=19-9-0-1
這樣,我們就可以在線觀看這些VIP視頻了:
但是這個網(wǎng)站只提供了在線解析視頻的功能完箩,沒有提供下載接口赐俗,如果想把視頻下載下來,我們就可以利用網(wǎng)絡(luò)爬蟲進行抓包弊知,將視頻下載下來阻逮。
(2)實戰(zhàn)升級
分析方法相同,我們使用Fiddler進行抓包:
我們可以看到秩彤,有用的請求并不多叔扼,我們逐條分析。我們先看第一個請求返回的信息呐舔。
可以看到第一個請求是GET請求币励,沒有什么有用的信息,繼續(xù)看下一條珊拼。
我們看到食呻,第二條GET請求地址變了,并且在返回的信息中澎现,我們看到仅胞,這個網(wǎng)頁執(zhí)行了一個POST請求。POST請求是啥呢剑辫?它跟GET請求正好相反干旧,GET是從服務(wù)器獲得數(shù)據(jù),而POST請求是向服務(wù)器發(fā)送數(shù)據(jù)妹蔽,服務(wù)器再根據(jù)POST請求的參數(shù)椎眯,返回相應(yīng)的內(nèi)容。這個POST請求有四個參數(shù)胳岂,分別為time编整、key、url乳丰、type掌测。記住這個有用的信息,我們在抓包結(jié)果中产园,找一下這個請求汞斧,看看這個POST請求做了什么。
很顯然什燕,這個就是我們要找的POST請求粘勒,我們可以看到POST請求的參數(shù)以及返回的json格式的數(shù)據(jù)。其中url存放的參數(shù)如下:
xfsub_api\/url.php?key=02896e4af69fb18f70129b6046d7c718&time=1505724557&url=http%3A%2F%2Fwww.iqiyi.com%2Fv_19rr7qhfg0.html&type=&xml=1
這個信息有轉(zhuǎn)義了屎即,但是沒有關(guān)系仲义,我們手動提取一下,變成如下形式:
xfsub_api/url.php?key=02896e4af69fb18f70129b6046d7c718&time=1505724557&url=http://www.iqiyi.com/v_19rr7qhfg0.html&type=&xml=1
我們已經(jīng)知道了這個解析視頻的服務(wù)器的域名,再把域名加上:
http://api.xfsub.com/xfsub_api\url.php?key=02896e4af69fb18f70129b6046d7c718&time=1505724557&url=http://www.iqiyi.com/v_19rr7qhfg0.html&type=&xml=1
這里面存放的是什么東西埃撵?不會視頻解析后的地址吧赵颅?我們有瀏覽器打開這個地址看一下:
果然,我們可以看到視頻地址近在眼前啊暂刘,URL如下:
http://disp.titan.mgtv.com/vod.do?fmt=4&pno=1121&fid=1FEA2622E0BD9A1CA625FBE9B5A238A6&file=/c1/2017/09/06_0/1FEA2622E0BD9A1CA625FBE9B5A238A6_20170906_1_1_705.mp4
我們再打開這個視頻地址:
瞧饺谬,我們就這樣得到了這個視頻在服務(wù)器上的緩存地址。根據(jù)這個地址谣拣,我們就可以輕松下載視頻了募寨。
PS:需要注意一點,這些URL地址森缠,都是有一定時效性的拔鹰,很快就會失效,因為里面包含時間信息贵涵。所以列肢,各位在分析的時候,要根據(jù)自己的URL結(jié)果打開網(wǎng)站才能看到視頻宾茂。
接下來瓷马,我們的任務(wù)就是編程實現(xiàn)我們所分析的步驟,根據(jù)不同的視頻播放地址獲得視頻存放的地址跨晴。
現(xiàn)在梳理一下編程思路:
用正則表達式匹配到key欧聘、time、url等信息端盆。
根據(jù)匹配的到信息發(fā)POST請求怀骤,獲得一個存放視頻信息的url。
根據(jù)這個url獲得視頻存放的地址焕妙。
根據(jù)最終的視頻地址蒋伦,下載視頻。
(3)編寫代碼
編寫代碼的時候注意一個問題访敌,就是我們需要使用requests.session()保持我們的會話請求凉敲。簡單理解就是衣盾,在初次訪問服務(wù)器的時候寺旺,服務(wù)器會給你分配一個身份證明。我們需要拿著這個身份證去繼續(xù)訪問势决,如果沒有這個身份證明阻塑,服務(wù)器就不會再讓你訪問。這也就是這個服務(wù)器的反爬蟲手段果复,會驗證用戶的身份陈莽。
思路已經(jīng)給出,希望喜歡爬蟲的人可以在運行下代碼之后,自己重頭編寫程序走搁,因為只有經(jīng)過自己分析和測試之后独柑,才能真正明白這些代碼的意義。上述代碼運行結(jié)果如下:
我們已經(jīng)順利獲得了mp4這個視頻文件地址私植。根據(jù)視頻地址忌栅,使用 urllib.request.urlretrieve() 即可將視頻下載下來。編寫代碼如下:
urlretrieve()有三個參數(shù)曲稼,第一個url參數(shù)是視頻存放的地址索绪,第二個參數(shù)filename是保存的文件名,最后一個是回調(diào)函數(shù)贫悄,它方便我們查看下載進度瑞驱。代碼量不大,很簡單窄坦,主要在于分析過程唤反。代碼運行結(jié)果如下:
下載速度挺快的,幾分鐘視頻下載好了嫡丙。
對于這個程序拴袭,感興趣的朋友可以進行擴展一下,設(shè)計出一個小軟件曙博,根據(jù)用戶提供的url拥刻,提供PC在線觀看、手機在線觀看父泳、視頻下載等功能般哼。
四、總結(jié)
爬蟲時效性低惠窄,同樣的思路過了一個月蒸眠,甚至一周可能無法使用,但是爬取思路都是如此杆融,完全可以自行分析楞卡。
本次實戰(zhàn)代碼,均已上傳我的Github脾歇,歡迎Follow蒋腮、Star:
https://github.com/Jack-Cherish/python-spider
如有問題,請留言藕各。如有錯誤池摧,還望指正,謝謝激况!