多線程爬取表情包
有一個網(wǎng)站,叫做“斗圖啦”,網(wǎng)址是:https://www.doutula.com/。這里面包含了許許多多的有意思的斗圖圖片,還蠻好玩的。有時候為了斗圖要跑到這個上面來找表情,實在有點費勁。于是就產(chǎn)生了一個邪惡的想法,可以寫個爬蟲,把所有的表情都給爬下來。這個網(wǎng)站對于爬蟲來講算是比較友好了,他不會限制你的headers,不會限制你的訪問頻率(當(dāng)然,作為一個有素質(zhì)的爬蟲工程師,爬完趕緊撤,不要把人家服務(wù)器搞垮了),不會限制你的IP地址,因此技術(shù)難度不算太高。但是有一個問題,因為這里要爬的是圖片,而不是文本信息,所以采用傳統(tǒng)的爬蟲是可以完成我們的需求,但是因為是下載圖片所以速度比較慢,可能要爬一兩個小時都說不準(zhǔn)。因此這里我們準(zhǔn)備采用多線程爬蟲,一下可以把爬蟲的效率提高好幾倍。
一、分析網(wǎng)站和爬蟲準(zhǔn)備工作:
構(gòu)建所有頁面URL列表:
這里我們要爬的頁面不是“斗圖啦”首頁,而是最新表情頁面https://www.doutula.com/photo/list/,這個頁面包含了所有的表情圖片,只是是按照時間來排序的而已。我們把頁面滾動到最下面,可以看到這個最新表情使用的是分頁,當(dāng)我們點擊第二頁的時候,頁面的URL變成了https://www.doutula.com/photo/list/?page=2,而我們再回到第一頁的時候,page又變成了1,所以這個翻頁的URL其實很簡單,前面這一串https://www.doutula.com/photo/list/?page=都是固定的,只是后面跟的數(shù)字不一樣而已。并且我們可以看到,這個最新表情總共是有869頁,因此這里我們可以寫個非常簡單的代碼,來構(gòu)建一個從1到869的頁面的URL列表:
# 全局變量,用來保存頁面的URL的
PAGE_URL_LIST = []
BASE_PAGE_URL = 'https://www.doutula.com/photo/list/?page='
for x in range(1, 870):
url = BASE_PAGE_URL + str(x)
PAGE_URL_LIST.append(url)
獲取一個頁面中所有的表情圖片鏈接:
我們已經(jīng)拿到了所有頁面的鏈接,但是還沒有拿到每個頁面中表情的鏈接。經(jīng)過分析,我們可以知道,其實每個頁面中表情的HTML元素構(gòu)成都是一樣的,因此我們只需要針對一個頁面進行分析,其他頁面按照同樣的規(guī)則,就可以拿到所有頁面的表情鏈接了。這里我們以第一頁為例,跟大家講解。首先在頁面中右鍵->檢查->Elements,然后點擊Elements最左邊的那個小光標(biāo),再把鼠標(biāo)放在隨意一個表情上,這樣下面的代碼就定位到這個表情所在的代碼位置了:

可以看到,這個
img標(biāo)簽的class是等于img-responsive lazy image_dtz,然后我們再定位其他表情的img標(biāo)簽,發(fā)現(xiàn)所有的表情的img標(biāo)簽,他的class都是img-responsive lazy image_dtz:

因此我們只要把數(shù)據(jù)從網(wǎng)上拉下來,然后再根據(jù)這個規(guī)則進行提取就可以了。這里我們使用了兩個第三方庫,一個是requests,這個庫是專門用來做網(wǎng)絡(luò)請求的。第二個庫是bs4,這個庫是專門用來把請求下來的數(shù)據(jù)進行分析和過濾用的,如果沒有安裝好這兩個庫的,可以使用以下代碼進行安裝(我使用的是python2.7的版本):
# 安裝requests
pip install requests
# 安裝bs4
pip install bs4
# 安裝lxml解析引擎
pip install lxml
然后我們以第一個頁面為例,跟大家講解如何從頁面中獲取所有表情的鏈接:
# 導(dǎo)入requests庫
import requests
# 從bs4中導(dǎo)入BeautifulSoup
from bs4 import BeautifulSoup
# 第一頁的鏈接
url = 'https://www.doutula.com/photo/list/?page=1'
# 請求這個鏈接
response = requests.get(url)
# 使用返回的數(shù)據(jù),構(gòu)建一個BeautifulSoup對象
soup = BeautifulSoup(response.content,'lxml')
# 獲取所有class='img-responsive lazy image_dtz'的img標(biāo)簽
img_list = soup.find_all('img', attrs={'class': 'img-responsive lazy image_dta'})
for img in img_list:
# 因為src屬性剛開始獲取的是loading的圖片,因此使用data-original比較靠譜
print img['data-original']
這樣我們就可以在控制臺看到本頁中所有的表情圖片的鏈接就全部都打印出來了。
下載圖片:
有圖片鏈接后,還要對圖片進行下載處理,這里我們以一張圖片為例:http://ws2.sinaimg.cn/bmiddle/9150e4e5ly1fhpi3ysfocj205i04aa9z.jpg,來看看Python中如何輕輕松松下載一張圖片:
import urllib
url = 'http://ws2.sinaimg.cn/bmiddle/9150e4e5ly1fhpi3ysfocj205i04aa9z.jpg'
urllib.urlretrieve(url,filename='test.jpg')
這樣就可以下載一張圖片了。
結(jié)合以上三部分內(nèi)容:
以上三部分,分別對,如何構(gòu)建所有頁面的URL,一個頁面中如何獲取所有表情的鏈接以及下載圖片的方法。接下來把這三部分結(jié)合在一起,就可以構(gòu)建一個完整但效率不高的爬蟲了:
# 導(dǎo)入requests庫
import requests
# 從bs4中導(dǎo)入BeautifulSoup
from bs4 import BeautifulSoup
import urllib
import os
# 全局變量,用來保存頁面的URL的
PAGE_URL_LIST = []
BASE_PAGE_URL = 'https://www.doutula.com/photo/list/?page='
for x in range(1, 870):
url = BASE_PAGE_URL + str(x)
PAGE_URL_LIST.append(url)
for page_url in PAGE_URL_LIST:
# 請求這個鏈接
response = requests.get(page_url)
# 使用返回的數(shù)據(jù),構(gòu)建一個BeautifulSoup對象
soup = BeautifulSoup(response.content,'lxml')
# 獲取所有class='img-responsive lazy image_dtz'的img標(biāo)簽
img_list = soup.find_all('img', attrs={'class': 'img-responsive lazy image_dta'})
for img in img_list:
# 因為src屬性剛開始獲取的是loading的圖片,因此使用data-original比較靠譜
src = img['data-original']
# 有些圖片是沒有http的,那么要加一個http
if not src.startswith('http'):
src = 'http:'+ src
# 獲取圖片的名稱
filename = src.split('/').pop()
# 拼接完整的路徑
path = os.path.join('images',filename)
urllib.urlretrieve(src,path)
以上這份代碼??梢酝暾倪\行了。但是效率不高,畢竟是在下載圖片,要一個個排隊下載。如果能夠采用多線程,在一張圖片下載的時候,就完全可以去請求其他圖片,而不用繼續(xù)等待了。因此效率比較高,以下將該例子改為多線程來實現(xiàn)。
二、多線程下載圖片:
這里多線程我們使用的是Python自帶的threading模塊。并且我們使用了一種叫做生產(chǎn)者和消費者的模式,生產(chǎn)者專門用來從每個頁面中獲取表情的下載鏈接存儲到一個全局列表中。而消費者專門從這個全局列表中提取表情鏈接進行下載。并且需要注意的是,在多線程中使用全局變量要用鎖來保證數(shù)據(jù)的一致性。以下是多線程的爬蟲代碼(如果有看不懂的,可以看視頻,講解很仔細):
#encoding: utf-8
import urllib
import threading
from bs4 import BeautifulSoup
import requests
import os
import time
# 表情鏈接列表
FACE_URL_LIST = []
# 頁面鏈接列表
PAGE_URL_LIST = []
# 構(gòu)建869個頁面的鏈接
BASE_PAGE_URL = 'https://www.doutula.com/photo/list/?page='
for x in range(1, 870):
url = BASE_PAGE_URL + str(x)
PAGE_URL_LIST.append(url)
# 初始化鎖
gLock = threading.Lock()
# 生產(chǎn)者,負責(zé)從每個頁面中提取表情的url
class Producer(threading.Thread):
def run(self):
while len(PAGE_URL_LIST) > 0:
# 在訪問PAGE_URL_LIST的時候,要使用鎖機制
gLock.acquire()
page_url = PAGE_URL_LIST.pop()
# 使用完后要及時把鎖給釋放,方便其他線程使用
gLock.release()
response = requests.get(page_url)
soup = BeautifulSoup(response.content, 'lxml')
img_list = soup.find_all('img', attrs={'class': 'img-responsive lazy image_dta'})
gLock.acquire()
for img in img_list:
src = img['data-original']
if not src.startswith('http'):
src = 'http:'+ src
# 把提取到的表情url,添加到FACE_URL_LIST中
FACE_URL_LIST.append(src)
gLock.release()
time.sleep(0.5)
# 消費者,負責(zé)從FACE_URL_LIST提取表情鏈接,然后下載
class Consumer(threading.Thread):
def run(self):
print '%s is running' % threading.current_thread
while True:
# 上鎖
gLock.acquire()
if len(FACE_URL_LIST) == 0:
# 不管什么情況,都要釋放鎖
gLock.release()
continue
else:
# 從FACE_URL_LIST中提取數(shù)據(jù)
face_url = FACE_URL_LIST.pop()
gLock.release()
filename = face_url.split('/')[-1]
path = os.path.join('images', filename)
urllib.urlretrieve(face_url, filename=path)
if __name__ == '__main__':
# 2個生產(chǎn)者線程,去從頁面中爬取表情鏈接
for x in range(2):
Producer().start()
# 5個消費者線程,去從FACE_URL_LIST中提取下載鏈接,然后下載
for x in range(5):
Consumer().start()
寫在最后:
本教程采用多線程來完成表情的爬取,可以讓爬取效率高出很多倍。Python的多線程雖然有GIL全局解釋器鎖,但在網(wǎng)絡(luò)IO處理這一塊表現(xiàn)還是很好的,不用在一個地方一直等待。以上這個例子就很好的說明了多線程的好處。另外,如果想精通爬蟲技術(shù),建議學(xué)習(xí)這個課程,學(xué)習(xí)完后可以成長很多:21天搞定Python分布式爬蟲