爬蟲案例:手把手教你爬取圖片
大家好,我是一行
今天是小白菜同學(xué)的投稿,老規(guī)矩一個(gè)贊10積分
寫在最前面:程序很簡(jiǎn)單,重要的是思路以及對(duì)錯(cuò)誤的分析
1.確認(rèn)目標(biāo)和大體思路:
今天我們需要爬取的網(wǎng)站是:http://www.bbsnet.com/doutu
下面是網(wǎng)站的首頁(yè):

可以看見(jiàn),這個(gè)網(wǎng)站的特點(diǎn)是,一個(gè)圖片是一系列圖片的集合,因此我們需要先獲取每一個(gè)圖片的地址,然后跳轉(zhuǎn)進(jìn)去才能獲取里面真正的圖片。
接下來(lái)我們首先獲取第一頁(yè)的圖片地址,一般來(lái)說(shuō),第一頁(yè)與其他頁(yè)的差別就在于url的一些小差別,因此,搞定第一頁(yè)等于搞定所有。
下面開始盤它。
補(bǔ)充:我們下面使用requets庫(kù)請(qǐng)求和xpath來(lái)解析(附贈(zèng)正則解析方法)
二、分析與編寫代碼:
1.第一步導(dǎo)入庫(kù):
#導(dǎo)包
from lxml import etree
import requests
import re
import time
2.第二步寫出大體框架:
這里我們還是采用面向?qū)ο髞?lái)寫,因?yàn)檫@樣后期如果要改為多線程還是挺容易的。
class MySpider(object):
def __init__(self):
#顯然我們需要最基礎(chǔ)的headers信息
self.headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36',
}
#其次我們需要url
self.url = 'http://www.bbsnet.com/doutu'
def run(self):
#首先我們需要一個(gè)函數(shù)獲取每一個(gè)url
target_urls = self.get_target_urls()
#其次我們需要去獲取每一個(gè)url里面的圖片
self.get_target_images(target_urls)
def get_target_urls(self):
'''這里我們使用一個(gè)東西,比如列表等來(lái)存儲(chǔ)url的結(jié)果并返回'''
pass
def get_target_images(self,target_urls):
'''這里我們需要接收之前獲取的url,所以需要一個(gè)參數(shù)'''
pass
if __name__ == '__main__':
spider = MySpider()
spider.run()
顯然上面的代碼跟我們的思路一模一樣,是不是so easy。
3.第三步完成第一個(gè)函數(shù) -- get_target_urls():
首先按照慣例,寫下以下代碼:
response = requests.get(self.url,headers=self.headers)
print(response.content.decode('utf-8'))
首先,分享下為什么有的時(shí)候我們對(duì)于文本要decode('utf-8'),而有時(shí)候?qū)τ谖谋拘枰猟ecode('gbk'),這是根據(jù)網(wǎng)頁(yè)源代碼的編碼形式來(lái)寫的,比如這個(gè)網(wǎng)站的源代碼如下:

但是,嘗試運(yùn)行了下,發(fā)現(xiàn)以下錯(cuò)誤:
UnicodeEncodeError: 'gbk' codec can't encode character '\u2009' in position 424: illegal multibyte sequence
這個(gè)意思是說(shuō),這個(gè)網(wǎng)頁(yè)存在一些字符是utf-8無(wú)法解碼的,這是很正常的,畢竟有些神奇的字符,是utf-8無(wú)法表示的。
但是,大家不必緊張,這并不影響我們寫代碼。于是我們就這樣吧,不解碼了,直接以二進(jìn)制的形式存儲(chǔ)它,因?yàn)樗⒉挥绊懳覀內(nèi)カ@取我們需要的鏈接。
于是寫下這樣代碼:
response = requests.get(self.url,headers=self.headers)
text = response.content
好的,下面我們開始解析網(wǎng)頁(yè):

可以得到以下結(jié)果:

下面我們使用xpath來(lái)解析這個(gè)html代碼,我們首先如上圖定位a標(biāo)簽,顯然這個(gè)a標(biāo)簽在h2標(biāo)簽里面,且這個(gè)h2標(biāo)簽里面只有這么一個(gè)a標(biāo)簽,所以十分容易獲取,代碼如下:
html = etree.HTML(text)
target_urls = html.xpath('//h2/a/@href')
print(target_urls)
下面是嘗試打印的結(jié)果(部分):
['http://www.bbsnet.com/aoteman.html', 'http://www.bbsnet.com/jieqian.html', 'http://www.bbsnet.com/youxitu.html', 'http://www.bbsnet.com/liaotian-3.html',......]
好的,至此,這個(gè)函數(shù)就完成了,是不是十分簡(jiǎn)單。該函數(shù)代碼如下:
def get_target_urls(self):
response = requests.get(self.url,headers=self.headers)
text = response.content
html = etree.HTML(text)
target_urls = html.xpath('//h2/a/@href')
return target_urls
4.第四步完成第二個(gè)函數(shù) --get_target_images():
老套路,寫下下面的代碼:
for url in target_urls:
response = requests.get(url,headers=self.headers)
text = response.content.decode('utf-8')
print(text)
#這里強(qiáng)烈建議加上break測(cè)試
break
于是,得到下面的結(jié)果:
<html><head><script type="text/javascript">function f(){window.location.href="http://www.bbsnet.com/aoteman.html";}script>head><body onload="f()"><img style="display:none" src="http://tieba.baidu.com/_PXCK_7132125338940_2652120900.gif" />body>html>
好吧,顯然這并不是我們需要的結(jié)果,那這是什么呢?這段代碼(html代碼)會(huì)自動(dòng)把我們重定向到首頁(yè),這樣我們顯然獲取不成功,于是,我們?cè)趺崔k呢?在訪問(wèn)一次就可以了,我們僅需要在加上一次請(qǐng)求即可獲取所需的html代碼。如下:
for url in target_urls:
response = requests.get(url,headers=self.headers)
response = requests.get(url,headers=self.headers)
text = response.content
#這里同上面,不解碼,直接解析,原因同上
print(text)
break
結(jié)果如下(部分結(jié)果):
b'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r\n<html xmlns="http://www.w3.org/1999/xhtml">\r\n<head profile="http://gmpg.org/xfn/11">\r\n<meta charset="UTF-8">\r\n<meta http-equiv="Content-Type" content="text/html" />\r\n<meta name="referrer" content="no-referrer" />.......
好的,解決了這個(gè)大麻煩,我們下面的工作就很簡(jiǎn)單了。

可以得到以下內(nèi)容:

我們需要獲取img標(biāo)簽里面的src屬性,僅需獲取div標(biāo)簽下的p標(biāo)簽下的img標(biāo)簽即可。下面是代碼:
for url in target_urls:
response = requests.get(url,headers=self.headers)
time.sleep(2) #一定要加上,不然會(huì)訪問(wèn)太快會(huì)被禁止的
response = requests.get(url,headers=self.headers)
text = response.content
#開始解析
html = etree.HTML(text)
target_images = html.xpath('//div[@id="post_content"]//p//img/@src')
print(target_images)
break
部分結(jié)果如下:
['http://wx1.sinaimg.cn/mw690/6a04b428ly1g19akwia0rg209b09qwgf.gif', 'http://wx1.sinaimg.cn/mw690/6a04b428ly1g19al1td90g209q08sq4i.gif',....]
好的,我們僅僅剩最后一步,下載圖片,這里我給出兩者方式:
方式一:使用urllib庫(kù)里的urlretrieve()函數(shù),代碼如下:
#該段代碼添加至獲得target_images后面
#大家使用這個(gè)方法的話,一定要記得導(dǎo)入庫(kù):from urllib import request
for image_url in target_images:
request.urlretrieve(image_url,'./info/test.gif')
break
方式二:獲取鏈接的源代碼后保存至本地,代碼如下:
#該段代碼添加至獲得target_images后面
for image_url in target_images:
response = requests.get(image_url,headers=self.headers)
f = open('./info/test.gif','wb')
f.write(response.content)
f.close()
break
兩者的結(jié)果都如下:

ok,到此處,恭喜大家,已經(jīng)完成了99%的任務(wù)了。下面就是完善和補(bǔ)充一些代碼了。
此函數(shù)完整代碼如下:
def get_target_images(self,target_urls):
for url in target_urls:
response = requests.get(url,headers=self.headers)
time.sleep(2)
response = requests.get(url,headers=self.headers)
text = response.content
html = etree.HTML(text)
target_images =html.xpath('//div[@id="post_content"]//p//img/@src')
for image_url in target_images:
request.urlretrieve(image_url,'./info/test.gif')
break
break
#建議大家測(cè)試的時(shí)候,加上break,如果有基礎(chǔ)的可以考慮代理或者多線程
5.第五步優(yōu)化與完善:
一個(gè)優(yōu)秀的人,總是想寫出點(diǎn)優(yōu)秀的代碼,所以優(yōu)化必不可少
1.優(yōu)化角度一:文件存儲(chǔ)的優(yōu)化:
首先,上面我在這個(gè)py文件下面創(chuàng)建了一個(gè)info文件夾,所以才能寫"./info/..."這樣的代碼,但是我相信大家的目錄下肯定沒(méi)有。所以必須優(yōu)化。
其次,我們可以這樣考慮:使用os模塊自動(dòng)創(chuàng)建一個(gè)images的文件夾,其次可以在獲取圖片集合的url時(shí)順便獲取每個(gè)圖片集合的標(biāo)題,所以開始改寫代碼:
修改完成后如下(只需修改第二個(gè)函數(shù)):
def get_target_images(self,target_urls):
os.mkdir('./images')
for url in target_urls:
response = requests.get(url,headers=self.headers)
time.sleep(2)
response = requests.get(url,headers=self.headers)
text = response.content
html = etree.HTML(text)
target_images = html.xpath('//div[@id="post_content"]//p//img/@src')
title = html.xpath('//div[@id="post_content"]//p//img/@title')[0]
os.mkdir('./images/'+title)
for index,image_url in enumerate(target_images):
response = requests.get(image_url, headers=self.headers)
#這里我是用文件方式下載,因?yàn)椴恢罏槭裁次疫\(yùn)行urlretrieve下載時(shí)下太久了
f = open('./images/%s/%s.gif'%(title,index), 'wb')
f.write(response.content)
f.close()
break
示例結(jié)果;

2.優(yōu)化角度二:速度優(yōu)化:
我相信如果直接去掉break去爬取所有的肯定會(huì)報(bào)錯(cuò),因?yàn)樵L問(wèn)太快了,所以可以采取一定的優(yōu)化手段去優(yōu)化它,這里我推薦兩種方式,第一種是多線程,第二種是代理。這里我就不給出如何實(shí)現(xiàn)這種優(yōu)化了,因?yàn)檫@篇文章的目的不在于此。
3.優(yōu)化角度三:代碼本身優(yōu)化:
這里第二個(gè)函數(shù)的代碼其實(shí)有點(diǎn)長(zhǎng)了,因?yàn)槔锩嬗袃蓚€(gè)循環(huán)。所以可以考慮將其中的每一個(gè)循環(huán)封裝為一個(gè)新的函數(shù)。這個(gè)結(jié)果我將給在最后的全部代碼里面。
4.優(yōu)化角度四:異常捕獲:
想要捕獲異常,有簡(jiǎn)單的方式,也有復(fù)雜的方式。下面只給出簡(jiǎn)單方式的代碼:
這里我們只需修改主函數(shù):
def run(self):
try:
#首先我們需要一個(gè)函數(shù)獲取每一個(gè)url
target_urls = self.get_target_urls()
#其次我們需要去獲取每一個(gè)url里面的圖片
self.get_target_images(target_urls)
except Exception as e:
print('錯(cuò)誤原因:',e)
復(fù)雜的方式就是分別去獲取一些常見(jiàn)的異常,如404或者其他異常。
5.完善代碼:
下面給出如何爬取多頁(yè)的思路:
首先我們需要提取出第一、二、三頁(yè)的url,如下:
第一頁(yè):http://www.bbsnet.com/doutu
第二頁(yè):http://www.bbsnet.com/doutu/page/2
第三頁(yè):http://www.bbsnet.com/doutu/page/3
好的,下面提出共同的部分:http://www.bbsnet.com/doutu/page/ + 頁(yè)數(shù)
這里為了驗(yàn)證上面對(duì)于第一頁(yè)這個(gè)特殊頁(yè)是否適用,訪問(wèn)http://www.bbsnet.com/doutu/page/1,結(jié)果可以成功訪問(wèn),因此下面我們只需去一個(gè)個(gè)迭代即可獲取一頁(yè)頁(yè)的圖片。
代碼我將給在整體代碼中。
三、全部代碼:
# -*- coding:utf-8 -*-
# Author: 自學(xué)小白菜
'''
既然選擇了前進(jìn),那每一步都要認(rèn)真的去做
'''
#導(dǎo)包
from lxml import etree
import requests
from urllib import request
import time
import os
class MySpider(object):
def __init__(self):
#顯然我們需要最基礎(chǔ)的headers信息
self.headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36',
}
#其次我們需要url
self.url = 'http://www.bbsnet.com/doutu/page/{page}'
def run(self):
try:
pages = int(input('請(qǐng)輸入需要獲取多少頁(yè):'))
for page in range(1,pages+1):
url = self.url.format(page = page)
#首先我們需要一個(gè)函數(shù)獲取每一個(gè)url
target_urls = self.get_target_urls(url)
#其次我們需要去獲取每一個(gè)url里面的圖片
self.get_target_images(target_urls)
except Exception as e:
print('錯(cuò)誤原因:',e)
def get_target_urls(self,url):
'''這里我們使用一個(gè)東西,比如列表等來(lái)存儲(chǔ)url的結(jié)果并返回'''
response = requests.get(url,headers=self.headers)
text = response.content
#下面開始解析
html = etree.HTML(text)
target_urls = html.xpath('//h2/a/@href')
return target_urls
def get_target_images(self,target_urls):
'''這里我們需要接收之前獲取的url,所以需要一個(gè)參數(shù)'''
os.mkdir('./images')
for url in target_urls:
target_images,title = self.get_info(url)
for index,image_url in enumerate(target_images):
self.download(image_url,index,title)
break
break
def get_info(self,url):
response = requests.get(url, headers=self.headers)
time.sleep(2)
response = requests.get(url, headers=self.headers)
text = response.content
# 開始解析
html = etree.HTML(text)
target_images = html.xpath('//div[@id="post_content"]//p//img/@src')
title = html.xpath('//div[@id="post_content"]//p//img/@title')[0]
os.mkdir('./images/' + title)
return target_images,title
def download(self,image_url,index,title):
response = requests.get(image_url, headers=self.headers)
f = open('./images/%s/%s.gif' % (title, index), 'wb')
f.write(response.content)
f.close()
if __name__ == '__main__':
spider = MySpider()
spider.run()

推薦閱讀
(點(diǎn)擊標(biāo)題可跳轉(zhuǎn)閱讀)
點(diǎn)擊閱讀原文,積分可以免費(fèi)換書
