day068 多任務爬蟲&Selenium+PhantomJS&Tesseract-OCR

對於具有大量數據的爬蟲任務,單進程/線程就會顯得捉襟見肘,爬取速度會比較慢,如果需要加快速度,就需要選擇多線程/協程 進行處理;如果反爬蟲中有對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()

多線程

步驟

  1. 導入模塊:import threading
  2. 將任務加入到異步線程中:threading.Thread(target=self.change_value)
  3. 開始線程: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的使用

  • 使用線程池可以固定線程的個數,在加快任務處理速度的同時,避免過度消耗系統資源

步驟

  1. 導入: from multiprocessing.dummy import Pool
  2. 創建線程池:thread_pool = Pool(len(url_list))
  3. 給線程池添加任務:thread_pool.map(self.send_request, url_list)
  4. 關閉線程池任務的添加:thread_pool.close()
  5. 將線程池中的任務隊列加入到主線程中,等待所有隊列任務執行結束後,再繼續執行下面代碼: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()

協程

步驟

  1. 導入模塊:import gevent
  2. 導入設置自動跳轉的monkey模塊並且使用:from gevent import monkey; monkey.patch_all()
  3. 添加任務到協程中:gevent = gevent.spawn(self.send_request, url)
  4. 讓主線程等待所有協程執行完畢(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代碼前的頁面,即不完整的頁面,解決這個問題的辦法,有兩個:

    1. 直接從js代碼裏採集內容,這種方式,費時費力,不好搞
    2. 使用第三方的庫,採集執行過js代碼之後的,直接在瀏覽器裏看到的頁面內容
  • Selenium就是這樣一個庫,它是一個自動化測試工具,也可以用於爬蟲開發,它可以自動化控制瀏覽器(常規瀏覽器,谷歌,火狐,甚至無界面瀏覽器,PhantomJS),利用selenium控制瀏覽器請求網頁,返回響應,然後抓取響應的網頁內容進行解析,就能解決,使用了ajax,DHTML技術,不能直接請求url提取信息的問題。

  • Phantoms是一種無界面的瀏覽器,因爲不需要加載界面信息,所以請求加載頁面,返回結果速度比有界面的瀏覽器要快速的多。

使用步驟:

  1. 導入模塊:from selenium import webdriver
  2. 創建瀏覽器對象:driver = webdriver.PhantomJS() # 這裏使用的是無界面的PhantomJS瀏覽器
  3. 使用瀏覽器對象driver進行像正常用戶一樣的操作:
    1. 請求數據:driver.get(url)
    2. 獲取頁面數據:driver.page_source
    3. 獲取頁面按鈕對象:button = driver.find_element_by_xpath()(也可以使用by_class, by_id等方式)
    4. 點擊按鈕 :button.click()
    5. 在輸入框輸入內容:element.send_keys(u’…’)(注意輸入的內容要是Unicode的編碼,所以要加上u”)
    6. 獲取當前頁面:driver.window_handles
    7. 切換頁面:driver.switch_to_window(driver.window_handles[1])
    8. 獲取cookies: driver.get_cookies()
    9. 獲取當前頁面的url:driver.current_url
    10. 保存快照:driver.save_screenshot(‘文件名’)
    11. 關閉瀏覽器:driver.quit()
    12. 關閉頁面: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

  • 在爬蟲過程中,難免會遇到驗證碼卡住爬蟲進程的情況。在這種情況下怎麼處理呢?
    1. 直接使用第三方平臺的驗證碼識別sdk,如極驗,某雲,要錢
    2. 對於簡單的驗證碼,可以使用成熟的工具包,如Tesseract

使用步驟;

  1. 先把驗證碼圖片下載下來,清理乾淨
  2. 然後使用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()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章