對於具有大量數據的爬蟲任務,單進程/線程就會顯得捉襟見肘,爬取速度會比較慢,如果需要加快速度,就需要選擇多線程/協程 進行處理;如果反爬蟲中有對js代碼進行加密的時候,一般的爬蟲手段都會失效,那麼解決的辦法有一種就是,直接調用Selenium測試框架控制瀏覽器進行代碼自動發送請求,對返回的真實頁面的數據進行解析;在爬蟲過程中,如果有驗證碼圖片的時候,對於一般的黑白清晰字碼,可以使用tesseract模塊進行識別。
多任務進行爬蟲
- 多任務爬蟲的目標就是更快的將數據爬下來,對比單線程爬取和多任務(多線程,協程爬取的時間)
示例對比:爬取豆瓣電影top250
單線程爬蟲
代碼:
# -*- coding:utf-8 -*-
import requests
import json, time
from lxml import etree
class Spider_Douban(object):
def __init__(self):
self.base_url = 'https://movie.douban.com/top250?filter=&start='
self.headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'}
self.data_list = []
# 發送請求
def send_request(self, url):
data = requests.get(url, headers=self.headers).content
return data
# 數據解析
def parse_data(self, data):
html = etree.HTML(data)
data_list = html.xpath('//*[@id="content"]/div/div[1]/ol/li/div/div[2]/div[1]/a/span[1]/text()')
for data in data_list:
self.data_list.append(data)
# 保存數據到本地
def save_data(self):
data_json = json.dumps(self.data_list)
with open('03_spider_douban.json', 'w') as f:
f.write(data_json)
# 主邏輯
def main(self):
import time
start_time = time.time()
for page in range(0, 225 + 1, 25):
url = self.base_url + str(page)
# 發送數據
data = self.send_request(url)
# 數據解析
self.parse_data(data)
print '正在爬第--%d--頁' % ((page / 25) + 1)
# 保存數據
self.save_data()
end_time = time.time()
time = end_time - start_time
print '單線程所需時間爲:%s' % time
# 單線程所需時間爲:2.55781912804
if __name__ == '__main__':
spider_douban = Spider_Douban()
spider_douban.main()
多線程
步驟
- 導入模塊:import threading
- 將任務加入到異步線程中:threading.Thread(target=self.change_value)
- 開始線程:t1.start()
注意點:將子線程加入到主線程中:t1.join()
代碼
# -*- coding:utf-8 -*-
import threading
import time
class Test_Threads(object):
def change_value(self):
global a
a = 200
time.sleep(5)
print '這是修改a的子線程1,a = %d'%a
def read_value(self):
print '這是讀取a的子線程2,a = %d'%a
def run(self,):
t1 = threading.Thread(target=self.change_value)
t1.start()
t1.join()
threading.Thread(target=self.read_value).start()
print '這是主線程'
if __name__ == '__main__':
a = 100
test_thread= Test_Threads()
test_thread.run()
線程池thread_pool的使用
- 使用線程池可以固定線程的個數,在加快任務處理速度的同時,避免過度消耗系統資源
步驟
- 導入: from multiprocessing.dummy import Pool
- 創建線程池:thread_pool = Pool(len(url_list))
- 給線程池添加任務:thread_pool.map(self.send_request, url_list)
- 關閉線程池任務的添加:thread_pool.close()
- 將線程池中的任務隊列加入到主線程中,等待所有隊列任務執行結束後,再繼續執行下面代碼:thread_pool.join()
代碼:
# -*- coding:utf-8 -*-
import requests
import json
from multiprocessing.dummy import Pool
from lxml import etree
class Spider_Douban(object):
def __init__(self):
self.base_url = 'https://movie.douban.com/top250?filter=&start='
self.headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'
}
self.data_list = []
# 發送請求,解析數據
def send_request(self, url):
data = requests.get(url, headers=self.headers).content
self.parse_data(data)
def parse_data(self, data):
html = etree.HTML(data)
data_list = html.xpath('//*[@id="content"]/div/div[1]/ol/li/div/div[2]/div[1]/a/span[1]/text()')
for data in data_list:
self.data_list.append(data)
# 保存數據到本地
def save_data(self):
data_json = json.dumps(self.data_list)
with open('03_spider_douban.json', 'w') as f:
f.write(data_json)
# 主邏輯
def main(self):
import time
start_time = time.time()
url_list = []
for page in range(0, 225 + 1, 25):
url = self.base_url + str(page)
url_list.append(url)
# 使用線程池處理
# 1.創建線程池
thread_pool = Pool(len(url_list))
# 2.給線程池添加任務
thread_pool.map(self.send_request, url_list)
# 3.關閉線程池
thread_pool.close()
# 4.將所有線程join到主線程中
thread_pool.join()
# 保存數據
self.save_data()
end_time = time.time()
time = end_time - start_time
print '線程池所需時間爲:%s' % time
# 線程池所需時間爲:2.41724801064
if __name__ == '__main__':
spider_douban = Spider_Douban()
spider_douban.main()
協程
步驟
- 導入模塊:import gevent
- 導入設置自動跳轉的monkey模塊並且使用:from gevent import monkey; monkey.patch_all()
- 添加任務到協程中:gevent = gevent.spawn(self.send_request, url)
- 讓主線程等待所有協程執行完畢(join):gevent.joinall(gevent_list)
代碼
# !/usr/bin/env python
# _*_ coding:utf-8 _*_
import requests
from lxml import etree
import time
import gevent
from gevent import monkey
monkey.patch_all()
class Douban_Spider(object):
def __init__(self):
self.base_url = 'https://movie.douban.com/top250?filter=&start='
self.headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"}
self.count = 0
# 請求數據
def send_request(self, url):
time.sleep(1)
try:
data = requests.get(url, headers=self.headers).content
self.analysis_data(data)
except Exception, err:
print err
# 解析數據
def analysis_data(self, data):
# 1.轉換類型
html_data = etree.HTML(data)
# 2.解析 -->list
data_list = html_data.xpath('//*[@id="content"]/div/div[1]/ol/li/div/div[2]/div[1]/a/span[1]/text()')
for name in data_list:
print name
self.count += 1
# 調度的方法
def start_work(self):
# 開始時間
start_time = time.time()
gevent_list = []
for page in range(0, 225 + 1, 25):
url = self.base_url + str(page)
print url
# 1.創建協程
gevent = gevent.spawn(self.send_request, url)
gevent_list.append(gevent)
# 2. 讓主線程等待, 協程執行完畢
gevent.joinall(gevent_list)
end_time = time.time()
print end_time - start_time
print self.count
if __name__ == '__main__':
tool = Douban_Spider()
tool.start_work()
Selenium + PhantomJS
對於那些使用了ajax請求和DHTML技術的網頁,傳統的直接請求url方式就行不通了,因爲那樣只能請求到沒有被執行js代碼前的頁面,即不完整的頁面,解決這個問題的辦法,有兩個:
- 直接從js代碼裏採集內容,這種方式,費時費力,不好搞
- 使用第三方的庫,採集執行過js代碼之後的,直接在瀏覽器裏看到的頁面內容
Selenium就是這樣一個庫,它是一個自動化測試工具,也可以用於爬蟲開發,它可以自動化控制瀏覽器(常規瀏覽器,谷歌,火狐,甚至無界面瀏覽器,PhantomJS),利用selenium控制瀏覽器請求網頁,返回響應,然後抓取響應的網頁內容進行解析,就能解決,使用了ajax,DHTML技術,不能直接請求url提取信息的問題。
Phantoms是一種無界面的瀏覽器,因爲不需要加載界面信息,所以請求加載頁面,返回結果速度比有界面的瀏覽器要快速的多。
使用步驟:
- 導入模塊:from selenium import webdriver
- 創建瀏覽器對象:driver = webdriver.PhantomJS() # 這裏使用的是無界面的PhantomJS瀏覽器
- 使用瀏覽器對象driver進行像正常用戶一樣的操作:
- 請求數據:driver.get(url)
- 獲取頁面數據:driver.page_source
- 獲取頁面按鈕對象:button = driver.find_element_by_xpath()(也可以使用by_class, by_id等方式)
- 點擊按鈕 :button.click()
- 在輸入框輸入內容:element.send_keys(u’…’)(注意輸入的內容要是Unicode的編碼,所以要加上u”)
- 獲取當前頁面:driver.window_handles
- 切換頁面:driver.switch_to_window(driver.window_handles[1])
- 獲取cookies: driver.get_cookies()
- 獲取當前頁面的url:driver.current_url
- 保存快照:driver.save_screenshot(‘文件名’)
- 關閉瀏覽器:driver.quit()
- 關閉頁面:driver.close()
完整使用步驟代碼:
# -*- coding:utf-8 -*-
from selenium import webdriver
import time
def base_use_selenium():
url = 'https://www.so.com/'
# 1.創建瀏覽器對象
driver = webdriver.PhantomJS()
# 2.請求數據
driver.get(url)
# 4.獲取數據
data = driver.page_source # 格式爲unicode,如需保存需要:data.encode('utf-8')
# 5.點擊新聞按鈕
# 獲取按鈕對象
button = driver.find_element_by_xpath('//*[@id="bd_tabnav"]/nav/a[2]')
# 點擊按鈕
button.click()
# 6.在輸入框中輸入內容
# 獲取輸入框對象
element = driver.find_element_by_id('haosou-input')
# 輸入內容
element.send_keys(u'中興')
# 7.點擊搜索按鈕(放大鏡)進行
driver.find_element_by_xpath('//*[@id="search-form"]//div/button').click()
# 8.獲取當前的頁面
print driver.window_handles
# 9.切換頁面
driver.switch_to_window(driver.window_handles[1])
# 10.獲取cookies
cookies = driver.get_cookies()
# 11.獲取當前頁面的url
current_url = driver.current_url
# 3.保存快照
driver.save_screenshot('so.png')
# 12.關閉瀏覽器
driver.quit()
if __name__ == '__main__':
base_use_selenium()
示例:使用selenium-PhantomJS登錄到豆瓣首頁
代碼:
-*- coding:utf-8 -*-
from selenium import webdriver
import time
def login_douban():
# 1.登錄的網址
url = 'http://www.douban.com/accounts/login?source=movie'
# 2.創建瀏覽器對象
driver = webdriver.PhantomJS()
# 3.請求url
driver.get(url)
# 4.輸入用戶名
driver.find_element_by_id('email').send_keys(u'[email protected]')
# 5.輸入密碼
driver.find_element_by_id('password').send_keys(u'ALARMCHIME')
# 6.點擊登錄按鈕,看是否出現驗證碼
driver.find_element_by_name('login').click()
time.sleep(2) # 等待頁面加載完成
# 保存快照,查看驗證碼,手動輸入,此處若使用第三方SDK(某雲,極驗)可以將驗證碼圖片當參數傳給第三方接口
# 第三方平臺處理好後,返回結果,直接輸入結果即可,這裏不調用,所以要手動輸入
driver.save_screenshot('veri_code.png')
code = raw_input('請輸入驗證碼')
# 7.輸入驗證碼
driver.find_element_by_id('captcha_field').send_keys(code)
# 8.點擊登錄按鈕
driver.find_element_by_name('login').click()
driver.save_screenshot('douban.png')
if __name__ == '__main__':
login_douban()
Tesseract-OCR
- 在爬蟲過程中,難免會遇到驗證碼卡住爬蟲進程的情況。在這種情況下怎麼處理呢?
- 直接使用第三方平臺的驗證碼識別sdk,如極驗,某雲,要錢
- 對於簡單的驗證碼,可以使用成熟的工具包,如Tesseract
使用步驟;
- 先把驗證碼圖片下載下來,清理乾淨
- 然後使用Tesseract處理圖片,處理完的結果使用selenium創建的driver,使用send_keys()方法輸入,獲取確認按鈕,然後button.click()進入,主頁
示例:識別豆瓣驗證碼
# -*- coding:utf-8 -*-
from selenium import webdriver
import time
def login_douban():
# 1.登錄的網址
url = 'http://www.douban.com/accounts/login?source=movie'
# 2.創建瀏覽器對象
driver = webdriver.PhantomJS()
# 3.請求url
driver.get(url)
# 4.輸入用戶名
driver.find_element_by_id('email').send_keys(u'[email protected]')
# 5.輸入密碼
driver.find_element_by_id('password').send_keys(u'ALARMCHIME')
# 6.點擊登錄按鈕,看是否出現驗證碼
driver.find_element_by_name('login').click()
time.sleep(2) # 等待頁面加載完成
# 保存快照,查看驗證碼,手動輸入,此處若使用第三方SDK(某雲,極驗)可以將驗證碼圖片當參數傳給第三方接口
# 第三方平臺處理好後,返回結果,直接輸入結果即可,這裏不調用,所以要手動輸入
driver.save_screenshot('veri_code.png')
code = raw_input('請輸入驗證碼')
# 7.輸入驗證碼
driver.find_element_by_id('captcha_field').send_keys(code)
# 8.點擊登錄按鈕
driver.find_element_by_name('login').click()
driver.save_screenshot('douban.png')
if __name__ == '__main__':
login_douban()