當(dāng)我們需要從網(wǎng)頁上面下載很多圖片的時(shí)候,一個一個手動保存實(shí)在是太累人了。那么有沒有批量下載的辦法呢?
答案是有的,Python爬蟲就可以完美的做到這一點(diǎn),而且作為一個Python的初學(xué)者,我可以很負(fù)責(zé)任的告訴你,這門語言入門挺簡單的,特別是對于那些有其他編程語言經(jīng)驗(yàn)的人。
1 爬取原理講解
提示:沒耐心看原理的同學(xué)可以直接跳到第2步實(shí)戰(zhàn)。
網(wǎng)頁上的圖片其實(shí)都是存到一個服務(wù)器上面的,每張圖片都會有一個對應(yīng)的下載網(wǎng)址。
當(dāng)我們在瀏覽器上打開一個包含很多圖片的網(wǎng)址時(shí),這個網(wǎng)址其實(shí)不是這些圖片實(shí)際的地址,而是把所有圖片的真實(shí)地址都包含在這個網(wǎng)址上面了。所以當(dāng)我們在圖片上面右鍵點(diǎn)擊保存圖片,其實(shí)是通過訪問這張圖片的下載網(wǎng)址來下載圖片的。那么同樣的,我們用Python爬取圖片也是基于這個操作。
總而言之,爬取網(wǎng)頁圖片的根本方法就是在這個網(wǎng)頁上找到圖片的原圖地址,然后下載保存。
實(shí)際操作的大概流程如下:
- 訪問一個包含圖片的網(wǎng)址。
- 分析這個網(wǎng)頁的HTML源碼,找到目標(biāo)圖片的下載地址。
- 通過圖片地址下載保存到電腦上。
- 通過這個網(wǎng)址得到下一頁或者下一個套圖的網(wǎng)址。
- 繼續(xù)重復(fù)上面4個操作。
1.1 查看網(wǎng)頁源代碼
我們在瀏覽器上面隨便打開一個有圖片的網(wǎng)頁,按F12
就會彈出網(wǎng)頁源代碼。左邊是網(wǎng)頁的內(nèi)容,右邊則是HTML代碼。
比如我打開一個網(wǎng)頁(域名:Xgyw.Top,我這里只是舉個例子,這個網(wǎng)站的網(wǎng)址是定期變的,過一段時(shí)間可能就打不開了)。
1.2 分析網(wǎng)頁源碼并制定對應(yīng)的爬取方案
1) 找到目標(biāo)圖片的相關(guān)信息
剛打開html源碼的時(shí)候,很多內(nèi)容是折疊起來的,我們可以展開,然后在這些源碼里面找到我們需要下載的圖片相關(guān)的信息。
網(wǎng)頁里面的內(nèi)容其實(shí)是一個個模塊拼起來的,當(dāng)鼠標(biāo)放在某一行代碼上面,這段源碼對應(yīng)的網(wǎng)頁內(nèi)容會出現(xiàn)一個藍(lán)色的框框,通過這個提示我們可以很快的找到目標(biāo)圖片對應(yīng)的那段代碼。
2) 從圖片信息中提取目標(biāo)圖片的下載地址
找到圖片的信息之后就需要根據(jù)實(shí)際情況分析了,有些網(wǎng)頁是直接把原圖的完整網(wǎng)址放在這里了,這種就比較簡單,我們直接把網(wǎng)址提取出來,然后訪問這個網(wǎng)站下載圖片即可。
但是有些網(wǎng)頁是不會這么容易就讓你得到這個網(wǎng)址的,比如我上面這個網(wǎng)站,一個網(wǎng)頁有3張圖片,但是都沒有完整的提供圖片地址,不過這個標(biāo)簽里面有一個src的信息,這里面的內(nèi)容明顯就跟圖片的網(wǎng)址有關(guān)聯(lián)。
我們這時(shí)可以在網(wǎng)頁的圖片上右鍵,在新標(biāo)簽頁中打開圖片,然后查看一下原圖的網(wǎng)址。通過對比,我發(fā)現(xiàn)這里src的信息正是原圖地址后面的一部分,而前面那部分則是一個固定的內(nèi)容。也就是說這里他把一個完整的地址拆成了兩部分,網(wǎng)頁源代碼里面只有后半部分的信息,很雞賊。
但是找到規(guī)律了之后就好辦了,我們只需要在Python的代碼里面把這兩段地址拼接起來就能得到一個完整的地址了。
提示:如果你沒辦法從這個網(wǎng)頁中得到目標(biāo)圖片的地址,那就沒辦法繼續(xù)往下走。
比如這個網(wǎng)頁對圖片做了加密,需要登錄或者VIP權(quán)限才能查看到圖片的信息,而你又沒有的時(shí)候,就沒辦法通過這種方式爬取到目標(biāo)圖片了。
1.3 完善爬取流程和細(xì)節(jié)
當(dāng)我們能夠解決原圖地址的問題,那么爬取就不是什么難事了,我們下一步需要做的是完善整個爬取的流程,以便于批量下載。
1) 根據(jù)網(wǎng)頁排版確定哪些是目標(biāo)圖片
不同的網(wǎng)頁排版可能會不同,比如有些網(wǎng)站一個網(wǎng)頁里面只放3張圖片,有的則放5張或者更多,而且除了目標(biāo)圖片邊上可能也會有一些小的預(yù)覽圖,下一頁等等。
我們需要在這些信息中準(zhǔn)確的找到目標(biāo)圖片,這個就需要根據(jù)實(shí)際爬取的網(wǎng)頁來編寫爬取的代碼了。方法是有很多的,要靈活使用,只要能達(dá)到目的就行,沒有規(guī)定說一定要用哪種方法。
2) 爬取完一個網(wǎng)頁之后自動查找下一個目標(biāo)
圖片比較多的時(shí)候,不可能把所有圖片都放在一個網(wǎng)頁上,所以很多時(shí)候需要跳轉(zhuǎn)到下一頁,這個時(shí)候我們就需要在HTML源碼上面找到下一頁的入口地址,或者通過觀察每一頁的網(wǎng)址并從中找到排列規(guī)律,然后跳轉(zhuǎn)到下一個網(wǎng)頁繼續(xù)爬。
具體怎么找要看這個網(wǎng)頁的源碼是怎樣的,原理跟查找原圖地址是一樣的,這里就不在過多贅述了。
3) 爬取目標(biāo)圖片的命名以及保存路徑
我們下載完一張圖片之后肯定是需要命名以及保存的,命名以及路徑如果設(shè)置的不合理,到時(shí)候下載完就不好查找了,特別是一次性批量下載很多套圖的時(shí)候,如果都堆到一個文件夾下面或者隨機(jī)命名,到時(shí)候想找一張圖片就跟大海撈針一樣。
因此,我們需要提前想好文件的結(jié)構(gòu)和圖片的命名方式。
比如:很多網(wǎng)頁上面本身就對套圖進(jìn)行了命名,那么我們就可以按照網(wǎng)頁上面的命名創(chuàng)建文件夾,然后再把相應(yīng)的圖片放到這個文件夾下,按序號排列圖片。
當(dāng)然了,我這里只是舉個例子,具體實(shí)施的時(shí)候你愛怎么搞就怎么搞。
4) 偽裝成瀏覽器訪問,防止網(wǎng)頁有反爬機(jī)制
有些網(wǎng)站是有反爬機(jī)制的,如果它檢查你的請求參數(shù),發(fā)現(xiàn)不符合它的規(guī)則時(shí),那么它就不會返回正確的信息給你,這也就沒辦法獲取到目標(biāo)圖片的信息了。
因此,我們有時(shí)候需要偽裝成正常的瀏覽器訪問。
2 實(shí)戰(zhàn)演練
2.1 PyCharm下載安裝
這個是一個比較受歡迎的Python開發(fā)IDE,用起來也是十分的方便,具體怎么下載安裝和使用,這里就不講解了,網(wǎng)上都能找到教程,不懂的同學(xué)請自行查閱資料。
2.2 安裝相應(yīng)依賴包(類庫)
下面是需要用到的一些庫以及發(fā)布這篇博客時(shí)當(dāng)前的版本,其中有些是Python自帶的標(biāo)準(zhǔn)庫,不需要再安裝了。
不會怎么安裝庫的同學(xué)請自行查找相關(guān)教程,可以在pycharm上直接裝,也可以借助pip裝。
package | version |
---|---|
os | 注:python標(biāo)準(zhǔn)庫,無需安裝 |
re | 注:python標(biāo)準(zhǔn)庫,無需安裝 |
time | 注:python標(biāo)準(zhǔn)庫,無需安裝 |
requests | v2.28.1 |
bs4 | v0.01 |
2.3 編寫代碼
我這里還是以上面講的那個很頂?shù)木W(wǎng)站為例,編寫相應(yīng)的爬取的代碼。
其他的網(wǎng)站其實(shí)都是大同小異的,關(guān)鍵是要靈活運(yùn)用。
根據(jù)上面講解的原理以及對這個網(wǎng)站的分析,先寫第一版代碼,主要是確保目標(biāo)圖片能夠正確的被下載保存,如果流程走通了,我們后面第二版再去完善一些細(xì)節(jié),如果走不通,那就要重新分析,查找失敗的原因。
第一版測試代碼:
# 第一版
#-*-coding:utf-8-*-
import os
import re
import time
import requests
import bs4
from bs4 import BeautifulSoup
# 手動寫入目標(biāo)套圖的首頁地址
download_url = "https://www.xgmn09.com/XiaoYu/XiaoYu23172.html"
# 手動寫入目標(biāo)套圖的頁數(shù)
page_num = 25
# 創(chuàng)建一個文件夾用來保存圖片
file_name = "測試圖庫"
# 目標(biāo)圖片下載地址的前半部分(固定不變那部分,后半段是變化的,需要解析網(wǎng)頁得到)
imgae_down_url_1 = "https://jpxgyw.net"
# 創(chuàng)建文件夾
def CreateFolder(file):
"""創(chuàng)建存儲數(shù)據(jù)文件夾"""
flag = 1
while flag == 1: # 若文件已存在,則不繼續(xù)往下走以免覆蓋了原文件
if not os.path.exists(file):
os.mkdir(file)
flag = 0
else:
print('該文件已存在,請重新輸入')
flag = 1
time.sleep(1)
# 返回文件夾的路徑,這里直接放這工程的根目錄下
path = os.path.abspath(file) + ""
return path
# 下載圖片
def DownloadPicture(download_url, list, path):
# 訪問目標(biāo)網(wǎng)址
r = requests.get(url=download_url, timeout=20)
r.encoding = r.apparent_encoding
soup = BeautifulSoup(r.text, "html.parser")
# 解析網(wǎng)址,提取目標(biāo)圖片相關(guān)信息,注:這里的解析方法是不固定的,可以根據(jù)實(shí)際的情況靈活使用
p = soup.find_all("p")
tag = p[0].find_all("img") # 得到該頁面目標(biāo)圖片的信息
# 下載圖片
j = 0
for i in range(list, list + 3):
if(j < len(tag) and tag[j].attrs['src'] != None) :
img_name = str(i) + ".jpg" # 以數(shù)字命名圖片,圖片格式為jpg
# 獲取目標(biāo)圖片下載地址的后半部分
imgae_down_url_2 = tag[j].attrs['src']
j = j + 1
# 把目標(biāo)圖片地址的前后兩部分拼接起來,得到完整的下載地址
imgae_down_url = imgae_down_url_1 + imgae_down_url_2
print("imgae_down_url: ", imgae_down_url)
# 下載圖片
try:
img_data = requests.get(imgae_down_url)
except:
continue
# 保存圖片
img_path = path + img_name
with open(img_path,'wb') as fp:
fp.write(img_data.content)
print(img_name, " ******下載完成!")
# 主函數(shù)
if __name__ == "__main__":
# 創(chuàng)建保存數(shù)據(jù)的文件夾
path = CreateFolder(file_name)
print("創(chuàng)建文件夾成功: ", path)
# 按頁下載圖片
for i in range(0, page_num):
if i == 0 :
page_url = download_url # 首頁網(wǎng)址,注:因?yàn)檫@個網(wǎng)站首頁和后面那些頁面網(wǎng)址的規(guī)則不一樣,所以這里要區(qū)分開來
else :
page_url = download_url[:-5] + "_" + str(i) + ".html" # 第2頁往后的網(wǎng)址,都是用數(shù)字來排布頁面
# 下載圖片
# print("page_url: ", page_url)
DownloadPicture(page_url, i * 3, path) # 注:這個網(wǎng)站每一頁最多是3張圖片,每張圖片我都用數(shù)字命名
print("全部下載完成!", "共" + str(len(os.listdir(path))) + "張圖片")
運(yùn)行測試:
運(yùn)行之后我們可以通過輸出的log看到每張圖片的下載地址以及圖片保存的一個情況,并且在項(xiàng)目工程的根目錄下我們可以看到新建的這個文件夾以及里面的圖片。
開始運(yùn)行:
運(yùn)行結(jié)束:
在工程根目錄下自動創(chuàng)建的文件夾:
爬取到的圖片:
圖片這里就不放出來了,因?yàn)檫@個圖片的問題我的文章發(fā)布了10天就被下架了,還有就是這個網(wǎng)站最近好像訪問不了了,不知道是怎么回事,反正大家學(xué)方法就行了,還有很多其他網(wǎng)站可以實(shí)踐。
至此,初步的流程是已經(jīng)走通了。
2.4 補(bǔ)充細(xì)節(jié)和優(yōu)化
前面的測試已經(jīng)驗(yàn)證了這個流程是OK的,那么我們現(xiàn)在就可以在這個基礎(chǔ)上繼續(xù)補(bǔ)充一些細(xì)節(jié),完善這套代碼了。
我主要完善了以下幾點(diǎn):
- 文件保存的路徑改成自定義,之前的代碼是直接放在工程的根目錄下的。
- 修改了文件夾的命名,由原來固定寫死的命名改為從網(wǎng)頁獲取套圖的名稱,方便了我們后續(xù)批量操作。
- 套圖的頁面總數(shù)通過解析該套圖的首頁得到,不需要再手動輸入了。
- 下載圖片的時(shí)候增加了超時(shí)重連,防止意外出現(xiàn)的網(wǎng)絡(luò)問題導(dǎo)致下載失敗從而丟失某些圖片。
- 在爬取圖片之前先檢查一下輸入的地址,如果不是該套圖的首頁,則按這個網(wǎng)站的規(guī)則改成首頁的地址。
注:這樣一來我只需要輸入該套圖的任意一頁地址,就能正確爬取到這個套圖。 - 每個網(wǎng)頁爬取的圖片數(shù)量不再是固定的,而是根據(jù)這個網(wǎng)頁本身存放目標(biāo)圖片的數(shù)量來爬取,動態(tài)變化。
注:之前按照我的分析,除了最后一頁是不固定的,其他的頁面都是3張圖片,但是后來發(fā)現(xiàn)并不是所有套圖都這樣排版,如果都按3張爬取,雖然也不會漏,但是圖片的命名排序中間會空一些數(shù)字,比如前面1-9都是正常排序,然后直接跳過10,從11繼續(xù)往后排了。 - 增加了請求的headers,偽造成瀏覽器訪問。
注:實(shí)際上這一步也可以不用,因?yàn)樯厦娴牡谝话娲a沒加這個也能正常爬取,說明這個網(wǎng)站應(yīng)該是沒有反爬機(jī)制的。不過不管爬什么網(wǎng)頁最好都加上,不然可能會有其他的意外。
第二版測試代碼:
# 第二版
# -*-coding:utf-8-*-
import os
import re
import time
import requests
import bs4
from bs4 import BeautifulSoup
# 手動寫入目標(biāo)套圖的首頁地址
download_url = "https://www.xgmn09.com/XiaoYu/XiaoYu23172.html"
# 這里不需要手動輸入頁面數(shù)量了,可以通過解析首頁地址得到總頁面數(shù)
# page_num = 25
# 文件保存的絕對路徑(D:imgaetest_file),注:這個路徑上面的文件夾一定是要已經(jīng)創(chuàng)建好了的,不然運(yùn)行會報(bào)錯
file_path = "D:imgaetest_file"
# 文件名通過網(wǎng)頁得到,注:以網(wǎng)頁上套圖的名字命名
file_name = " "
# 目標(biāo)圖片下載地址的前半部分,注:固定不變那部分,后半段是變化的,需要解析網(wǎng)頁得到
imgae_down_url_1 = "https://jpxgyw.net"
# 修改請求headers以偽裝成瀏覽器訪問,從而繞開網(wǎng)站的反爬機(jī)制獲取正確的頁面,注:這個需要根據(jù)自己瀏覽器的實(shí)際的信息改
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36"
}
# 訪問網(wǎng)頁并返回HTML相關(guān)的信息
def getHTMLText(url, headers):
# 向目標(biāo)服務(wù)器發(fā)起請求并返回響應(yīng)
try:
r = requests.get(url=url, headers=headers, timeout=20)
r.encoding = r.apparent_encoding
soup = BeautifulSoup(r.text, "html.parser")
return soup
except:
return ""
# 獲取該套圖的名稱和總頁面數(shù)
def getFileName(url, headers):
# 從目標(biāo)地址上面獲取套圖的名稱
soup = getHTMLText(url, headers)
head1 = soup.find_all("header")
h1 = head1[1].find_all("h1")
name = h1[0].text
pagination = soup.find_all("div", "pagination")
a = pagination[0].find_all("a")
page = int(a[len(a) - 2].text)
return name, page
# 創(chuàng)建文件夾
def CreateFolder(file_name):
flag = True
num = 0
while flag == 1:
if num <= 0:
file = file_path + '' + file_name # 如果文件夾不存在,則創(chuàng)建文件夾
else:
file = file_path + '' + str(num) + '_' + file_name # 如果文件夾已存在,則在文件夾前面加上數(shù)字,防止覆蓋掉以前保存過的文件
if not os.path.exists(file):
os.mkdir(file)
flag = False
else:
print('該文件名已存在,已重新命名')
flag = True
num += 1
# time.sleep(1)
# 返回文件存放的路徑
path = os.path.abspath(file) + ''
return path
# 下載圖片
def DownloadPicture(url, img_num, path):
# 訪問目標(biāo)網(wǎng)址
soup = getHTMLText(url, headers)
# 解析網(wǎng)址,提取目標(biāo)圖片相關(guān)信息,注:這里的解析方法是不固定的,可以根據(jù)實(shí)際的情況靈活使用
p = soup.find_all("p")
tag = p[0].find_all("img") # 得到該頁面目標(biāo)圖片的信息
# 下載圖片
for i in range(0, len(tag)):
if (tag[i].attrs['src'] != None):
# 解析網(wǎng)址,得到目標(biāo)圖片的下載地址
imgae_down_url_2 = tag[i].attrs['src'] # 獲取目標(biāo)圖片下載地址的后半部分
imgae_url = imgae_down_url_1 + imgae_down_url_2 # 把目標(biāo)圖片地址的前后兩部分拼接起來,得到完整的下載地址
print("imgae_url: ", imgae_url)
# 給圖片命名
img_num += 1
name = tag[i].attrs['alt'] + '_' + str(img_num) # 獲取img標(biāo)簽的alt屬性,用來給保存的圖片命名,圖片格式為jpg
img_name = name + ".jpg"
# 下載圖片
timeout = 5 # 超時(shí)重連次數(shù)
while timeout > 0:
try:
img_data = requests.get(url=imgae_url, headers=headers, timeout=30)
# 保存圖片
img_path = path + img_name
with open(img_path, 'wb') as fp:
fp.write(img_data.content)
print(img_name, "******下載完成!")
timeout = 0
except:
print(img_name, "******等待超時(shí),下載失??!")
time.sleep(1)
timeout -= 1
return img_num
# 主函數(shù)
if __name__ == "__main__":
# 記錄下載時(shí)間
start = time.time()
# 檢查網(wǎng)址,如果輸入的網(wǎng)址不是首頁,則改成首頁地址
result = download_url.find('_')
if(result != -1):
new_str = ''
check_flag = 1
for i in range(0, len(download_url)):
if(download_url[i] != '_' and check_flag):
new_str = new_str + download_url[i]
else:
if(download_url[i] == '_'):
check_flag = 0
if(download_url[i] == '.'):
new_str = new_str + download_url[i]
check_flag = 1
download_url = new_str
print("new download_url: ", download_url)
# 創(chuàng)建保存數(shù)據(jù)的文件夾
file_name, page_num = getFileName(download_url, headers) # 獲取套圖名稱
print("page_num: ", page_num)
path = CreateFolder(file_name)
print("創(chuàng)建文件夾成功: ", path)
# 按頁下載圖片
image_num = 0 # 當(dāng)前累計(jì)下載圖片總數(shù)
for i in range(0, int(page_num)):
if i == 0:
page_url = download_url # 首頁網(wǎng)址,注:因?yàn)檫@個網(wǎng)站首頁和后面那些頁面網(wǎng)址的規(guī)則不一樣,所以這里要區(qū)分開來
else:
page_url = download_url[:-5] + "_" + str(i) + ".html" # 第2頁往后的網(wǎng)址,都是用數(shù)字來排布頁面
# 下載圖片
print("page_url: ", page_url)
image_num = DownloadPicture(page_url, image_num, path)
# image_num = num # 每下載完一頁圖片就累計(jì)當(dāng)前下載圖片總數(shù)
print("全部下載完成!", "共" + str(len(os.listdir(path))) + "張圖片")
# 打印下載總耗時(shí)
end = time.time()
print("共耗時(shí)" + str(end - start) + "秒")
2.5 運(yùn)行測試
運(yùn)行結(jié)果:
上述需要完善的功能均已實(shí)現(xiàn),nice!
到這里的話基本功能算是完成了,我們先設(shè)置好保存的路徑,然后每次只需輸入一個套圖的地址就可以自動下載保存好。
其實(shí)這個代碼還有很多地方可以繼續(xù)優(yōu)化的,不過我只是業(yè)余玩一下,就不搞那么復(fù)雜了,感興趣的同學(xué)可以繼續(xù)深入。
提示:上述這套代碼只是針對這個測試網(wǎng)頁當(dāng)前的結(jié)構(gòu)來解析的,如果后面這個網(wǎng)站做了修改或者換一個網(wǎng)站,那么這套代碼不一定還能用,總的來說還是要根據(jù)實(shí)際的情況去做調(diào)整。
所以,如果這篇博客發(fā)布了很久以后你才看到,并且發(fā)現(xiàn)這套代碼已經(jīng)用不了,不用懷疑,這跟博主沒有關(guān)系,我現(xiàn)在測試是OK的。
結(jié)束語
好了,關(guān)于如何用Python爬取網(wǎng)頁圖片的介紹就到這里了,內(nèi)容不多,講的也比較簡單,主要還是要了解原理,大部分網(wǎng)頁都是萬變不離其中,原理懂了自然就能找到破解方法。
我自己也是一個初學(xué)者,如果哪里講的不對或者有更好的方法,歡迎指正,如果還有什么問題,歡迎在評論區(qū)留言或者私信給我。
閑話:
可能有些同學(xué)會說:你這個代碼需要一個一個輸入太麻煩了,能不能批量爬取多個套圖呢?
答案是當(dāng)然可以,你可以從這個網(wǎng)站的首頁開始進(jìn)入,然后跳轉(zhuǎn)到每個套圖里面一個一個下載,直到把整個網(wǎng)站都爬完。也可以利用這個網(wǎng)站的檢索功能,通過篩選關(guān)鍵字,把某些有共同特征的套圖下載下來。
這些操作都是沒問題的,問題是你頂?shù)米幔?/p>
PS:如果你有其他值得爬的網(wǎng)站也可以在評論區(qū)分享一下,獨(dú)樂樂不如眾樂樂。
想了解更多Python的內(nèi)容,可以關(guān)注一下博主,后續(xù)我還會繼續(xù)分享更多的經(jīng)驗(yàn)給大家。
如果這篇文章能夠幫到你,就…你懂得。
PS:這篇文章發(fā)布之后,有些同學(xué)問我說沒有python的環(huán)境能不能用,于是我又改了一版代碼生成了一個exe應(yīng)用程序,不需要搭環(huán)境,只要是windows系統(tǒng)都可以運(yùn)行。
下載地址:https://download.csdn.net/download/ShenZhen_zixian/86514142
使用說明:
文件目錄: