功能:爬取目標網站全部主要圖片(例子中是美圖錄網站的全部寫真圖片,按人名分類)
本示例使用Python3.5,需要額外安裝BeautifulSoup 4
BeautifulSoup 4 安裝方法:
Linux:
sudo apt-get install python-bs4
Mac:
sudo easy_install pip
pip install beautifulsoup4
Windows:
下載源碼后,
python setup.py install
或者:
pip install beautifulsoup4
具體安裝方式見:<a >點這里</a>
分析網站結構
目標網站<a >“美圖錄”</a>(別問我為什么選這個網站。。。百度上“隨便”找的)
因為打算下載全部的網頁圖片,所以從最小的單元開始,也就是圖片集(再小就是單一的圖片了,也就可以直接下載了)
先打開首頁,隨便點開一個圖片集,發現圖片集的地址是這樣的
http://www.meitulu.com/item/7487.html
在圖片集中檢查頁面元素,如下所示
<div class="content">
<center>
![[Ugirls尤果網] U181 陳雅漫 寫真套圖_第1頁/第1張圖](http://upload-images.jianshu.io/upload_images/2475481-8cdfce535296ab31.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
</center>
<center>
![[Ugirls尤果網] U181 陳雅漫 寫真套圖_第1頁/第3張圖](http://upload-images.jianshu.io/upload_images/2475481-43e882deb2d0c0cf.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
</center>
<center>
![[Ugirls尤果網] U181 陳雅漫 寫真套圖_第1頁/第4張圖](http://upload-images.jianshu.io/upload_images/2475481-2d5cb01257ad639e.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
</center>
<center>
![[Ugirls尤果網] U181 陳雅漫 寫真套圖_第1頁/第5張圖](http://upload-images.jianshu.io/upload_images/2475481-8e773c5ec770c466.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
</center>
</div>
發現每一張主要圖片資源鏈接都在center標簽中,這樣就可以在這個頁面上提取圖片鏈接并下載了
繼續向下,發現下圖所示,圖片并不是存放在一個頁面中的
而檢查這里的html代碼可以看到
<div id="pages">
<a class="a1" >上一頁</a>
<span>1</span>
<a >2</a>
<a >4</a>
<a >5</a>
<a >6</a>
<a >7</a>
<a >8</a>
<a >9</a>
<a >10</a>
".."
<a >16</a>
<a class="a1" >下一頁</a>
</div>
這個頁面列表在class="pages"的div標簽中,當前頁是用span標簽裝飾的,我們可以通過提取 下一頁 按鈕的鏈接來繼續下載下一個頁面的圖片,但是我們怎么知道什么時候會到最后一頁呢?點擊最后一個頁面的按鈕,這里就是16頁。再次檢查這一部分的html代碼
<div id="pages">
<a class="a1" >上一頁</a>
<a >1</a>
".."
<a >7</a>
<a >8</a>
<a >9</a>
<a >10</a>
<a >11</a>
<a >12</a>
<a >13</a>
<a >14</a>
<a >15</a>
<span>16</span>
<a class="a1" >下一頁</a>
</div>
從這段代碼中可以看到,下一頁 按鈕的鏈接指向的是16頁,也就是當前頁,而前面的頁面指向的都是當前頁的下一頁。所以我們可以利用這一點來判斷是否到最后一頁。這樣我們就有了下載一個完整圖片集的思路了。
下面我們看看如何獲得所有圖片集的鏈接
發現網站首頁有一個圖集分類,我們可以認為他把網站上所有的資源都放在這里分好類了,隨便點開一個分類,可以看到里面有排列整齊的圖集,檢查html代碼
<div class="boxs">
<ul class="img">
<li>
<a target="_blank">
![[尤蜜薈] 可樂Vicky 蘇梅島旅拍 第二刊 ~[43]](http://upload-images.jianshu.io/upload_images/2475481-1915763ae66ee8ad.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
</a>
<p><span>3</span>圖片: 43 張(1600X2400)</p>
<p>機構:
<a target="_blank" class="tags">推女神</a>
</p>
<p>模特:
<a target="_blank" class="tags">可樂Vicky</a>
</p>
<p>標簽:
<a target="_blank" class="tags">極品</a>
<a target="_blank" class="tags">女神</a>
<a target="_blank" class="tags">清新</a>
<a target="_blank" class="tags">清純</a>
<a target="_blank" class="tags">唯美</a>
<a target="_blank" class="tags">戶外</a>
<a target="_blank" class="tags">養眼</a>
</p>
<p class="p_title">
<a target="_blank">[尤蜜薈] 可樂Vicky 蘇梅島旅拍 第二刊 ~</a>
</p>
</li>
"..."
<!--為了方便查看,這里省略了一堆li標簽-->
</ul>
</div>
從這段代碼中可以發現,所有的圖集被放在了<div class="boxs">
標簽中,每個圖集的信息中包含模特名字,發行機構和一系列的標簽。每一個信息對應一個鏈接,鏈接中是包含對應信息的圖集的分類頁面,這里我們按照人名分類,所以只要檢索 模特 關鍵字就可以了。
這段頁面的下方也是一個頁面列表,檢查html元素會發現與圖集的列表模式相同。
另外,對于某些只有一個圖集的人來說,他沒有對應的分類頁面,對于這些人要另外處理
小結
根據這些特征,我們遍歷分類頁面中的所有圖集,通過字典記錄人名對應的鏈接,如果遇到沒有分類頁面的人,則直接創建文件夾,下載圖集。這樣我們前期的分析工作就完成了,下面
開始寫爬蟲吧
先初始化幾個要用到的全局變量
categaries = {} # 分類列表
person = {} # 人名列表
PATH = os.getcwd() # 根目錄路徑
forbidchar = r'<|>|/|\\|\||:|"|\*|\?' # 系統禁止用作文件名的字符,正則表達式
一、圖片下載函數
首先,我們要下載網站上所有的圖片,所以需要有一個給定圖片鏈接就能下載下來的函數:
def downloadimg(link, name): # link為圖片鏈接,name為圖片名字
data = urlopen(link, timeout=10) # 打開連接
tname = name+".jpg" # 給圖片命名
with open(tname, "ab") as code: # 以追加二進制模式打開文件,并保存數據
code.write(data.read())
print(tname+" is done.") # 打印提示文字
但這還不夠,因為經常會碰到鏈接沒有響應的情況,所以加上異常處理
def downloadimg(link, name):
name = re.split(forbidchar, name)
name = '.'.join(name) # 通過re模塊的split,將windows不支持的文件名符號,全部換成'.'
for i in range(10):
time.sleep(0.5) # 照顧別人服務器的帶寬,適當加點延時。。。加多少看你心情
try:
data = urlopen(link, timeout=10)
tname = name+".jpg"
with open(tname, "ab") as code:
code.write(data.read())
print(tname+" is done.")
break
except Exception:
time.sleep(3) # 多數情況下,上面的語句中只有urlopen會出現無響應的異常,這時等待三秒,重新發送請求
二、圖集下載函數
更進一步的,我們要處理一個給定鏈接的圖集,首先我們寫一個下載當前頁面的主要圖片的功能
def downloaditem(link, ):
html = urlopen(link, timeout=100) # 打開鏈接
bsObj = BeautifulSoup(html, "html.parser") # 用bs解析html
for center in bsObj.findAll("center"): # 找到所有的center標簽
for img in center.findAll("img"): # 找到其中包含img標簽的
boola = downloadimg(img.attrs['src'], img.attrs['alt'])
# 下載image,并以圖片的alt屬性內容給圖片命名
但這還沒完,記得前面提到的頁面列表么,我們還要繼續下載 下一頁 的圖片。于是繼續
def downloaditem(link, ):
html = urlopen(link, timeout=100)
bsObj = BeautifulSoup(html, "html.parser")
for center in bsObj.findAll("center"):
for img in center.findAll("img"):
boola = downloadimg(img.attrs['src'], img.attrs['alt'])
#---------------------------------------------------------------------------
page = bsObj.find("div", {"id":"pages"}) # 找到所有id屬性為pages的div標簽
for a in page.findAll("a", {"class":"a1"}):
# 找到其中class屬性為a1的a標簽
if re.search(re.compile(r'下一頁'), a.getText()):
# 如果標簽內容包含下一頁
number = re.search(re.compile(r"http://www\.meitulu\.com/.*?_([0-9]*?)\.html"), a.attrs['href'])
#用正則表達式匹配鏈接中的頁碼
if number: #如果匹配成功,失敗時number為None
link = number.group(0) #提取頁面鏈接
number = number.group(1) #提取頁碼
if number != page.find('span').getText():
#如果鏈接的頁碼跟當前頁碼不同,則不是最后一頁,
print("download deeper...")#輸出提示信息
downloaditem(link) #繼續下載下一頁
完善一下代碼,添加異常捕捉和延時
def downloaditem(link, ):
for i in range(10):
time.sleep(1)
try:
html = urlopen(link, timeout=100)
break
except Exception:
print("Url Erroe")
time.sleep(2)
for i in range(10):
try:
bsObj = BeautifulSoup(html, "html.parser")
break
except Exception:
print("Soup Error")
for center in bsObj.findAll("center"):
for img in center.findAll("img"):
boola = downloadimg(img.attrs['src'], img.attrs['alt'])
time.sleep(2)
page = bsObj.find("div", {"id":"pages"})
for a in page.findAll("a", {"class":"a1"}):
if re.search(re.compile(r'下一頁'), a.getText()):
number = re.search(re.compile(r"http://www\.meitulu\.com/.*?_([0-9]*?)\.html"), a.attrs['href'])
if number:
link = number.group(0)
number = number.group(1)
if number != page.find('span').getText():
print("download deeper...")
downloaditem(link)
三、獲取人名分類下的所有圖集鏈接
def downloadperson(link, name):
name = re.split(forbidchar, name)
name = '.'.join(name) # 跟圖片文件名原理一樣,替換被禁止的字符
personitems = {}
if not os.path.exists(name): # 檢查這個人的文件夾之前有沒有創建
os.mkdir(name) # 如果沒有就創建一個
os.chdir(name) # 進入這個目錄
html = urlopen(link, timeout=100) # 打開鏈接
bsObj = BeautifulSoup(html, "html.parser") # 用bs解析
for boxs in bsObj.findAll("div", {"class":"boxs"}): # 找到裝載圖片集的<div>標簽
for li in boxs.findAll("li"): # 處理每一個圖片集
for p in li.findAll('p', {"class":"p_title"}): # 找到包含圖片鏈接的p標簽
psn = p.find('a')
personitems[psn.getText()] = psn.attrs['href'] # 用文件名作為key給字典添加圖集鏈接
PATHtmp = os.getcwd() # PATHtmp是這一層人名文件夾的路徑
for key in personitems: # 遍歷字典,下載每一個圖集
print('\n', "downloading ", key, '\n')
if not os.path.exists(key): # 檢驗文件夾是否存在
os.mkdir(key)
os.chdir(key) # 進入文件夾
downloaditem(personitems[key]) # 下載圖集
os.chdir(PATHtmp) # 回到上一層目錄,這里用的絕對路徑,避免中途被打斷導致后面的下載也出現錯誤
os.chdir(PATH) # 回到根目錄
完善代碼,添加異常捕捉和延時,這里因為同一個人沒有發現有多頁的情況,所以沒有處理頁面列表的代碼
def downloadperson(link, name):
name = re.split(forbidchar, name)
name = '.'.join(name)
personitems = {}
if not os.path.exists(name):
os.mkdir(name)
os.chdir(name)
for i in range(10):
time.sleep(1)
try:
html = urlopen(link, timeout=100)
break
except Exception:
time.sleep(2)
print("Url Erroe")
for i in range(10):
try:
bsObj = BeautifulSoup(html, "html.parser")
break
except Exception:
print("Soup Error")
for boxs in bsObj.findAll("div", {"class":"boxs"}):
for li in boxs.findAll("li"):
try:
for p in li.findAll('p', {"class":"p_title"}):
print('\n',p,'\n')
psn = p.find('a')
personitems[psn.getText()] = psn.attrs['href']
except:
print("Find Error")
PATHtmp = os.getcwd()
for key in personitems:
print('\n', "downloading ", key, '\n')
if not os.path.exists(key):
os.mkdir(key)
os.chdir(key)
downloaditem(personitems[key])
os.chdir(PATHtmp)
os.chdir(PATH)
四、獲得分類下所有人名的分類鏈接
def getperson(link,):
for i in range(10):
time.sleep(1)
try:
html = urlopen(link, timeout=100) # 打開連接
break
except Exception:
time.sleep(2)
print("Url Erroe")
for i in range(10):
try:
bsObj = BeautifulSoup(html, "html.parser") # bs解析
break
except Exception:
print("Soup Error")
for boxs in bsObj.findAll("div", {"class":"boxs"}): # 獲取分類下包含圖集的標簽
for li in boxs.findAll("li"): # 逐個圖集處理
try:
for a in li.findAll('p'):
print(a.getText()) # 輸出圖集提示信息
name = re.search(re.compile(r'^模特:(.*?)$'), a.getText())
if name:
psn = a.find('a') # 嘗試查找人名分類頁面鏈接
person[psn.getText()] = psn.attrs['href']
except: # 如果找不到分類頁面,則直接下載圖集
print("downloading item..."+name.group(1))
item = li.find('p', {"class":"p_title"}).find("a")
print(item.getText())
if not os.path.exists(name.group(1)):
os.mkdir(name.group(1)) # 創建人名文件夾
os.chdir(name.group(1)) # 進入人名文件夾
print(name.group(1))
name = item.getText() # 提取圖集
name = re.split(forbidchar, name) # 處理圖集名(文件夾名)
name = '.'.join(name)
if not os.path.exists(name):
os.mkdir(name) # 創建圖集文件夾
os.chdir(name) # 進入圖集文件夾
downloaditem(item.attrs['href']) # 下載圖集
os.chdir(PATH) # 回到根目錄
time.sleep(3) # 延時
page = bsObj.find("div", {"id":"pages"}) # 處理下一頁問題,原理同downloaditem函數
for a in page.findAll("a", {"class":"a1"}):
if re.search(re.compile(r'下一頁'), a.getText()):
number = re.search(re.compile(r"http://www\.meitulu\.com/t/.*?([0-9]*?)\.html"), a.attrs['href'])
link = number.group(0)
number = number.group(1)
if number != page.find('span').getText():
print("scrap deeper...")
getperson(link)
break
五、主函數
if __name__ == "__main__":
for i in range(10):
time.sleep(1)
try:
html = urlopen("http://www.meitulu.com", timeout=100) # 打開首頁鏈接
break
except Exception:
print("Url Erroe")
time.sleep(2)
for i in range(10):
try:
bsObj = BeautifulSoup(html, "html.parser") # bs解析
break
except Exception:
print("Soup Error")
for a in bsObj.find("li", {"id":"tag"}).find("ul", {"id":"tag_ul"}).findAll("a"):
categaries[a.getText()] = a.attrs['href'] # 獲取所有分類首頁的鏈接,以分類名為key
for key in categaries:
time.sleep(3)
print(i,"loading page..."+key)
getperson(categaries[key]) # 獲取每一個分類下的所有人名鏈接
for key in person:
downloadperson(person[key], key) # 下載每一個人名下的所有圖集
總結
完整代碼在這里:<a >simplespider.py</a>
我在代碼中延時加的比較多,所以運行起來有些慢,但畢竟這只是個練習,照顧一下別人服務器比較好= =。