1. 程式人生 > >python爬蟲入門(五)Selenium模擬使用者操作

python爬蟲入門(五)Selenium模擬使用者操作

爬蟲(Spider),反爬蟲(Anti-Spider),反反爬蟲(Anti-Anti-Spider) 之間恢巨集壯闊的鬥爭...

  • 小莫想要某站上所有的電影,寫了標準的爬蟲(基於HttpClient庫),不斷地遍歷某站的電影列表頁面,根據 Html 分析電影名字存進自己的資料庫。

  • 這個站點的運維小黎發現某個時間段請求量陡增,分析日誌發現都是 IP(xxx.xxx.xxx.xxx)這個使用者,並且 user-agent 還是 Python-urllib/2.7 ,基於這兩點判斷非人類後直接在伺服器上封殺。

  • 小莫電影只爬了一半,於是也針對性的變換了下策略:1. user-agent 模仿百度("Baiduspider..."),2. IP每爬半個小時就換一個IP代理。

  • 小黎也發現了對應的變化,於是在伺服器上設定了一個頻率限制,每分鐘超過120次請求的再遮蔽IP。 同時考慮到百度家的爬蟲有可能會被誤傷,想想市場部門每月幾十萬的投放,於是寫了個指令碼,通過 hostname 檢查下這個 ip 是不是真的百度家的,對這些 ip 設定一個白名單。

  • 小莫發現了新的限制後,想著我也不急著要這些資料,留給伺服器慢慢爬吧,於是修改了程式碼,隨機1-3秒爬一次,爬10次休息10秒,每天只在8-12,18-20點爬,隔幾天還休息一下。

  • 小黎看著新的日誌頭都大了,再設定規則不小心會誤傷真實使用者,於是準備換了一個思路,當3個小時的總請求超過50次的時候彈出一個驗證碼彈框,沒有正確輸入的話就把 IP 記錄進黑名單。

  • 小莫看到驗證碼有些傻臉了,不過也不是沒有辦法,先去學習了影象識別(關鍵詞 PIL,tesseract),再對驗證碼進行了二值化,分詞,模式訓練之後,總之最後識別了小黎的驗證碼(關於驗證碼,驗證碼的識別,驗證碼的反識別也是一個恢弘壯麗的鬥爭史...),之後爬蟲又跑了起來。

  • 小黎是個不折不撓的好同學,看到驗證碼被攻破後,和開發同學商量了變化下開發模式,資料並不再直接渲染,而是由前端同學非同步獲取,並且通過 JavaScript 的加密庫生成動態的 token,同時加密庫再進行混淆。

  • 混淆過的加密庫就沒有辦法了麼?當然不是,可以慢慢除錯,找到加密原理,不過小莫不準備用這麼耗時耗力的方法,他放棄了基於 HttpClient的爬蟲,選擇了內建瀏覽器引擎的爬蟲(關鍵詞:PhantomJS,Selenium),在瀏覽器引擎執行頁面,直接獲取了正確的結果,又一次拿到了對方的資料。

  • 小黎:.....

Selenium

 Selenium是一個Web的自動化測試工具,最初是為網站自動化測試而開發的,型別像我們玩遊戲用的按鍵精靈,可以按指定的命令自動操作,不同是Selenium 可以直接執行在瀏覽器上,它支援所有主流的瀏覽器(包括PhantomJS這些無介面的瀏覽器)。

Selenium 可以根據我們的指令,讓瀏覽器自動載入頁面,獲取需要的資料,甚至頁面截圖,或者判斷網站上某些動作是否發生。

Selenium 自己不帶瀏覽器,不支援瀏覽器的功能,它需要與第三方瀏覽器結合在一起才能使用.

下載selenium webdriver ‘geckodriver.exe’,下載好後放到python目錄裡面

firefox的目錄也要新增到環境變數中

Selenium 庫裡有個叫 WebDriver 的 API。WebDriver 有點兒像可以載入網站的瀏覽器,但是它也可以像 BeautifulSoup 或者其他 Selector 物件一樣用來查詢頁面元素,與頁面上的元素進行互動 (傳送文字、點選等),以及執行其他動作來執行網路爬蟲。

selenium快速入門

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from selenium import webdriver

# 要想呼叫鍵盤按鍵操作需要引入keys包
from selenium.webdriver.common.keys import Keys

#建立瀏覽器物件
driver = webdriver.Firefox()

driver.get("http://www.baidu.com")

#列印頁面標題“百度一下你就知道”
print driver.title

#生成當前頁面快照
driver.save_screenshot("baidu.png")

# id="kw"是百度搜索框,輸入字串“微博”,跳轉到搜尋中國頁面
driver.find_element_by_id("kw").send_keys(u"微博")

# id="su"是百度搜索按鈕,click() 是模擬點選
driver.find_element_by_id("su").click()

# 獲取新的頁面快照
driver.save_screenshot(u"微博.png")

# 列印網頁渲染後的原始碼
print driver.page_source

# 獲取當前頁面Cookie
print driver.get_cookies()

# ctrl+a 全選輸入框內容
driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'a')

# ctrl+x 剪下輸入框內容
driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'x')

# 輸入框重新輸入內容
driver.find_element_by_id("kw").send_keys("test")

# 模擬Enter回車鍵
driver.find_element_by_id("su").send_keys(Keys.RETURN)

# 清除輸入框內容
driver.find_element_by_id("kw").clear()

# 生成新的頁面快照
driver.save_screenshot("test.png")

# 獲取當前url
print driver.current_url

# 關閉當前頁面,如果只有一個頁面,會關閉瀏覽器
# driver.close()

# 關閉瀏覽器
driver.quit()

1.頁面操作

假如有下面的輸入框

<input type="text" name="user-name" id="passwd-id" />

尋找方法

# 獲取id標籤值
element = driver.find_element_by_id("passwd-id")
# 獲取name標籤值
element = driver.find_element_by_name("user-name")
# 獲取標籤名值
element = driver.find_elements_by_tag_name("input")
# 也可以通過XPath來匹配
element = driver.find_element_by_xpath("//input[@id='passwd-id']")

2.定位元素的方法

find_element_by_id
find_elements_by_name
find_elements_by_xpath
find_elements_by_link_text
find_elements_by_partial_link_text
find_elements_by_tag_name
find_elements_by_class_name
find_elements_by_css_selector

3.滑鼠動作

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from selenium import webdriver

# 要想呼叫鍵盤按鍵操作需要引入keys包
from selenium.webdriver.common.keys import Keys
from selenium.webdriver import ActionChains

#建立瀏覽器物件
driver = webdriver.Firefox()

driver.get("http://www.baidu.com")

#滑鼠移動到某處
action1 = driver.find_element_by_id("su")
ActionChains(driver).move_to_element(action1).perform()

#滑鼠移動到某處單擊
action2 = driver.find_element_by_id("su")
ActionChains(driver).move_to_element(action2).click(action2).perform()

#滑鼠移動到某處雙擊
action3 = driver.find_element_by_id("su")
ActionChains(driver).move_to_element(action3).double_click(action3).perform()

# 滑鼠移動到某處右擊
action4 = driver.find_element_by_id("su")
ActionChains(driver).move_to_element(action4).context_click(action4).perform()

4.Select表單

遇到下來框需要選擇操作時,Selenium專門提供了Select類來處理下拉框

# 匯入 Select 類
from selenium.webdriver.support.ui import Select

# 找到 name 的選項卡
select = Select(driver.find_element_by_name('status'))

# 
select.select_by_index(1)
select.select_by_value("0")
select.select_by_visible_text(u"xxx")

以上是三種選擇下拉框的方式,它可以根據索引來選擇,可以根據值來選擇,可以根據文字來選擇。注意:

  • index 索引從 0 開始
  • value是option標籤的一個屬性值,並不是顯示在下拉框中的值
  • visible_text是在option標籤文字的值,是顯示在下拉框的值

全部取消方法

select.deselect_all()

5.彈窗處理

當頁面出現了彈窗提示

alert = driver.switch_to_alert()

6.頁面切換

一個瀏覽器肯定會有很多視窗,所以我們肯定要有方法來實現視窗的切換。切換視窗的方法如下:

driver.switch_to.window("this is window name")

7.頁面前進和後退

操作頁面的前進和後退功能:

driver.forward()     #前進
driver.back()        # 後退

例項 模擬登陸douban網站

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time

driver = webdriver.Firefox()
driver.get("http://www.douban.com")

# 輸入賬號密碼
driver.find_element_by_name("form_email").send_keys("158xxxxxxxx")
driver.find_element_by_name("form_password").send_keys("zhxxxxxxxx")

# 模擬點選登入
driver.find_element_by_xpath("//input[@class='bn-submit']").click()

# 等待3秒
time.sleep(3)

# 生成登陸後快照
driver.save_screenshot(u"douban.png")

driver.quit()

 動態頁面模擬點選--->>>爬取鬥魚所有房間名,觀眾人數

(1)首先分析‘’下一頁‘’的class變化,如果不是最後一頁的時候,‘下一頁’的class如下

 

(2)如果到了最後一頁,‘下一頁’變為隱藏,點選不了,class變為如下

(3)找到個房間的名字和觀眾人數的class

(4)程式碼

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import unittest
from selenium import webdriver
from bs4 import BeautifulSoup as bs

class douyu(unittest.TestCase):
    # 初始化方法,必須是setUp()
    def setUp(self):
        self.driver = webdriver.Firefox()
        self.num = 0
        self.count = 0

    # 測試方法必須有test字樣開頭
    def testDouyu(self):
        self.driver.get("https://www.douyu.com/directory/all")

        while True:
            soup = bs(self.driver.page_source, "lxml")
            # 房間名, 返回列表
            names = soup.find_all("h3", {"class" : "ellipsis"})
            # 觀眾人數, 返回列表
            numbers = soup.find_all("span", {"class" :"dy-num fr"})

            # zip(names, numbers) 將name和number這兩個列表合併為一個元組 : [(1, 2), (3, 4)...]
            for name, number in zip(names, numbers):
                print u"觀眾人數: -" + number.get_text().strip() + u"-\t房間名: " + name.get_text().strip()
                self.num += 1
                #self.count += int(number.get_text().strip())

            # 如果在頁面原始碼裡找到"下一頁"為隱藏的標籤,就退出迴圈
            if self.driver.page_source.find("shark-pager-disable-next") != -1:
                    break

            # 一直點選下一頁
            self.driver.find_element_by_class_name("shark-pager-next").click()

    # 測試結束執行的方法
    def tearDown(self):
        # 退出Firefox()瀏覽器
        print "當前網站直播人數" + str(self.num)
        print "當前網站觀眾人數" + str(self.count)
        self.driver.quit()

if __name__ == "__main__":
    # 啟動測試模組
    unittest.main()

爬取的結果: