日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

第15讲:Selenium 爬取实战

發布時間:2024/4/11 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 第15讲:Selenium 爬取实战 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在上一課時我們學習了 Selenium 的基本用法,本課時我們就來結合一個實際的案例來體會一下 Selenium 的適用場景以及使用方法。

1.準備工作

在本課時開始之前,請確保已經做好了如下準備工作:

  • 安裝好 Chrome 瀏覽器并正確配置了 ChromeDriver。
  • 安裝好 Python (至少為 3.6 版本)并能成功運行 Python 程序。
  • 安裝好了 Selenium 相關的包并能成功用 Selenium 打開 Chrome 瀏覽器。

2.適用場景

在前面的實戰案例中,有的網頁我們可以直接用 requests 來爬取,有的可以直接通過分析 Ajax 來爬取,不同的網站類型有其適用的爬取方法。

Selenium 同樣也有其適用場景。對于那些帶有 JavaScript 渲染的網頁,我們多數情況下是無法直接用 requests 爬取網頁源碼的,不過在有些情況下我們可以直接用 requests 來模擬 Ajax 請求來直接得到數據。
然而在有些情況下 Ajax 的一些請求接口可能帶有一些加密參數,如 token、sign 等等,如果不分析清楚這些參數是怎么生成的話,我們就難以模擬和構造這些參數。怎么辦呢?這時候我們可以直接選擇使用 Selenium 驅動瀏覽器渲染的方式來另辟蹊徑,實現所見即所得的爬取,這樣我們就無需關心在這個網頁背后發生了什么請求、得到什么數據以及怎么渲染頁面這些過程,我們看到的頁面就是最終瀏覽器幫我們模擬了 Ajax 請求和 JavaScript 渲染得到的最終結果,而 Selenium 正好也能拿到這個最終結果,相當于繞過了 Ajax 請求分析和模擬的階段,直達目標。

然而 Selenium 當然也有其局限性,它的爬取效率較低,有些爬取需要模擬瀏覽器的操作,實現相對煩瑣。不過在某些場景下也不失為一種有效的爬取手段。

3.爬取目標

本課時我們就拿一個適用 Selenium 的站點來做案例,其鏈接為:https://dynamic2.scrape.cuiqingcai.com/,還是和之前一樣的電影網站,頁面如圖所示。


初看之下頁面和之前也沒有什么區別,但仔細觀察可以發現其 Ajax 請求接口和每部電影的 URL 都包含了加密參數。

比如我們點擊任意一部電影,觀察一下 URL 的變化,如圖所示。

這里我們可以看到詳情頁的 URL 和之前就不一樣了,在之前的案例中,URL 的 detail 后面本來直接跟的是 id,如 1、2、3 等數字,但是這里直接變成了一個長字符串,看似是一個 Base64 編碼的內容,所以這里我們無法直接根據規律構造詳情頁的 URL 了。

好,那么接下來我們直接看看 Ajax 的請求,我們從列表頁的第 1 頁到第 10 頁依次點一下,觀察一下 Ajax 請求是怎樣的,如圖所示。


可以看到這里接口的參數比之前多了一個 token,而且每次請求的 token 都是不同的,這個 token 同樣看似是一個 Base64 編碼的字符串。更困難的是,這個接口還是有時效性的,如果我們把 Ajax 接口 URL 直接復制下來,短期內是可以訪問的,但是過段時間之后就無法訪問了,會直接返回 401 狀態碼。

那現在怎么辦呢?之前我們可以直接用 requests 來構造 Ajax 請求,但現在 Ajax 請求接口帶了這個 token,而且還是可變的,現在我們也不知道 token 的生成邏輯,那就沒法直接通過構造 Ajax 請求的方式來爬取了。這時候我們可以把 token 的生成邏輯分析出來再模擬 Ajax 請求,但這種方式相對較難。所以這里我們可以直接用 Selenium 來繞過這個階段,直接獲取最終 JavaScript 渲染完成的頁面源碼,再提取數據就好了。

所以本課時我們要完成的目標有:

  • 通過 Selenium 遍歷列表頁,獲取每部電影的詳情頁 URL。
  • 通過 Selenium 根據上一步獲取的詳情頁 URL 爬取每部電影的詳情頁。
  • 提取每部電影的名稱、類別、分數、簡介、封面等內容。

4.爬取列表頁

首先要我們要做如下初始化的工作,代碼如下:

from selenium import webdriver from selenium.common.exceptions import TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait import logging logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s: %(message)s') INDEX_URL = 'https://dynamic2.scrape.cuiqingcai.com/page/{page}' TIME_OUT = 10 TOTAL_PAGE = 10 browser = webdriver.Chrome() wait = WebDriverWait(browser, TIME_OUT)

首先我們導入了一些必要的 Selenium 模塊,包括 webdriver、WebDriverWait 等等,后面我們會用到它們來實現頁面的爬取和延遲等待等設置。然后接著定義了一些變量和日志配置,和之前幾課時的內容是類似的。接著我們使用 Chrome 類生成了一個 webdriver 對象,賦值為 browser,這里我們可以通過 browser 調用 Selenium 的一些 API 來完成一些瀏覽器的操作,如截圖、點擊、下拉等等。最后我們又聲明了一個 WebDriverWait 對象,利用它我們可以配置頁面加載的最長等待時間。

好,接下來我們就觀察下列表頁,實現列表頁的爬取吧。這里可以觀察到列表頁的 URL 還是有一定規律的,比如第一頁為 https://dynamic2.scrape.cuiqingcai.com/page/1,頁碼就是 URL 最后的數字,所以這里我們可以直接來構造每一頁的 URL。

那么每個列表頁要怎么判斷是否加載成功了呢?很簡單,當頁面出現了我們想要的內容就代表加載成功了。在這里我們就可以用 Selenium 的隱式判斷條件來判定,比如每部電影的信息區塊的 CSS 選擇器為 #index .item,如圖所示。

所以這里我們直接使用 visibility_of_all_elements_located 判斷條件加上 CSS 選擇器的內容即可判定頁面有沒有加載出來,配合 WebDriverWait 的超時配置,我們就可以實現 10 秒的頁面的加載監聽。如果 10 秒之內,我們所配置的條件符合,則代表頁面加載成功,否則則會拋出 TimeoutException 異常。

代碼實現如下:

def scrape_page(url, condition, locator):logging.info('scraping %s', url)try:browser.get(url)wait.until(condition(locator))except TimeoutException:logging.error('error occurred while scraping %s', url, exc_info=True) def scrape_index(page):url = INDEX_URL.format(page=page)scrape_page(url, condition=EC.visibility_of_all_elements_located,locator=(By.CSS_SELECTOR, '#index .item'))

這里我們定義了兩個方法。

