天天看點

python爬蟲系列第四次筆記之動态渲染頁面爬取1Selenium的使用

為了解決異步渲染網頁,我們直接模拟浏覽器運作的方式來實作,這樣就可以左到在浏覽器中看到什麼樣,抓取的源碼就是什麼樣,也就是可見即可爬。這樣我們就可以不用管網頁内部的javascript用了什麼算法渲染頁面,也就是所謂的js加密,也不用管網頁背景的Ajax接口有哪些參數。

1Selenium的使用

在使用selenium之前,需要安裝Driver檔案,有ChromeDriver(适用chrome),GeckoDriver(适用firefox),PhantomJS。如何下載下傳到csdn上直接搜尋就有,需要注意的是除了PhantomJS以外,另外兩個需要和你本機安裝的浏覽器版本相近(不要求完全一樣,但是必須是相近的版本),這是必須要注意的。下載下傳完成後,把exe檔案複制粘貼到你安裝的python檔案中bin檔案(或者DOC)同級目錄下。

1.1selenium的基本用法

from selenium import webdriver
import time
driver = webdriver.Chrome()
start_url = "https://www.baidu.com"
driver.get(start_url)
time.sleep(5)
driver.close()
driver.quit()
           

如果運作上面的代碼之後,會彈出一個chrome浏覽器頁面,然後過5秒後退出 ,就說明你的運作檔案版本以及安裝位置是正确的,當然,這裡可以換成Firefox().

那麼我們還可以做一些什麼事呢?

#coding=utf-8
from selenium import webdriver
import time
driver = webdriver.Chrome()
start_url = "https://www.baidu.com"
driver.get(start_url)
driver.find_element_by_id("kw").send_keys("長城")
driver.find_element_by_id("su").click()
time.sleep(5)
driver.quit()
           

我們這裡根據id定位元素,定位一個位置并給予它一個參數,然後下面使用click()方法實行點選一個按鈕。實際上這裡是給我們的百度輸入框中輸入長城這個值,然後點選“百度”按鈕。最後過5秒鐘,關閉頁面。

那麼繼續

from selenium import webdriver
import time
driver = webdriver.Chrome()
start_url = "https://www.baidu.com"
driver.get(start_url)
driver.find_element_by_id("kw").send_keys("長城")
driver.find_element_by_id("su").click()
time.sleep(5)
print(driver.page_source.encode('GBK','ignore').decode('GBK'))
driver.quit()
           

這裡使用了編碼解碼的操作,為什麼要這麼做,是因為網頁本身是utf-8,但我們爬取網頁時用的是Unicode,但使用print()列印時,裡面的内容應該是GBK編碼,那麼就需要我們将需要列印的資料後加上 .encode(‘GBK’,‘ignore’).decode(‘GBk’)第一個GBK是忽略掉非法字元,然後再譯碼。不然會報編碼錯誤。這裡會列印出網頁的源碼。

小結:

  1. selenium的導包:

    from selenium import webdriver

  2. selenium建立driver對象:

    webdriver.PhantomJS()

  3. selenium請求資料:

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

  4. selenium檢視資料:

    driver.page_source

  5. 關閉無界面浏覽器:

    driver.quit()

  6. 根據id定位元素:

    driver.find_element_by_id(“kw”)

  7. 操作點選事件:

    click()

  8. 給輸入框指派:

    send_keys()

1.2selenium元素定位的方法

1.2.1定位方法

find_element_by_id (傳回一個元素)
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_element

find_elements

的差別

by_link_text

by_partial_link_tex

的差別:全部文本和包含某個文本

這些解析方法在之前都有介紹,這裡就不一一贅述,我們用一個例子來介紹這些方法。

from selenium import webdriver

  driver = webdriver.Chrome()

  driver.get("https://www.douban.com/")

  ret1 = driver.find_element_by_id("anony-nav")
  print(ret1)
  # 輸出為:<selenium.webdriver.remote.webelement.WebElement (session="ea6f94544ac3a56585b2638d352e97f3", element="0.5335773935305805-1")>

  ret2 = driver.find_elements_by_id("anony-nav")
  print(ret2)
  #輸出為:[<selenium.webdriver.remote.webelement.WebElement (session="ea6f94544ac3a56585b2638d352e97f3", element="0.5335773935305805-1")>]

  ret3 = driver.find_elements_by_xpath("//*[@id='anony-nav']/h1/a")
  print(len(ret3))
   #輸出為:1

  ret4 = driver.find_elements_by_tag_name("h1")
  print(len(ret4))
   #輸出為:1

  ret5 = driver.find_elements_by_link_text("下載下傳豆瓣 App")
  print(len(ret5))
   #輸出為:1

  ret6 = driver.find_elements_by_partial_link_text("豆瓣")
  print(len(ret6))
   #輸出為:20

  driver.close()
           

這裡調用的方法并不複雜,隻是需要去網頁中找到你想要的元素的id,或者class,或者xpath方法,需要注意的是xpath路徑是可以直接在網頁中右鍵點選elements中的元素,copy–》xpath,來獲得。

還有css元素定位

from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
driver.find_element_by_class_name('s_ipt').send_keys('3浪還有')
# driver.find_element_by_css_selector('#su').click()
driver.find_element_by_class_name('btn self-btn bg s_btn').click()
#其他css方法
#  通過id屬性(css屬性)
driver.find_element_by_css_selector("#kw").send_keys("python")
#  通過class屬性定位(css屬性)
driver.find_element_by_css_selector(".s_ipt").send_keys("python")
#  通過标簽定位(css屬性)
driver.find_element_by_css_selector("input").send_keys("python")
#  通過name屬性定位(其他屬性)
driver.find_element_by_css_selector("[name='wd']").send_keys("python")
#  通過autocomplete屬性定位(其他屬性)
driver.find_element_by_css_selector("[autocomplete='off']").send_keys("python")
#  通過type屬性定位(其他屬性)
driver.find_element_by_css_selector("[type='text']").send_keys("python")
#  css也可以通過标簽與屬性的組合來定位元素
driver.find_element_by_css_selector("input.s_ipt").send_keys("python")
driver.find_element_by_css_selector("input#kw").send_keys("python")
driver.find_element_by_css_selector("input[id='kw']").send_keys("python")
#  css層級關系
driver.find_element_by_css_selector("form#form>span>input").send_keys("python")
driver.find_element_by_css_selector("form.fm>span>inout").send_keys("python")
#  css邏輯運算
driver.find_element_by_css_selector("input[id='kw'][name='wd']").send_keys("python")
           

那麼我們獲得,或者說定位到了元素後我們需要怎麼辦呢?

1.2.2解析手段

在第一章介紹了賦予參數方法,和點選方法。那麼還有什麼方法?

find_element僅僅能夠擷取元素,不能夠直接擷取其中的資料,如果需要擷取資料需要使用以下方法:

  • 擷取文本:

    element.text

  • 擷取屬性值:

    element.get_attribute("href")

    示例:
#coding=gbk
from selenium import webdriver

driver =webdriver.Chrome()

driver.get("https://www.douban.com/")

ret4 = driver.find_elements_by_tag_name("h1")
print(ret4[0].text)
#輸出:豆瓣

ret5 = driver.find_elements_by_link_text("下載下傳豆瓣 App")
print(ret5[0].get_attribute("href"))
#輸出:https://www.douban.com/doubanapp/app?channel=nimingye

driver.close()
           

這裡給出了傳回txt内容和href,是不是和上一章的解析方法有些類似。

小結

  1. 根據xpath定位元素:

    driver.find_elements_by_xpath("//*[@id='s']/h1/a")

  2. 根據class定位元素:

    driver.find_elements_by_class_name("box")

  3. 根據link_text定位元素:

    driver.find_elements_by_link_text("下載下傳豆瓣 App")

  4. 根據tag_name定位元素:

    driver.find_elements_by_tag_name("h1")

  5. 擷取文本内容:

    element.text

  6. 擷取标簽屬性:

    element.get_attribute("href")

