
人生苦短,我用 Python
前文傳送門(mén):
小白學(xué) Python 爬蟲(chóng)(1):開(kāi)篇
小白學(xué) Python 爬蟲(chóng)(2):前置準備(一)基本類(lèi)庫的安裝
小白學(xué) Python 爬蟲(chóng)(3):前置準備(二)Linux基礎入門(mén)
小白學(xué) Python 爬蟲(chóng)(4):前置準備(三)Docker基礎入門(mén)
小白學(xué) Python 爬蟲(chóng)(5):前置準備(四)數據庫基礎
小白學(xué) Python 爬蟲(chóng)(6):前置準備(五)爬蟲(chóng)框架的安裝
小白學(xué) Python 爬蟲(chóng)(7):HTTP 基礎
小白學(xué) Python 爬蟲(chóng)(8):網(wǎng)頁(yè)基礎
小白學(xué) Python 爬蟲(chóng)(9):爬蟲(chóng)基礎
小白學(xué) Python 爬蟲(chóng)(10):Session 和 Cookies
小白學(xué) Python 爬蟲(chóng)(11):urllib 基礎使用(一)
小白學(xué) Python 爬蟲(chóng)(12):urllib 基礎使用(二)
小白學(xué) Python 爬蟲(chóng)(13):urllib 基礎使用(三)
小白學(xué) Python 爬蟲(chóng)(14):urllib 基礎使用(四)
小白學(xué) Python 爬蟲(chóng)(15):urllib 基礎使用(五)
最近小編工作比較忙,每天能拿來(lái)寫(xiě)內容的時(shí)間都比較短。
剛更新完成的 urllib 基礎內容太干了,跟牛肉干一樣,咬著(zhù)牙疼。

今天稍微空一點(diǎn),決定好好放浪一下形骸,寫(xiě)點(diǎn)不正經(jīng)的內容。
看了標題各位同學(xué)應該知道小編今天要干啥了,沒(méi)毛病,小編要掏出自己多年的珍藏分享給大家。

首先,我們要確定自己的目標,我們本次實(shí)戰的目標網(wǎng)站是:https://www.mzitu.com/ 。
隨便找張圖給大家感受下:
小編要是上班看這個(gè),估計是要被老板吊起來(lái)打的。
如果是進(jìn)來(lái)找網(wǎng)址的,可以直接出去了,下面的內容已經(jīng)無(wú)關(guān)緊要。
先使用 Chrome 瀏覽器打開(kāi)網(wǎng)站 https://www.mzitu.com/ ,按 F12 打開(kāi)開(kāi)發(fā)者工具,查看網(wǎng)頁(yè)的源代碼:
可以看到,這里是一個(gè) a 標簽包裹了一個(gè) img 標簽,這里圖片的顯示是來(lái)源于 img 標簽,而點(diǎn)擊后的跳轉則是來(lái)源于 a 標簽。
將頁(yè)面滾動(dòng)到最下方,看到分頁(yè)組件,點(diǎn)擊下一頁(yè),我們觀(guān)察頁(yè)面 URL 的變化。

可以發(fā)現,頁(yè)面 URL 變化成為 https://www.mzitu.com/xinggan/page/2/ ,多翻幾頁(yè),發(fā)現只是后面的數字在增加。
當前頁(yè)面上的圖片只是縮略圖,明顯不是我們想要的圖片,隨便點(diǎn)擊一個(gè)圖片,進(jìn)入內層頁(yè)面:https://www.mzitu.com/214337/ ,可以發(fā)現,這里圖片才是我們需要的圖片:

和前面的套路一樣,我們往下翻幾頁(yè),可以發(fā)現 URL 的變化規律。
URL 由第一頁(yè)的 https://www.mzitu.com/214337/ 變化成為 https://www.mzitu.com/214337/2 ,同樣是最后一位的數字增加,嘗試當前頁(yè)面最大值 66 ,發(fā)現依然可以正常打開(kāi),如果變?yōu)?67 ,則可以看到當前頁(yè)面的標題已經(jīng)顯示為 404 (頁(yè)面無(wú)法找到)。
上面我們找到了頁(yè)面變化的規律,那么我們肯定是要從首頁(yè)開(kāi)始爬取數據。
接下來(lái)我們開(kāi)始用代碼實(shí)現這個(gè)過(guò)程,使用的請求類(lèi)庫為 Python 自帶的 urllib ,頁(yè)面信息提取采用 xpath 方式,類(lèi)庫使用 lxml 。
關(guān)于 xpath 和 lxml 是什么東西,小編后續會(huì )慢慢講,感興趣的同學(xué)可以先行百度學(xué)習。
下面我們開(kāi)始寫(xiě)代碼。
首先是構建請求頭:
# 請求頭添加 UA
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
'referer': 'https://www.mzitu.com/'
}
# 保存路徑
save_path = 'D:\\spider_file'
如果不加請求頭 UA,會(huì )被拒絕訪(fǎng)問(wèn),直接返回 403 狀態(tài)碼,在獲取圖片的時(shí)候,如果不加 referer ,則會(huì )被認為是盜鏈,同樣也無(wú)法獲取圖片。
保存路徑小編這里保存在本地的 D 盤(pán)的 spider_file 這個(gè)目錄中。
接下來(lái)增加一個(gè)創(chuàng )建文件夾的方法:
import os
# 創(chuàng )建文件夾
def createFile(file_path):
if os.path.exists(file_path) is False:
os.makedirs(file_path)
# 切換路徑至上面創(chuàng )建的文件夾
os.chdir(file_path)
這里需要引入 os 模塊。
然后是重頭戲,抓取首頁(yè)數據并進(jìn)行解析:
import urllib.request
from lxml import etree
# 抓取外頁(yè)數據
def get_outer(outer_url):
req = urllib.request.Request(url=outer_url, headers=headers, method='GET')
resp = urllib.request.urlopen(req)
html = etree.HTML(resp.read().decode('utf-8'))
# 獲取文件夾名稱(chēng)列表
title_list = html.xpath('//*[@id="pins"]/li/a/img/@alt')
# 獲取跳轉鏈接列表
src_list = html.xpath('//*[@id="pins"]/li/a/@href')
print('當前頁(yè)面' + outer_url + ', 共計爬取' + str(len(title_list)) + '個(gè)文件夾')
for i in range(len(title_list)):
file_path = save_path + '\\' + title_list[i]
img_url = src_list[i]
# 創(chuàng )建對應文件夾
createFile(file_path)
# 寫(xiě)入對應文件
get_inner(img_url, file_path)
具體每行代碼是做什么,小編就不一一詳細介紹了,注釋已經(jīng)寫(xiě)得比較清楚了。
我們爬取完外頁(yè)的信息后,需要通過(guò)外頁(yè)提供的信息來(lái)爬取內頁(yè)的數據,這也是我們真正想要爬取的數據,具體的爬取思路已經(jīng)在上面講過(guò)了,這里直接貼代碼:
import urllib.request
import os
from lxml import etree
import time
# 抓取內頁(yè)數據并寫(xiě)入文件
def get_inner(url, file_path):
req = urllib.request.Request(url=url, headers=headers, method='GET')
resp = urllib.request.urlopen(req)
html = etree.HTML(resp.read().decode('utf-8'))
# 獲取當前頁(yè)面最大頁(yè)數
max_num = html.xpath('/html/body/div[2]/div[1]/div[4]/a[5]/span/text()')[0]
print('當前頁(yè)面url:', url, ', 最大頁(yè)數為', max_num)
for i in range(1, int(max_num)):
# 訪(fǎng)問(wèn)過(guò)快會(huì )被限制,增加睡眠時(shí)間
time.sleep(1)
inner_url = url + '/' + str(i)
inner_req = urllib.request.Request(url=inner_url, headers=headers, method='GET')
inner_resp = urllib.request.urlopen(inner_req)
inner_html = etree.HTML(inner_resp.read().decode('utf-8'))
# 獲取圖片 url
img_src = inner_html.xpath('/html/body/div[2]/div[1]/div[3]/p/a/img/@src')[0]
file_name = str(img_src).split('/')[-1]
# 下載圖片
try:
request = urllib.request.Request(url=img_src, headers=headers, method='GET')
response = urllib.request.urlopen(request)
get_img = response.read()
file_os_path = file_path + '\\' + file_name
if os.path.isfile(file_os_path):
print('圖片已存在:', file_os_path)
pass
else:
with open(file_os_path, 'wb') as fp:
fp.write(get_img)
print('圖片保存成功:', file_os_path)
fp.close()
except Exception as e:
print('圖片保存失敗')
因為該網(wǎng)站對訪(fǎng)問(wèn)速度有限制,所以小編在這里增加了程序的睡眠時(shí)間,這只是一種解決方案,還可以使用代理的方式突破限制,穩定高速代理都挺貴的,小編還是慢一點(diǎn)慢慢爬比較省錢(qián)。
整體的代碼到這一步,主體框架邏輯已經(jīng)完全確定了,只需要對代碼做一個(gè)大致簡(jiǎn)單的整理即可,所以,完整的代碼如下:
import urllib.request
import os
from lxml import etree
import time
# 請求頭添加 UA
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
'referer': 'https://www.mzitu.com/'
}
# 保存路徑
save_path = 'D:\\spider_file'
# 創(chuàng )建文件夾
def createFile(file_path):
if os.path.exists(file_path) is False:
os.makedirs(file_path)
# 切換路徑至上面創(chuàng )建的文件夾
os.chdir(file_path)
# 抓取外頁(yè)數據
def get_outer(outer_url):
req = urllib.request.Request(url=outer_url, headers=headers, method='GET')
resp = urllib.request.urlopen(req)
html = etree.HTML(resp.read().decode('utf-8'))
# 獲取文件夾名稱(chēng)列表
title_list = html.xpath('//*[@id="pins"]/li/a/img/@alt')
# 獲取跳轉鏈接列表
src_list = html.xpath('//*[@id="pins"]/li/a/@href')
print('當前頁(yè)面' + outer_url + ', 共計爬取' + str(len(title_list)) + '個(gè)文件夾')
for i in range(len(title_list)):
file_path = save_path + '\\' + title_list[i]
img_url = src_list[i]
# 創(chuàng )建對應文件夾
createFile(file_path)
# 寫(xiě)入對應文件
get_inner(img_url, file_path)
# 抓取內頁(yè)數據并寫(xiě)入文件
def get_inner(url, file_path):
req = urllib.request.Request(url=url, headers=headers, method='GET')
resp = urllib.request.urlopen(req)
html = etree.HTML(resp.read().decode('utf-8'))
# 獲取當前頁(yè)面最大頁(yè)數
max_num = html.xpath('/html/body/div[2]/div[1]/div[4]/a[5]/span/text()')[0]
print('當前頁(yè)面url:', url, ', 最大頁(yè)數為', max_num)
for i in range(1, int(max_num)):
# 訪(fǎng)問(wèn)過(guò)快會(huì )被限制,增加睡眠時(shí)間
time.sleep(1)
inner_url = url + '/' + str(i)
inner_req = urllib.request.Request(url=inner_url, headers=headers, method='GET')
inner_resp = urllib.request.urlopen(inner_req)
inner_html = etree.HTML(inner_resp.read().decode('utf-8'))
# 獲取圖片 url
img_src = inner_html.xpath('/html/body/div[2]/div[1]/div[3]/p/a/img/@src')[0]
file_name = str(img_src).split('/')[-1]
# 下載圖片
try:
request = urllib.request.Request(url=img_src, headers=headers, method='GET')
response = urllib.request.urlopen(request)
get_img = response.read()
file_os_path = file_path + '\\' + file_name
if os.path.isfile(file_os_path):
print('圖片已存在:', file_os_path)
pass
else:
with open(file_os_path, 'wb') as fp:
fp.write(get_img)
print('圖片保存成功:', file_os_path)
fp.close()
except Exception as e:
print('圖片保存失敗')
def main():
url = 'https://www.mzitu.com/xinggan/page/'
for i in range(1, 163):
get_outer(url + str(i))
if __name__ == '__main__':
main()
最后看一下小編大致用 20 分鐘左右爬取的數據:

做爬蟲(chóng)比較重要的部分是需要在網(wǎng)頁(yè)上分析我們所需要提取的數據的數據來(lái)源,需要我們清晰的了解清楚頁(yè)面的 URL 的具體變化,而這些變化一定是遵循某種規則的,找到這種規則就可以達成自動(dòng)化的目的。
一些網(wǎng)站會(huì )對爬蟲(chóng)做一些防御,這些防御并不是完全無(wú)解的,其實(shí)就像矛和盾的故事。本次爬取數據的過(guò)程中遇到的防御方式有限制 ip 一定時(shí)間的訪(fǎng)問(wèn)次數,防止非本網(wǎng)站的請求請求圖片(防盜鏈,referer 檢測),以及 UA 頭檢測(防止非瀏覽器訪(fǎng)問(wèn))。但是正所謂上有 x 策下有 x 策,防御并不是無(wú)解的,只是解決代價(jià)的大小而已。
小編這只爬蟲(chóng)還是希望能起到拋磚引玉的作用,希望大家能自己親自動(dòng)手試試看。
多動(dòng)手才能學(xué)好代碼哦~~~
本系列的所有代碼小編都會(huì )放在代碼管理倉庫 Github 和 Gitee 上,方便大家取用。
聯(lián)系客服