為了解決異步渲染網頁,我們直接模拟浏覽器運作的方式來實作,這樣就可以左到在浏覽器中看到什麼樣,抓取的源碼就是什麼樣,也就是可見即可爬。這樣我們就可以不用管網頁内部的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是忽略掉非法字元,然後再譯碼。不然會報編碼錯誤。這裡會列印出網頁的源碼。
小結:
- selenium的導包:
from selenium import webdriver
- selenium建立driver對象:
webdriver.PhantomJS()
- selenium請求資料:
driver.get("http://www.baidu.com/")
- selenium檢視資料:
driver.page_source
- 關閉無界面浏覽器:
driver.quit()
- 根據id定位元素:
driver.find_element_by_id(“kw”)
- 操作點選事件:
click()
- 給輸入框指派:
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,是不是和上一章的解析方法有些類似。
小結
- 根據xpath定位元素:
driver.find_elements_by_xpath("//*[@id='s']/h1/a")
- 根據class定位元素:
driver.find_elements_by_class_name("box")
- 根據link_text定位元素:
driver.find_elements_by_link_text("下載下傳豆瓣 App")
- 根據tag_name定位元素:
driver.find_elements_by_tag_name("h1")
- 擷取文本内容:
element.text
- 擷取标簽屬性:
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由于在擷取頁面的過程中會發送很多請求,是以效率非常低,是以在很多時候需要酌情使用
小結
- 擷取cookie:
get_cookies()
- 删除cookie:
delete_all_cookies()
- 切換視窗:
switch_to.window()
- 切換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’,但是你需要首先在彈出來鬥魚登陸界面,掃碼登陸你的鬥魚賬号,不然你是不能發彈幕。該案例中用了很多方法,融彙貫通會很适合。我将在下一期講解類方法的使用。