1.2.3其他方法

selenium 處理cookie

通過

driver.get_cookies()

能夠擷取所有的cookie

# 把cookie轉化為字典
{cookie[‘name’]: cookie[‘value’] for cookie in driver.get_cookies()}

#删除一條cookie
driver.delete_cookie("CookieName")
# 删除所有的cookie
driver.delete_all_cookies()
           

頁面等待

  • 為什麼需要等待

    如果網站采用了動态html技術,那麼頁面上的部分元素出現時間便不能确定,這個時候就可以設定一個等待時間,強制要求在時間内出現,否則報錯

  • 頁面等待的方法

    time.sleep(10)

switch方法切換的操作

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

也可以使用 window_handles 方法來擷取每個視窗的操作對象。例如:


    # 1. 擷取目前所有的視窗
    current_windows = driver.window_handles

    # 2. 根據視窗索引進行切換
    driver.switch_to.window(current_windows[1])
           

iframe是html中常用的一種技術,即一個頁面中嵌套了另一個網頁,selenium預設是通路不了frame中的内容的,對應的解決思路是

driver.switch_to.frame()
           

在使用selenium登入qq郵箱的過程中,我們會發現,無法在郵箱的登入input标簽中輸入内容,通過觀察源碼可以發現,form表單在一個frame中,是以需要切換到frame中。這個方法使用的很多。

當你觸發了某個事件之後,頁面出現了彈窗提示,處理這個提示或者擷取提示資訊方法如下:

alert = driver.switch_to_alert()
           

頁面前進和後退

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

selenium的優缺點

  • selenium能夠執行頁面上的js,對于js渲染的資料和模拟登陸處理起來非常容易
  • selenium由于在擷取頁面的過程中會發送很多請求,是以效率非常低,是以在很多時候需要酌情使用

小結

  1. 擷取cookie:

    get_cookies()

  2. 删除cookie:

    delete_all_cookies()

  3. 切換視窗:

    switch_to.window()

  4. 切換iframe:

    switch_to.frame()

1.4執行個體說明

在給出例子之前,我們要想到,動态解析頁面的時候,我們需要擷取在網頁上所有的内容,就必須要渲染出網頁全部的頁面,不然我們可能就會定位不到我們想要的資訊,是以我們就需要進行頁面的滾動,一直到頁面最底部。

方法為:

from selenium import webdriver
import time, random

# 1.建立driver對象
driver = webdriver.Chrome()
# 通路url位址
driver.get('https://www.csdn.net')
for i in range(1, 10):
    # 構造滑動函數,scrollTo滾動,參數為滑動的距離
    js = 'scrollTo(0, {})'.format(1000 * i)
    # execute_script 執行滑動軌迹
    driver.execute_script(js)
    time.sleep(random.randint(1, 3))
print(driver.page_source)
           

這樣運作後,頁面就會滑到最底部,無需記憶,直接複制粘貼就行。

我們來一個比較狠的,我們來做一個鬥魚的彈幕機。

from selenium import webdriver
from lxml import etree
import time
import re

class DySpider(object):
    def __init__(self):
        self.driver = webdriver.Chrome()
        # 最大化視窗
        self.driver.maximize_window()
        self.start_url = 'https://www.douyu.com/directory/all'
        self.driver.get('https://passport.douyu.com/member/login?')
        time.sleep(10)

    def parse_url(self):
        """
        關閉彈窗,執行滑動
        :return:html_element(elements内容), num(最大頁碼數)
        """
        self.driver.get(self.start_url)
        try:
            """通路後,沒有彈窗,在此使用try嵌套"""
            time.sleep(3)
            self.driver.find_element_by_class_name('ZoomTip-tipHide').click()
        except:
            """沒有彈窗,則pass"""
            pass
        """擷取elements内容"""
        time.sleep(2)
        self.page_url()
        html_element = self.driver.page_source
        print(html_element)
        """提取翻頁的最大頁碼數"""
        num = re.findall('dy-Pagination-item dy-Pagination-item-(\d+)',html_element)[5]
        """驗證是否正常(成功)擷取"""
        print('傳回的頁面共有:' + num)
        return num

    def page_url(self, num=None):
        """
        構造向下滑動
        :return:
        """
        if num is None:
            num = 10000
        """每次向下滑動距離10000"""
        js = 'scrollTo(0, {})'.format(num)
        """執行滑動"""
        self.driver.execute_script(js)
        """滑動之後沉睡等待2秒"""
        time.sleep(2)

    def parse_page_url(self, num):
        """
        執行翻頁
        :param num: 頁碼數
        """
        """根據最大頁碼數的周遊,來執行點選下一頁的次數"""
        for i in range(int(num) - 2):
            """調用滑動函數"""
            self.page_url()
            time.sleep(2)
            """下一頁的點選事件"""
            self.driver.find_element_by_css_selector('#listAll > section.layout-Module.js-ListContent > div.layout-Module-container.layout-Cover.ListContent > div > ul > li.dy-Pagination-next > span').click()
            """每次成功下一頁之後,擷取其elements内容"""
            html_data = self.driver.page_source
            html_element = etree.HTML(html_data)
            self.save_id_data(html_element)

    def save_id_data(self, html_element):
        """
        :param html_element: etree轉換之後的對象
        """
        # 擷取直播間的id清單
        # html_element = etree.HTML(html_element)
        id_num = html_element.xpath('//*[@id="listAll"]/section[2]/div[2]/ul/li/div/a[1]/@href')
        # 直播間id去重
        # id_num = list(set(id_num))
        print(id_num)
        # 通路第一個直播間,達到去除視窗的效果
        js = 'window.open("{}");'.format('https://www.douyu.com' + id_num[0])
        self.driver.execute_script(js)
        time.sleep(2)
        # 擷取所有浏覽器視窗
        windows = self.driver.window_handles
        # 視窗切換,切換到新打開的直播間視窗
        self.driver.switch_to.window(windows[1])
        # 關閉打開的視窗
        self.driver.close()
        # 切換到初始視窗
        self.driver.switch_to.window(windows[0])
        for id in id_num:
            # 直播間的url位址拼接
            url = 'https://www.douyu.com' + id
            # 調用函數
            self.requests_url_by(url)
            # print(url)
    def requests_url_by(self, url):
        """
        通路直播
        :param url: 直播間的url位址
        """
        # js代碼執行打開url--直播間的url位址
        js = 'window.open("{}");'.format(url)
        self.driver.execute_script(js)
        # 隐式等待
        self.driver.implicitly_wait(10)
        # 擷取目前所有視窗,傳回一個清單
        windows = self.driver.window_handles
        # 視窗切換,下标操作
        self.driver.switch_to.window(windows[1])
        try:
            for i in range(1, 3):
                text = '1'
                self.driver.find_element_by_css_selector('.ChatSend-txt').send_keys(text)  # 輸入彈幕
                self.driver.find_element_by_css_selector('.ChatSend-button ').click()  # 點選發送彈幕
                print(f'彈幕----------已發送----------第{i}次')
                time.sleep(1.5)  # 每一秒發送一次共發送20次
        except:
            print('發送失敗')
        self.driver.close()
        self.driver.switch_to.window(windows[0])

    def run(self):
        num = self.parse_url()
        # self.save_id_data(html_element)
        self.parse_page_url(num)


if __name__ == '__main__':
    dy = DySpider()
    dy.run()
           

這個案例會周遊現在鬥魚開播的直播間,然後在每個直播間發兩遍‘1’,但是你需要首先在彈出來鬥魚登陸界面,掃碼登陸你的鬥魚賬号,不然你是不能發彈幕。該案例中用了很多方法,融彙貫通會很适合。我将在下一期講解類方法的使用。