第一個方法 scrape_page 依然是一個通用的爬取方法,它可以實現任意 URL 的爬取和狀態監聽以及異常處理,它接收 url、condition、locator 三個參數,其中 url 參數就是要爬取的頁面 URL;condition 就是頁面加載的判定條件,它可以是 expected_conditions 的其中某一項判定條件,如 visibility_of_all_elements_located、visibility_of_element_located 等等;locator 代表定位器,是一個元組,它可以通過配置查詢條件和參數來獲取一個或多個節點,如 (By.CSS_SELECTOR, ‘#index .item’) 則代表通過 CSS 選擇器查找 #index .item 來獲取列表頁所有電影信息節點。另外爬取的過程添加了 TimeoutException 檢測,如果在規定時間(這里為 10 秒)沒有加載出來對應的節點,那就拋出 TimeoutException 異常并輸出錯誤日志。

第二個方法 scrape_index 則是爬取列表頁的方法,它接收一個參數 page,通過調用 scrape_page 方法并傳入 condition 和 locator 對象,完成頁面的爬取。這里 condition 我們用的是 visibility_of_all_elements_located,代表所有的節點都加載出來才算成功。

注意,這里爬取頁面我們不需要返回任何結果,因為執行完 scrape_index 后,頁面正好處在對應的頁面加載完成的狀態,我們利用 browser 對象可以進一步進行信息的提取。

好,現在我們已經可以加載出來列表頁了,下一步當然就是進行列表頁的解析,提取出詳情頁 URL ,我們定義一個如下的解析列表頁的方法:

from urllib.parse import urljoin def parse_index():elements = browser.find_elements_by_css_selector('#index .item .name')for element in elements:href = element.get_attribute('href')yield urljoin(INDEX_URL, href)

這里我們通過 find_elements_by_css_selector 方法直接提取了所有電影的名稱,接著遍歷結果,通過 get_attribute 方法提取了詳情頁的 href,再用 urljoin 方法合并成一個完整的 URL。

最后,我們再用一個 main 方法把上面的方法串聯起來,實現如下:

def main():try:for page in range(1, TOTAL_PAGE + 1):scrape_index(page)detail_urls = parse_index()logging.info('details urls %s', list(detail_urls))finally:browser.close()

這里我們就是遍歷了所有頁碼,依次爬取了每一頁的列表頁并提取出來了詳情頁的 URL。

運行結果如下:

2020-03-29 12:03:09,896 - INFO: scraping https://dynamic2.scrape.cuiqingcai.com/page/1 2020-03-29 12:03:13,724 - INFO: details urls ['https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx', ... 'https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI5', 'https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIxMA=='] 2020-03-29 12:03:13,724 - INFO: scraping https://dynamic2.scrape.cuiqingcai.com/page/2 ...

由于輸出內容較多,這里省略了部分內容。

觀察結果我們可以發現,詳情頁那一個個不規則的 URL 就成功被我們提取到了!

5.爬取詳情頁

好了,既然現在我們已經可以成功拿到詳情頁的 URL 了,接下來我們就進一步完成詳情頁的爬取并提取對應的信息吧。

同樣的邏輯,詳情頁我們也可以加一個判定條件,如判斷電影名稱加載出來了就代表詳情頁加載成功,同樣調用 scrape_page 方法即可,代碼實現如下:

def scrape_detail(url):scrape_page(url, condition=EC.visibility_of_element_located,locator=(By.TAG_NAME, 'h2'))

這里的判定條件 condition 我們使用的是 visibility_of_element_located,即判斷單個元素出現即可,locator 我們傳入的是 (By.TAG_NAME, ‘h2’),即 h2 這個節點,也就是電影的名稱對應的節點,如圖所示。

如果執行了 scrape_detail 方法,沒有出現 TimeoutException 的話,頁面就加載成功了,接著我們再定義一個解析詳情頁的方法,來提取出我們想要的信息就可以了,實現如下:

def parse_detail():url = browser.current_urlname = browser.find_element_by_tag_name('h2').textcategories = [element.text for element in browser.find_elements_by_css_selector('.categories button span')]cover = browser.find_element_by_css_selector('.cover').get_attribute('src')score = browser.find_element_by_class_name('score').textdrama = browser.find_element_by_css_selector('.drama p').textreturn {'url': url,'name': name,'categories': categories,'cover': cover,'score': score,'drama': drama}

這里我們定義了一個 parse_detail 方法,提取了 URL、名稱、類別、封面、分數、簡介等內容,提取方式如下:

  • URL:直接調用 browser 對象的 current_url 屬性即可獲取當前頁面的 URL。
    名稱:通過提取 h2 節點內部的文本即可獲取,這里使用了 find_element_by_tag_name 方法并傳入 h2,提取到了名稱的節點,然后調用 text 屬性即提取了節點內部的文本,即電影名稱。
  • 類別:為了方便,類別我們可以通過 CSS 選擇器來提取,其對應的 CSS 選擇器為 .categories button span,可以選中多個類別節點,這里我們通過 find_elements_by_css_selector 即可提取 CSS 選擇器對應的多個類別節點,然后依次遍歷這個結果,調用它的 text 屬性獲取節點內部文本即可。
  • 封面:同樣可以使用 CSS 選擇器 .cover 直接獲取封面對應的節點,但是由于其封面的 URL 對應的是 src 這個屬性,所以這里用 get_attribute 方法并傳入 src 來提取。
  • 分數:分數對應的 CSS 選擇器為 .score ,我們可以用上面同樣的方式來提取,但是這里我們換了一個方法,叫作 find_element_by_class_name,它可以使用 class 的名稱來提取節點,能達到同樣的效果,不過這里傳入的參數就是 class 的名稱 score 而不是 .score 了。提取節點之后,我們再調用 text 屬性提取節點文本即可。
  • 簡介:同樣可以使用 CSS 選擇器 .drama p 直接獲取簡介對應的節點,然后調用 text 屬性提取文本即可。

最后,我們把結果構造成一個字典返回即可。
接下來,我們在 main 方法中再添加這兩個方法的調用,實現如下:

def main():try:for page in range(1, TOTAL_PAGE + 1):scrape_index(page)detail_urls = parse_index()for detail_url in list(detail_urls):logging.info('get detail url %s', detail_url)scrape_detail(detail_url)detail_data = parse_detail()logging.info('detail data %s', detail_data)finally:browser.close()

這樣,爬取完列表頁之后,我們就可以依次爬取詳情頁,來提取每部電影的具體信息了。

2020-03-29 12:24:10,723 - INFO: scraping https://dynamic2.scrape.cuiqingcai.com/page/1 2020-03-29 12:24:16,997 - INFO: get detail url https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx 2020-03-29 12:24:16,997 - INFO: scraping https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx 2020-03-29 12:24:19,289 - INFO: detail data {'url': 'https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx', 'name': '霸王別姬 - Farewell My Concubine', 'categories': ['劇情', '愛情'], 'cover': 'https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@464w_644h_1e_1c', 'score': '9.5', 'drama': '影片借一出《霸王別姬》的京戲,牽扯出三個人之間一段隨時代風云變幻的愛恨情仇。段小樓(張豐毅 飾)與程蝶衣(張國榮 飾)是一對打小一起長大的師兄弟,兩人一個演生,一個飾旦,一向配合天衣無縫,尤其一出《霸王別姬》,更是譽滿京城,為此,兩人約定合演一輩子《霸王別姬》。但兩人對戲劇與人生關系的理解有本質不同,段小樓深知戲非人生,程蝶衣則是人戲不分。段小樓在認為該成家立業之時迎娶了名妓菊仙(鞏俐 飾),致使程蝶衣認定菊仙是可恥的第三者,使段小樓做了叛徒,自此,三人圍繞一出《霸王別姬》生出的愛恨情仇戰開始隨著時代風云的變遷不斷升級,終釀成悲劇。'} 2020-03-29 12:24:19,291 - INFO: get detail url https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIy 2020-03-29 12:24:19,291 - INFO: scraping https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIy 2020-03-29 12:24:21,524 - INFO: detail data {'url': 'https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIy', 'name': '這個殺手不太冷 - Léon', 'categories': ['劇情', '動作', '犯罪'], 'cover': 'https://p1.meituan.net/movie/6bea9af4524dfbd0b668eaa7e187c3df767253.jpg@464w_644h_1e_1c', 'score': '9.5', 'drama': '里昂(讓·雷諾 飾)是名孤獨的職業殺手,受人雇傭。一天,鄰居家小姑娘馬蒂爾德(納塔麗·波特曼 飾)敲開他的房門,要求在他那里暫避殺身之禍。原來鄰居家的主人是警方緝毒組的眼線,只因貪污了一小包毒品而遭惡警(加里·奧德曼 飾)殺害全家的懲罰。馬蒂爾德 得到里昂的留救,幸免于難,并留在里昂那里。里昂教小女孩使槍,她教里昂法文,兩人關系日趨親密,相處融洽。 女孩想著去報仇,反倒被抓,里昂及時趕到,將女孩救回。混雜著哀怨情仇的正邪之戰漸次升級,更大的沖突在所難免……'} ...

這樣詳情頁數據我們也可以提取到了。

6.數據存儲

最后,我們再像之前一樣添加一個數據存儲的方法,為了方便,這里還是保存為 JSON 文本文件,實現如下:

from os import makedirs from os.path import exists RESULTS_DIR = 'results' exists(RESULTS_DIR) or makedirs(RESULTS_DIR) def save_data(data):name = data.get('name')data_path = f'{RESULTS_DIR}/{name}.json'json.dump(data, open(data_path, 'w', encoding='utf-8'), ensure_ascii=False, indent=2)

這里原理和實現方式與 Ajax 爬取實戰課時是完全相同的,不再贅述。

最后添加上 save_data 的調用,完整看下運行效果。

7.Headless

如果覺得爬取過程中彈出瀏覽器有所干擾,我們可以開啟 Chrome 的 Headless 模式,這樣爬取過程中便不會再彈出瀏覽器了,同時爬取速度還有進一步的提升。

只需要做如下修改即可:

options = webdriver.ChromeOptions() options.add_argument('--headless') browser = webdriver.Chrome(options=options)

這里通過 ChromeOptions 添加了 --headless 參數,然后用 ChromeOptions 來進行 Chrome 的初始化即可。

修改后再重新運行代碼,Chrome 瀏覽器就不會彈出來了,爬取結果是完全一樣的。

8.總結

本課時我們通過一個案例了解了 Selenium 的適用場景,并結合案例使用 Selenium 實現了頁面的爬取,從而對 Selenium 的使用有進一步的掌握。

以后我們就知道什么時候可以用 Selenium 以及怎樣使用 Selenium 來完成頁面的爬取啦。

總結

以上是生活随笔為你收集整理的第15讲:Selenium 爬取实战的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。