动态网页的抓取
已寫章節
第一章 網絡爬蟲入門
第二章 基本庫的使用
第三章 解析庫的使用
第四章 數據存儲
第五章 動態網頁的抓取
文章目錄
- 已寫章節
- 第五章 動態網頁的抓取(Selenium)
- 5.1 Ajax的原理
- 5.2 Selenium的使用
- 5.2.1 準備工作
- 5.2.2 Selenium的使用
- 1. 聲明瀏覽器對象
- 2. 訪問頁面
- 3. 查找節點
- 4. 查找多個節點
- 5. 節點交互
- 6. 動作鏈
- 7. 滑動頁面
- 8. 執行JavaScript
- 9. 獲取節點的信息
- 10. 切換Frame
- 11. 等待
- 12. 控制頁面前進和后退
- 13. 對cookies的操作
- 14. 選項卡
- 15. 模擬輸入鍵盤
- 16. 異常處理
- 5.3 使用Selenium的例子
第五章 動態網頁的抓取(Selenium)
有時候我們在用requests抓取頁面的時候,得到的結果可能和我們在瀏覽器中看到的結果不一樣:在瀏覽器中可以正常看到頁面數據,但是使用requests得到的結果并沒有。這是因為requests庫獲取的都是原始的HTML文檔,而在瀏覽器中看到的頁面是經過JavaScript處理數據后生成的結果,這些數據來源有很多種,可能是通過Ajax加載的,可能是包含在HTML文檔中的,也有可能是經過JavaScript和特定的算法計算后生成的。如果是通過Ajax加載的,原始頁面最初不會包含某些數據,原始頁面加載完之后,會再向服務器請求某個接口獲取數據,然后數據才被處理從而呈現在網頁上,這其實就是發生了一個Ajax請求。如果遇到這樣的頁面,需要分析出網頁后臺向服務器發送的Ajax請求,如果可以使用requests來模擬Ajax請求,那么就可以成功抓取了。
5.1 Ajax的原理
Ajax(Asynchronous JavaScript nad XML),即異步的JavaScript和XML。它不是一門編程語言,而是利用JavaScript在保證網頁不被刷新、頁面不改變的情況下與服務器交換數據并更新部分網頁的技術。
例如:打開tx視頻,在電影中,鼠標的滑輪向下劃,你會發現電影好像沒有盡頭,有時候最下面會出現加載的動畫。頁面其實并沒有整個刷新,鏈接也并沒有變化,但是網頁中新增加了電影,這就是通過Ajax獲取新數據并呈現的過程。
Ajax有其特殊的請求類型,它是xhr,可以在Chrome的檢查中選擇xhr查看。
模擬Ajax請求來爬取tx視頻的例子:
import requests from fake_useragent import UserAgent from lxml import etree import re from typing import NoReturn, List import time import mysql.connectordef start() -> NoReturn:first_url = 'https://v.qq.com/channel/movie?listpage=1&channel=movie&itype=100062'base_url = 'https://v.qq.com/x/bu/pagesheet/list?append=1&channel=movie&itype=100062&listpage=2&offset={}&pagesize=30'create_table()storage_MysqlDB(get_page(first_url), 1)page_numbers = ((get_number_of_movies(first_url)-30)//30)+1for i in range(1, page_numbers):url = base_url.format(str(i*30))storage_MysqlDB(get_page(url), (i*30)+1)# time.sleep(5)def get_number_of_movies(url: str) -> int:headers = {'user-Agent': UserAgent().chrome}response = requests.get(url=url, headers=headers)response.encoding = response.apparent_encodinge = etree.HTML(response.text, etree.HTMLParser())number_of_movies = e.xpath('//body/div[5]/div/div/div/span/text()')[0]return int(number_of_movies)def get_page(url: str) -> List[List]:response = requests.get(url=url, headers={'user-Agent': UserAgent().chrome})response.encoding = response.apparent_encodinge = etree.HTML(response.text, etree.HTMLParser())all_div = e.xpath('//div[@class="list_item"]')res = []for div in all_div:movies_name = div.xpath('./div[1]/a/@title')hrefs = div.xpath('./div[1]/a/@href')performers = div.xpath('./div[1]/div/text()')performers = [i.replace('主演:', '') for i in performers]number_of_observers = div.xpath('./div[2]/text()')film_length = div.xpath('./a/div[1]/text()')score = div.xpath('./a/div[2]/text()')if len(movies_name) == 0:movies_name = ['None']if len(hrefs) == 0:hrefs = ['None']if len(performers) == 0:performers = ['None']if len(number_of_observers) == 0:number_of_observers = ['None']if len(film_length) == 0:film_length = ['None']if len(score) == 0:score = ['None']tem = []tem.append(str(movies_name[0]))tem.append(str(hrefs[0]))tem.append(str(performers[0]))tem.append(str(number_of_observers[0]))tem.append(str(film_length[0]))tem.append(str(score[0]))res.append(tem)print(res)return resdef create_table():mydb = mysql.connector.connect(host='localhost',user='root',passwd='123456',port=3307,charset='utf8')mycursor = mydb.cursor()sql_create_table = ["USE reptile;","CREATE TABLE IF NOT EXISTS tencent_movies(\ID INT AUTO_INCREMENT COMMENT '編號',\movies_name VARCHAR(40) COMMENT '電影名',\hrefs VARCHAR(100) COMMENT '鏈接',\performers VARCHAR(100) COMMENT '主演',\number_of_observers VARCHAR(20) COMMENT '播放量',\film_length VARCHAR(20) COMMENT '電影時長',\score varchar(4) COMMENT '評分',\PRIMARY KEY(ID)\)COMMENT = 'All movie information in Tencent video'\ENGINE = INNODB CHARSET='utf8mb4' COLLATE='utf8mb4_unicode_ci';"]for i in sql_create_table:mycursor.execute(i)def storage_MysqlDB(data: List, start: int)-> None:mydb = mysql.connector.connect(host='localhost',user='root',passwd='123456',port=3307,charset='utf8',database='reptile')mycursor = mydb.cursor()sql_insert = "insert into tencent_movies values(%s, %s, %s, %s, %s, %s, %s)"for i in data:i.insert(0, start)new_i = tuple(i)mycursor.execute(sql_insert, new_i)# print(start)# time.sleep(1)start += 1mydb.commit()mycursor.close()if __name__ == '__main__':start()在上面,我們介紹了Ajax,并使用requests來模擬Ajax請求來爬取數據。
但是,JavaScript動態渲染頁面不止Ajax這一種。有很多網頁是由JavaScript產生的,并不會包含Ajax請求。淘寶頁面也是使用Ajax來獲取數據的,但是,他的Ajax接口中包含許多的加密參數,我們很難找出其規律,也很難使用requests來模擬Ajax請求。
為了解決這些問題,我們可以直接使用模擬瀏覽器的方式來實現,這樣就可以做到在瀏覽器中看到什么樣子,抓取的源碼就是什么樣子。
5.2 Selenium的使用
Selenium是一個自動化測試工具,利用它可以驅動瀏覽器執行特定的動作,如下拉、點擊按鈕、在文本框中輸入文字、下劃頁面等動作,同時還可以獲取瀏覽器當前呈現的頁面的源代碼。可以將Selenium看做是個通過代碼來控制瀏覽器的工具。
5.2.1 準備工作
- 需要下載與你的Chrome版本相對應的ChromeDriver
- 安裝Selenium庫
- 將下載好的瀏覽器驅動放在Python的安裝目錄下的Scripy目錄中
注意:使用Edge的驅動器會提示要將驅動添加到path中,可以在初始化一個Edge瀏覽器對象的時候傳入Edge驅動的路徑:
from selenium import webdriverbrowser = webdriver.Edge('E:\python\Scripts\msedgedriver.exe') browser.get('https://www.baidu.com')所以建議大家使用Chrome瀏覽器。
ChromeDriver下載地址
Edge驅動下載地址
Selenium官方文檔
5.2.2 Selenium的使用
當所有的準備工作都完成后,打開編輯器,來敲代碼了。
首先來看看下面這個小例子:
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWaitbrowser = webdriver.Chrome() # 聲明一個chrome瀏覽器對象 try:browser.get('https://www.baidu.com') # 打開指定的網頁input = browser.find_element_by_id('kw') # 尋找搜索框input.send_keys('Python') # 在搜索框中輸入指定的內容input.send_keys(Keys.ENTER)wait = WebDriverWait(browser, 10)wait.until(EC.presence_of_all_elements_located((By.ID, 'content_left')))print(browser.current_url) # 打印當前頁面的urlprint(browser.get_cookies()) # 打印當前頁面的cookiesprint(browser.page_source) # 打印當前頁面的源代碼 finally:browser.close()執行上面的代碼,如果一切正常的話,你的電腦應該自動打開了Chrome瀏覽器并來到了百度的首頁,然后自動在搜索框中輸入了“Python”并點擊了搜索按鈕,控制臺中打印出當前頁面的url、cookie、頁面源代碼。
1. 聲明瀏覽器對象
Selenium支持非常多的瀏覽器,如Chrome、Firefox、Edge等,還有Android、BlackBerry等手機端的瀏覽器。
下面是初始瀏覽器的方法:
from selenium import webdriverbrowser = webdriver.Chrome() browser = webdriver.Firefox() browser = webdriver.Edge() browser = webdriver.PhantomJS() browser = webdriver.Safari()注意:
可以指定驅動器路徑來初始化瀏覽器對象:
browser = webdriver.Edge(‘E:\python\Scripts\msedgedriver.exe’)
2. 訪問頁面
可以使用get()方法來請求網頁,將要請求的url傳入即可,下面是請求百度的例子:
from selenium import webdriver import timebrowser = webdriver.Edge('E:\python\Scripts\msedgedriver.exe') browser.get('https://www.baidu.com') print(browser.page_source) time.sleep(10) browser.close()3. 查找節點
Selenium可以驅動瀏覽器完成各種操作,比如填充表單、模擬點擊等。比如,我們想要完成在某個輸入框輸入文本的操作,我們首先需要使用selenium查找到這個輸入框所在的節點位置。
-
查找單個節點的常用方法
查找單個節點的方法 find_element_by_id() 通過節點的id屬性值來查找單個節點 find_element_by_name() 通過節點的name屬性值來查找單個節點 find_element_by_xpath() 通過xpath來查找單個節點 find_element_by_link_text() 通過完整的鏈接文本(點擊該文本就會跳到另一個頁面)查找單個節點 find_element_by_partial_link_text() 通過部分鏈接文本來查找單個節點 find_element_by_tag_name() 通過節點的標簽名來查找單個節點 find_element_by_class_name() 通過節點的class屬性值來查找單個節點 find_element_by_css_selector() 通過css來查找當個節點 find_element(By.ID,‘ ’) 同find_element_by_id() find_element(By.CLASS_NAME, ‘ ’) 同find_element_by_class_name() find_element(By.CSS_SELECTOR,’ ’) 同find_element_by_css_selector() find_element(By.LINK_TEXT,’ ’) 同find_element_by_link_text() find_element(By.NAME,’ ’) 同find_element_by_name() find_element(By.PARTIAL_LINK_TEXT,’ ’) 同find_element_by_partial_link_text() find_element(By.TAG_NAME,’ ’) 同find_element_by_tag_name() find_element(By.XPATH,’ ’) 同find_element_by_xpath()
例子:
from selenium import webdriver from selenium.webdriver.common.by import Bybrowser = webdriver.Chrome() url = 'https://www.taobao.com/' browser.get(url) print(browser.find_element(By.ID, 'q')) print(browser.find_element_by_id('q')) print(browser.find_element_by_xpath('//*[id="q"]')) print(broswer.find_element_by_css_selector('#q'))運行結果:
<selenium.webdriver.remote.webelement.WebElement (session="5e8df8d577c9340c9524ecebcf924800", element="18f8aa32-46d6-43cd-89b1-67e48e314310")> <selenium.webdriver.remote.webelement.WebElement (session="5e8df8d577c9340c9524ecebcf924800", element="18f8aa32-46d6-43cd-89b1-67e48e314310")> <selenium.webdriver.remote.webelement.WebElement (session="5e8df8d577c9340c9524ecebcf924800", element="18f8aa32-46d6-43cd-89b1-67e48e314310")> <selenium.webdriver.remote.webelement.WebElement (session="5e8df8d577c9340c9524ecebcf924800", element="18f8aa32-46d6-43cd-89b1-67e48e314310")>4. 查找多個節點
在網頁中如果查找的節點有多個,使用find_element()方法將只會找到第一個節點,如果想要查找多個節點,就需要使用find_elements()方法。
from selenium import webdriverwith webdriver.Chrome() as browser:browser.get('https://www.taobao.com')all_nodes = browser.find_elements_by_xpath('//li[@class="J_Cat a-all"]')print(len(all_nodes))print(all_nodes)上面的代碼將自動使用Chrome打開淘寶的主頁,打印出查找到的淘寶主頁左上角的15個導航欄。
將上面的表格中查找單個節點的方法中的element改為elements就是查找多個節點的方法。
5. 節點交互
Selenium可以驅動瀏覽器來執行一些操作,也就是可以讓瀏覽器執行一些動作。
常用的有:輸入文字時用send_keys()方法,清空文字時用clear()方法,點擊時用click()方法。
from selenium import webdriver import timewith webdriver.Chrome() as browser:browser.get('https://www.taobao.com') # 打開鏈接input = browser.find_element_by_xpath('//*[@id="q"]') # 找到搜索框input.send_keys('背包') # 在搜索框中輸入背包time.sleep(5)input.clear() # 清空搜索框input.send_keys('書包') # 在搜索框中輸入書包search_button = browser.find_element_by_xpath('//button[@class="btn-search tb-bg"]') # 查找搜索按鈕time.sleep(5)search_button.click() # 點擊搜索按鈕6. 動作鏈
上面的選擇輸入框、向輸入框中輸入文本、點擊按鈕等動作,都是有特定的執行對象;但是,對于鼠標移動、按盤按鍵等,這些動作由另一種方式來執行,這就是動作鏈。
from selenium import webdriver from selenium.webdriver import ActionChains import timebrowser = webdriver.Chrome() browser.get('示例網站的url') browser.find_element_by_xpath('//input[@placeholder="請輸入郵箱"]').send_keys('jxd') time.sleep(1) browser.find_element_by_xpath('//input[@placeholder="請輸入密碼"]').send_keys('123456') time.sleep(1) browser.find_element_by_xpath('//div[@class="geetest_wait"]').click() time.sleep(1)# 更換監視界面 ifame = browser.find_element_by_xpath('//div[@class="inner-conntent"]') browser.switch_to.frame(ifame) # 要拖動的元素 source = browser.find_element_by_xpath('//div[@class="geetest_slider_button"]') # 要拖動到的位置元素 target = browser.find_element_by_xpath('//div[@class="geetest_slider_tip geetest_fade"]') actions = ActionChains(browser) actions.drag_and_drop(source, target) actions.perform()7. 滑動頁面
滑動頁面其實就是執行js語句,可以將控制頁面滑動的js語句放在driver.execute_script(js語句)中來執行:
driver.execute_script('window.scrollBy(0,1000)') # 滑動的距離,分為x軸和y軸driver.execute_script('window.scrollTo(0,1000)') # 滑動到指定的坐標driver.execute_script("window.scrollTo(0,document.body.scrollHeight);") # 滑動到頁面的最下方driver.execute_script("document.getElementById('page').scrollIntoView(true)") # 將查找到的元素顯示在屏幕中間,scrollIntoView(false)就是將查找到的元素顯示在屏幕的底部8. 執行JavaScript
上面的向下滑動頁面就是執行JavaScript的例子,我們可以使用driver.execute_script(javascript語句)來執行JavaScript代碼。
9. 獲取節點的信息
| WebElement.get_attribute(‘屬性名’) | 獲取標簽指定的屬性的屬性值 |
| WebElement.text | 獲取標簽的文本 |
| WebElement.id | 獲取標簽的id |
| WebElement.location | 獲取標簽在頁面中的相對位置 |
| WebElement.tag_name | 獲取標簽的標簽名 |
| WebElement.size | 獲取標簽的大小 |
10. 切換Frame
網頁中有一種節點叫做iframe,也就是子頁面,我們使用selenium提供的選擇節點的方法是在父級Frame里面操作,而如果頁面中有子頁面,它是不能獲取到子Frame中的節點的。此時,就需要使用switch_to.frame()方法來切換Frame,上面的例子中有。
注意:switch_to.frame()默認接收的是目標frame節點的id或name,如果目標節點沒有id屬性和name屬性,那么就可以傳入一個用select_element_by_xpath()等方法定位到的WebElement對象。
11. 等待
有時我們請求的頁面中的元素或節點是通過Ajax加載的,就需要一定的時間,在元素未加載出來之前我們去查找該元素就會找不到,所以,就需要等待一段時間,等元素加載出來之后再執行查找元素等一系列操作。
隱式等待:在查找節點而節點并沒有立即出現時,隱式等待會等待一段時間再查找節點,時間默認是0秒,如果還未找到節點就會拋出節點未找到的異常。隱式等待可以看做是time.sleep()方法。
browser.imlicitly_wait(10) # 隱式等待10秒鐘顯示等待:規定一個最長等待時間,在這段時間里如果查找到了節點就返回節點,如果在這段時間里還找到該節點,則拋出超時異常。
from selenium.webdriver.support import expected_conditions as ECwait = WebDriverWait(browser, 10) # 設置顯式等待時間是10秒 input = wait.until(EC.presence_of_element_located((By.ID, 'q')))until()方法接收一個等待條件expected_conditions,上面的例子中的等待條件是presence_of_element_located(),指的是節點出現的意思,其參數是節點的定位元組,也就是ID為q的節點。
常見的等待條件
| title_is | 標題是某內容 |
| title_contains | 標題包含某內容 |
| presence_of_element_located | 節點加載出來,傳入定位元組,如(By.ID, ‘p’) |
| visibility_of_element_located | 節點可見,傳入定位元組 |
| visibility_of | 節點可見,傳入節點對象 |
| presence_of_all_elements_located | 所有節點加載出來 |
| text_to_be_present_in_element | 某個節點文本包含某個文字 |
| text_to_be_present_in_element | 某個節點值包含某個文字 |
| frame_to_be_avaliable_and_switch_to_it | 加載并切換 |
| invisibility_of_element_located | 節點不可見 |
| element_to_be_clickable | 節點可點擊 |
| staleness_of | 判斷 個節點是否仍在 DOM ,可判斷頁面是杏已經刷新 |
| element_ to_be_selected | 節點可選擇,傳節點對象 |
| element_located_to_be_selected | 節點可選擇,傳人定位元組 |
| element_selection_state_to_be | 傳人節點對象以及狀態,相等返回 True ,否則返回 False |
| element_located_selection_state_to_be | 傳入定位元組以及狀態,相等返回 True ,否則返回 False |
| alert_is_present | 是否出現警告 |
12. 控制頁面前進和后退
from seleniun import webdriverbrowser.forward() # 前進 browser.back() # 后退13. 對cookies的操作
from selenium import webdirverbrowser.get_cookies() # 返回字典類型的所有cookies browser.add_cookies() # 添加一個cookies,參數是字典類型 browser.delete_all_cookies() # 刪除所有的cookies14. 選項卡
from selenium import webdriver import timebrowser = webdriver.Chrome() # 初始化一個瀏覽器對象browser.get('https://www.baidu.com') # 訪問百度 browser.execute_script('window.open()') # 新開啟一個選項卡 time.sleep(2)print(browser.window_handles) # 打印所有選項卡代號列表browser.switch_to.window(browser.window_handles[1]) # 更換到選項卡代號列表中的第一個選項卡 time.sleep(1)browser.get('https://www.taobao.com') browser.switch_to.window(browser.window_handles[0]) time.sleep(1)browser.get('https://python.org')15. 模擬輸入鍵盤
上面講的sendkeys()方法可以向輸入框中輸入文字,它還可以實現模擬鍵盤的輸入,甚至是組合鍵。
from selenium.webdriver.common.keys import Keysdriver = webdriver.Firefox() driver.get(“[https://www.baidu.com](https://www.baidu.com/)”)el = driver.find_element(By.ID, "kw") # 找到輸入框 el.send_keys(“seleniumm”) # 在輸入框中輸入"seleniumm" el.send_keys(Keys.BACK_SPACE) # 模擬按一下Backspaece鍵 el.send_keys(Keys.SPACE) # 模擬按一下Space鍵 el.send_keys(“教程”) # 輸入"教程" el.send_keys(Keys.CONTROL, ‘a’) # 模擬按下 "a"鍵 el.send_keys(Keys.CONTROL, ‘x’) # 模擬按下 "x"鍵 el.send_keys(Keys.CONTROL, ‘v’) # 模擬按下 "v"鍵 el.send_keys(Keys.ENTER) # 模擬按下"enter"鍵16. 異常處理
selenium的異常類型都在selenium.common.exceptions這個包中,下面列舉了常用的異常:
| NoSuchElementException | 未找到節點 |
| NoSuchAttributeException | 節點沒有改屬性 |
| NoSuchFrameException | frame未找到 |
| NoSuchWindowException | window未找到 |
知道這些常用的異常之后,就可以使用try except來將這些異常捕獲并做相應的處理了。
5.3 使用Selenium的例子
使用Selenium爬取手機信息
感謝你的閱讀!
總結
- 上一篇: Mybatis配置文件头
- 下一篇: AMD Ryzen内存bug解析:究竟是