前言
從今天開始就要擼起袖子,直接寫Python爬蟲了,學習語言最好的辦法就是有目的的進行,所以,接下來我將用10+篇的部落格,寫爬圖片
這一件事情。希望可以做好。
為了寫好爬蟲,我們需要準備一個火狐瀏覽器,還需要準備抓包工具,抓包工具,我使用的是CentOS自帶的tcpdump,加上wireshark ,這兩款軟體的安裝和使用,建議你還是學習一下,後面我們應該會用到。
網路請求模組requests
Python中的大量開源的模組使得編碼變的特別簡單,我們寫爬蟲第一個要了解的模組就是requests。
安裝requests
開啟終端:使用命令
pip3 install requests
等待安裝完畢即可使用
接下來在終端中鍵入如下命令
# mkdir demo
# cd demo
# touch down.py
複製程式碼
上面的linux命令是 建立一個名稱為demo
的資料夾,之後建立一個down.py
檔案,你也可以使用GUI工具,像操作windows一樣,右鍵建立各種檔案。
為了提高在linux上的開發效率,我們需要安裝一個visual studio code
的開發工具
對於怎麼安裝vscode,參考官方的https://code.visualstudio.com/docs/setup/linux 有詳細的說明。
對於centos則如下:
sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
sudo sh -c 'echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\nenabled=1\ngpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" > /etc/yum.repos.d/vscode.repo'
複製程式碼
然後用yum命令安裝
yum check-update
sudo yum install code
複製程式碼
安裝成功之後,在你的CentOS中會出現如下畫面
接著說我們上面的操作 ,因為我們這邊是用gnome圖形介面,所以後面的有些操作,我直接用windows的操作風格講解了
開啟軟體>檔案>開啟檔案>找到我們剛剛建立的down.py
檔案
之後,在VSCODE裡面輸入
import requests #匯入模組
def run(): #宣告一個run方法
print("跑碼檔案") #列印內容
if __name__ == "__main__": #主程式入口
run() #呼叫上面的run方法
複製程式碼
tips:本教程不是Python3的基礎入門課,所以有些編碼基礎,預設你懂,比如Python沒有分號結尾,需要對齊格式。我會盡量把註釋寫的完整
按鍵盤上的ctrl+s
儲存檔案,如果提示許可權不足,那麼按照提示輸入密碼即可
通過終端進入demo目錄,然後輸入
python3 down.py
顯示如下結果,代表編譯沒有問題
[root@bogon demo]# python3 down.py
跑碼檔案
複製程式碼
接下來,我們開始測試requests
模組是否可以使用
修改上述程式碼中的
import requests
def run():
response = requests.get("http://www.baidu.com")
print(response.text)
if __name__ == "__main__":
run()
複製程式碼
執行結果(出現下圖代表你執行成功了):
接下來,我們實際下載一張圖片試試,比如下面這張圖片
修改程式碼,在這之前,我們修改一些內容
由於每次修改檔案,都提示必須管理員許可權,所以你可以使用linux命令修改許可權。
[root@bogon linuxboy]# chmod -R 777 demo/
import requests
def run():
response = requests.get("http://www.newsimg.cn/big201710leaderreports/xibdj20171030.jpg")
with open("xijinping.jpg","wb") as f :
f.write(response.content)
f.close
if __name__ == "__main__":
run()
複製程式碼
執行程式碼之後,發現在資料夾內部生成了一個檔案
但是開啟檔案之後發現,這個檔案並不能查閱,這代表這個檔案壓根沒有下載下來
我們繼續修改程式碼,因為有的伺服器圖片,都做了一些限制,我們可以用瀏覽器開啟,但是使用Python程式碼並不能完整的下載下來。
修改程式碼
import requests
def run():
# 標頭檔案,header是字典型別
headers = {
"Host":"www.newsimg.cn",
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.5383.400 QQBrowser/10.0.1313.400"
}
response = requests.get("http://www.newsimg.cn/big201710leaderreports/xibdj20171030.jpg",headers=headers)
with open("xijinping.jpg","wb") as f :
f.write(response.content)
f.close
if __name__ == "__main__":
run()
複製程式碼
好了,這次在終端編譯一下python檔案
python3 down.py
發現圖片下載下來了
我們重點檢視上述程式碼中 requests.get
部分,新增了一個headers
的實參。這樣我們程式就下載下來了完整的圖片。
Python爬蟲頁面分析
有了上面這個簡單的案例,我們接下來的操作就變的簡單多了。爬蟲是如何進行的呢?
輸入域名->下載原始碼->分析圖片路徑->下載圖片
上面就是他的步驟
輸入域名
我們今天要爬的網站叫做 www.meizitu.com/a/pure.html
為啥爬取這個網站,因為好爬。
好了,接下來分析這個頁面
做爬蟲很重要的一點,就是你要找到分頁的地方,因為有分頁代表著有規律,有規律,我們就好爬了(可以做的更智慧一些,輸入首頁網址,爬蟲自己就能分析到這個網站中的所有地址)
上面圖片中,我們發現了分頁,那麼找規律吧
使用火狐瀏覽器的開發者工具,發現分頁規律
http://www.meizitu.com/a/pure_1.html
http://www.meizitu.com/a/pure_2.html
http://www.meizitu.com/a/pure_3.html
http://www.meizitu.com/a/pure_4.html
複製程式碼
好了,接下來用Python實現這部分(以下寫法有部分物件導向的寫法,沒有基礎的同學,請百度找些基礎來看,不過對於想學習的你來說,這些簡單極了)
import requests
all_urls = [] #我們拼接好的圖片集和列表路徑
class Spider():
#建構函式,初始化資料使用
def __init__(self,target_url,headers):
self.target_url = target_url
self.headers = headers
#獲取所有的想要抓取的URL
def getUrls(self,start_page,page_num):
global all_urls
#迴圈得到URL
for i in range(start_page,page_num+1):
url = self.target_url % i
all_urls.append(url)
if __name__ == "__main__":
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0',
'HOST':'www.meizitu.com'
}
target_url = 'http://www.meizitu.com/a/pure_%d.html' #圖片集和列表規則
spider = Spider(target_url,headers)
spider.getUrls(1,16)
print(all_urls)
複製程式碼
上面的程式碼,可能需要有一定的Python基礎可以看懂,不過你其實仔細看一下,就幾個要點
第一個是 class Spider():
我們宣告瞭一個類,然後我們使用 def __init__
去宣告一個建構函式,這些我覺得你找個教程30分鐘也就學會了。
拼接URL,我們可以用很多辦法,我這裡用的是最直接的,字串拼接。
注意上述程式碼中有一個全域性的變數 all_urls
我用它來儲存我們的所有分頁的URL
接下來,是爬蟲最核心的部分程式碼了
我們需要分析頁面中的邏輯。首先開啟 www.meizitu.com/a/pure_1.ht… ,右鍵審查元素。
發現上圖紅色框框裡面的連結
點選圖片之後,發現進入一個圖片詳情頁面,發現竟然是一組圖片,那麼現在的問題是
我們要解決第一步,需要在 www.meizitu.com/a/pure_1.ht… 這種頁面中爬取所有的 www.meizitu.com/a/5585.html 這種地址
這裡我們採用多執行緒的方式爬取(這裡還用了一種設計模式,叫觀察者模式)
import threading #多執行緒模組
import re #正規表示式模組
import time #時間模組
複製程式碼
首先引入三個模組,分別是多執行緒,正規表示式,時間模組
新增加一個全域性的變數,並且由於是多執行緒操作,我們需要引入執行緒鎖
all_img_urls = [] #圖片列表頁面的陣列
g_lock = threading.Lock() #初始化一個鎖
複製程式碼
宣告一個生產者的類,用來不斷的獲取圖片詳情頁地址,然後新增到 all_img_urls
這個全域性變數中
#生產者,負責從每個頁面提取圖片列表連結
class Producer(threading.Thread):
def run(self):
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0',
'HOST':'www.meizitu.com'
}
global all_urls
while len(all_urls) > 0 :
g_lock.acquire() #在訪問all_urls的時候,需要使用鎖機制
page_url = all_urls.pop() #通過pop方法移除最後一個元素,並且返回該值
g_lock.release() #使用完成之後及時把鎖給釋放,方便其他執行緒使用
try:
print("分析"+page_url)
response = requests.get(page_url , headers = headers,timeout=3)
all_pic_link = re.findall('<a target=\'_blank\' href="(.*?)">',response.text,re.S)
global all_img_urls
g_lock.acquire() #這裡還有一個鎖
all_img_urls += all_pic_link #這個地方注意陣列的拼接,沒有用append直接用的+=也算是python的一個新語法吧
print(all_img_urls)
g_lock.release() #釋放鎖
time.sleep(0.5)
except:
pass
複製程式碼
上述程式碼用到了繼承的概念,我從threading.Thread中繼承了一個子類,繼承的基礎學習,你可以去翻翻 www.runoob.com/python3/pyt… 菜鳥教程就行。
執行緒鎖,在上面的程式碼中,當我們操作all_urls.pop()
的時候,我們是不希望其他執行緒對他進行同時操作的,否則會出現意外,所以我們使用g_lock.acquire()
鎖定資源,然後使用完成之後,記住一定要立馬釋放g_lock.release()
,否則這個資源就一直被佔用著,程式無法進行下去了。
匹配網頁中的URL,我使用的是正規表示式,後面我們會使用其他的辦法,進行匹配。
re.findall()
方法是獲取所有匹配到的內容,正規表示式,你可以找一個30分鐘入門的教程,看看就行。
程式碼容易出錯的地方,我放到了
try: except: 裡面,當然,你也可以自定義錯誤。
如果上面的程式碼,都沒有問題,那麼我們就可以在程式入口的地方編寫
for x in range(2):
t = Producer()
t.start()
複製程式碼
執行程式,因為我們的Producer繼承自threading.Thread類,所以,你必須要實現的一個方法是 def run
這個我相信在上面的程式碼中,你已經看到了。然後我們可以執行啦~~~
執行結果:
這樣,圖片詳情頁面的列表就已經被我們儲存起來了。
接下來,我們需要執行這樣一步操作,我想要等待圖片詳情頁面全部獲取完畢,在進行接下來的分析操作。
這裡增加程式碼
#threads= []
#開啟兩個執行緒去訪問
for x in range(2):
t = Producer()
t.start()
#threads.append(t)
# for tt in threads:
# tt.join()
print("進行到我這裡了")
複製程式碼
註釋關鍵程式碼,執行如下
[linuxboy@bogon demo]$ python3 down.py
分析http://www.meizitu.com/a/pure_2.html
分析http://www.meizitu.com/a/pure_1.html
進行到我這裡了
['http://www.meizitu.com/a/5585.html',
複製程式碼
把上面的tt.join等程式碼註釋開啟
[linuxboy@bogon demo]$ python3 down.py
分析http://www.meizitu.com/a/pure_2.html
分析http://www.meizitu.com/a/pure_1.html
['http://www.meizitu.com/a/5429.html', ......
進行到我這裡了
複製程式碼
發現一個本質的區別,就是,我們由於是多執行緒的程式,所以,當程式跑起來之後,print("進行到我這裡了")
不會等到其他執行緒結束,就會執行到,但是當我們改造成上面的程式碼之後,也就是加入了關鍵的程式碼 tt.join()
那麼主執行緒的程式碼會等到所以子執行緒執行完畢之後,在接著向下執行。這就滿足了,我剛才說的,先獲取到所有的圖片詳情頁面的集合,這一條件了。
join所完成的工作就是執行緒同步,即主執行緒遇到join之後進入阻塞狀態,一直等待其他的子執行緒執行結束之後,主執行緒在繼續執行。這個大家在以後可能經常會碰到。
下面編寫一個消費者/觀察者,也就是不斷關注剛才我們獲取的那些圖片詳情頁面的陣列。
新增一個全域性變數,用來儲存獲取到的圖片連結
pic_links = [] #圖片地址列表
複製程式碼
#消費者
class Consumer(threading.Thread) :
def run(self):
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0',
'HOST':'www.meizitu.com'
}
global all_img_urls #呼叫全域性的圖片詳情頁面的陣列
print("%s is running " % threading.current_thread)
while len(all_img_urls) >0 :
g_lock.acquire()
img_url = all_img_urls.pop()
g_lock.release()
try:
response = requests.get(img_url , headers = headers )
response.encoding='gb2312' #由於我們呼叫的頁面編碼是GB2312,所以需要設定一下編碼
title = re.search('<title>(.*?) | 妹子圖</title>',response.text).group(1)
all_pic_src = re.findall('<img alt=.*?src="(.*?)" /><br />',response.text,re.S)
pic_dict = {title:all_pic_src} #python字典
global pic_links
g_lock.acquire()
pic_links.append(pic_dict) #字典陣列
print(title+" 獲取成功")
g_lock.release()
except:
pass
time.sleep(0.5)
複製程式碼
看到沒有,上面的程式碼其實和我們剛才寫的極其相似,後面,我會在github上面把這部分程式碼修改的更加簡潔一些,不過這才是第二課,後面我們的路長著呢。
程式碼中比較重要的一些部分,我已經使用註釋寫好了,大家可以直接參考。大家一定要注意我上面使用了兩個正規表示式,分別用來匹配title和圖片的url這個title是為了後面建立不同的資料夾使用的,所以大家注意吧。
#開啟10個執行緒去獲取連結
for x in range(10):
ta = Consumer()
ta.start()
複製程式碼
執行結果:
[linuxboy@bogon demo]$ python3 down.py
分析http://www.meizitu.com/a/pure_2.html
分析http://www.meizitu.com/a/pure_1.html
['http://www.meizitu.com/a/5585.html', ......
<function current_thread at 0x7f7caef851e0> is running
<function current_thread at 0x7f7caef851e0> is running
<function current_thread at 0x7f7caef851e0> is running
<function current_thread at 0x7f7caef851e0> is running
<function current_thread at 0x7f7caef851e0> is running
<function current_thread at 0x7f7caef851e0> is running
<function current_thread at 0x7f7caef851e0> is running
<function current_thread at 0x7f7caef851e0> is running
進行到我這裡了
<function current_thread at 0x7f7caef851e0> is running
<function current_thread at 0x7f7caef851e0> is running
清純美如畫,攝影師的御用麻豆 獲取成功
宅男女神葉梓萱近日拍攝一組火爆寫真 獲取成功
美(bao)胸(ru)女王帶來制服誘惑 獲取成功
每天睜開眼看到美好的你,便是幸福 獲取成功
可愛女孩,願暖風呵護純真和執著 獲取成功
清純妹子如一縷陽光溫暖這個冬天 獲取成功 .....
複製程式碼
是不是感覺距離成功有進了一大步
接下來就是,我們開篇提到的那個儲存圖片的操作了,還是同樣的步驟,寫一個自定義的類
class DownPic(threading.Thread) :
def run(self):
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0',
'HOST':'mm.chinasareview.com'
}
while True: # 這個地方寫成死迴圈,為的是不斷監控圖片連結陣列是否更新
global pic_links
# 上鎖
g_lock.acquire()
if len(pic_links) == 0: #如果沒有圖片了,就解鎖
# 不管什麼情況,都要釋放鎖
g_lock.release()
continue
else:
pic = pic_links.pop()
g_lock.release()
# 遍歷字典列表
for key,values in pic.items():
path=key.rstrip("\\")
is_exists=os.path.exists(path)
# 判斷結果
if not is_exists:
# 如果不存在則建立目錄
# 建立目錄操作函式
os.makedirs(path)
print (path+'目錄建立成功')
else:
# 如果目錄存在則不建立,並提示目錄已存在
print(path+' 目錄已存在')
for pic in values :
filename = path+"/"+pic.split('/')[-1]
if os.path.exists(filename):
continue
else:
response = requests.get(pic,headers=headers)
with open(filename,'wb') as f :
f.write(response.content)
f.close
複製程式碼
我們獲取圖片連結之後,就需要下載了,我上面的程式碼是首先建立了一個之前獲取到title
的檔案目錄,然後在目錄裡面通過下面的程式碼,去建立一個檔案。
涉及到檔案操作,引入一個新的模組
import os #目錄操作模組
複製程式碼
# 遍歷字典列表
for key,values in pic.items():
path=key.rstrip("\\")
is_exists=os.path.exists(path)
# 判斷結果
if not is_exists:
# 如果不存在則建立目錄
# 建立目錄操作函式
os.makedirs(path)
print (path+'目錄建立成功')
else:
# 如果目錄存在則不建立,並提示目錄已存在
print(path+' 目錄已存在')
for pic in values :
filename = path+"/"+pic.split('/')[-1]
if os.path.exists(filename):
continue
else:
response = requests.get(pic,headers=headers)
with open(filename,'wb') as f :
f.write(response.content)
f.close
複製程式碼
因為我們的圖片連結陣列,裡面存放是的字典格式,也就是下面這種格式
[{"妹子圖1":["http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/01.jpg","http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/02.jpg"."http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/03.jpg"]},{"妹子圖2":["http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/01.jpg","http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/02.jpg"."http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/03.jpg"]},{"妹子圖3":["http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/01.jpg","http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/02.jpg"."http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/03.jpg"]}]
複製程式碼
需要先迴圈第一層,獲取title,建立目錄之後,在迴圈第二層去下載圖片,程式碼中,我們在修改一下,把異常處理新增上。
try:
response = requests.get(pic,headers=headers)
with open(filename,'wb') as f :
f.write(response.content)
f.close
except Exception as e:
print(e)
pass
複製程式碼
然後在主程式中編寫程式碼
#開啟10個執行緒儲存圖片
for x in range(10):
down = DownPic()
down.start()
複製程式碼
執行結果:
[linuxboy@bogon demo]$ python3 down.py
分析http://www.meizitu.com/a/pure_2.html
分析http://www.meizitu.com/a/pure_1.html
['http://www.meizitu.com/a/5585.html', 'http://www.meizitu.com/a/5577.html', 'http://www.meizitu.com/a/5576.html', 'http://www.meizitu.com/a/5574.html', 'http://www.meizitu.com/a/5569.html', .......
<function current_thread at 0x7fa5121f2268> is running
<function current_thread at 0x7fa5121f2268> is running
<function current_thread at 0x7fa5121f2268> is running
進行到我這裡了
清純妹子如一縷陽光溫暖這個冬天 獲取成功
清純妹子如一縷陽光溫暖這個冬天目錄建立成功
可愛女孩,願暖風呵護純真和執著 獲取成功
可愛女孩,願暖風呵護純真和執著目錄建立成功
超美,純純的你與藍藍的天相得益彰 獲取成功
超美,純純的你與藍藍的天相得益彰目錄建立成功
美麗凍人,雪地裡的跆拳道少女 獲取成功
五官精緻的美眉,彷彿童話裡的公主 獲取成功
有自信迷人的笑容,每天都是燦爛的 獲取成功
五官精緻的美眉,彷彿童話裡的公主目錄建立成功
有自信迷人的笑容,每天都是燦爛的目錄建立成功
清純美如畫,攝影師的御用麻豆 獲取成功
複製程式碼
檔案目錄下面同時出現
點開一個目錄
好了,今天的一個簡單的爬蟲成了
最後我們在程式碼的頭部寫上
# -*- coding: UTF-8 -*-
複製程式碼
防止出現 Non-ASCII character 'xe5' in file
報錯問題。
最後放上github程式碼地址: