1. 程式人生 > >爬蟲系列之第3章-Selenium模塊

爬蟲系列之第3章-Selenium模塊

== .com global 額外 position 安裝路徑 github 例如 off

簡介

selenium最初是一個自動化測試工具,而爬蟲中使用它主要是為了解決requests無法直接執行JavaScript代碼的問題 selenium本質是通過驅動瀏覽器,完全模擬瀏覽器的操作,比如跳轉、輸入、點擊、下拉等,來拿到網頁渲染之後的結果,可支持多種瀏覽器

安裝

1 下載驅動

http://npm.taobao.org/mirrors/chromedriver/2.35/ 

if mac系統:

然後將解壓後的chromedriver移動到/usr/local/bin目錄下

if window系統:

下載chromdriver.exe放到python安裝路徑的scripts目錄中即可,註意最新版本是2.38,並非2.9

2 安裝pip包

pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple selenium

註意:selenium3默認支持的webdriver是Firfox,而Firefox需要安裝geckodriver 下載鏈接

1 簡單使用

from selenium import webdriver
from selenium.webdriver.common.by import By  # 按照什麽方式查找,By.ID,By.CSS_SELECTOR
from selenium.webdriver.common.keys import
Keys # 鍵盤按鍵操作 from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait # 等待頁面加載某些元素 import time browser = webdriver.Chrome() try: browser.get("https://www.jd.com") input_tag = browser.find_element_by_id("key") # 找到id為key的標簽
input_tag.send_keys("美女") # 在輸入框內輸入“美女” input_tag.send_keys(Keys.ENTER) # 點擊回車 wait = WebDriverWait(browser, 10) # 顯示等待,(就是等頁面全部加載出來在記性下一步操作) wait.until(EC.presence_of_element_located((By.ID, "J_goodsList"))) # 等到id為J_goodsList的元素加載完畢,最多等10秒 time.sleep(8) finally: browser.close()

Selenium支持非常多的瀏覽器,如Chrome、Firefox、Edge等,還有Android、BlackBerry等手機端的瀏覽器。另外,也支持無界面瀏覽器PhantomJS。

from selenium import webdriver
  
browser = webdriver.Chrome()
browser = webdriver.Firefox()
browser = webdriver.Edge()
browser = webdriver.PhantomJS()
browser = webdriver.Safari()

2 元素定位

webdriver 提供了一系列的元素定位方法,常用的有以下幾種:

 id
 name
 class name
 tag name
 link text
 partial link text
 xpath
 css selector

分別對應python webdriver 中的方法為:

from selenium import webdriver


browser = webdriver.Chrome()
browser.get("https://www.jd.com")
tags = browser.find_elements_by_id("navitems")  # 找到id為xxx的
tags = browser.find_elements_by_class_name("text")  # 找到類為 xxx的
tags = browser.find_elements_by_name("keywords")
tags = browser.find_elements_by_link_text("")  # 單獨找到text文本
tags = browser.find_elements_by_partial_link_text("")  # 全部查找(相當於模糊匹配)
tags = browser.find_elements_by_css_selector(".cate_menu_item a")  # css 查找
tags = browser.find_elements_by_xpath("//*[@id=‘logo‘]/h1/a")  # 配合xpath使用
# 通過文本找到標簽
tags = browser.find_elements(By.CLASS_NAME, "text")
for tag in tags:
    print(tag.tag_name)

browser.close()

註意

1、find_element_by_xxx找的是第一個符合條件的標簽,find_elements_by_xxx找的是所有符合條件的標簽。

2、根據ID、CSS選擇器和XPath獲取,它們返回的結果完全一致。

3、另外,Selenium還提供了通用方法find_element(),它需要傳入兩個參數:查找方式By和值。實際上,它就是find_element_by_id()這種方法的通用函數版本,比如find_element_by_id(id)就等價於find_element(By.ID, id),二者得到的結果完全一致。

3 節點交互

Selenium可以驅動瀏覽器來執行一些操作,也就是說可以讓瀏覽器模擬執行一些動作。比較常見的用法有:輸入文字時用send_keys()方法,清空文字時用clear()方法,點擊按鈕時用click()方法。示例如下:

from selenium import webdriver
import time
 
browser = webdriver.Chrome()
browser.get(https://www.taobao.com)
input = browser.find_element_by_id(q)
input.send_keys(MAC)
time.sleep(1)
input.clear()
input.send_keys(IPhone)
button = browser.find_element_by_class_name(btn-search)
button.click()

常見節點的動作操作

4 動作鏈

在上面的實例中,一些交互動作都是針對某個節點執行的。比如,對於輸入框,我們就調用它的輸入文字和清空文字方法;對於按鈕,就調用它的點擊方法。其實,還有另外一些操作,它們沒有特定的執行對象,比如鼠標拖曳、鍵盤按鍵等,這些動作用另一種方式來執行,那就是動作鏈。

比如,現在實現一個節點的拖曳操作,將某個節點從一處拖曳到另外一處,可以這樣實現:

from selenium.webdriver import ActionChains
import time

browser = webdriver.Chrome()
url = "http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable"
browser.get(url)
browser.switch_to.frame("iframeResult")
source = browser.find_element_by_css_selector(#draggable)  # 小方塊
target = browser.find_element_by_css_selector("#droppable")  # 大方塊

actions = ActionChains(browser)

actions.click_and_hold(source)

for i in range(5):
    actions.move_by_offset(xoffset=17, yoffset=0).perform()
    time.sleep(0.5)

actions.release()

更多的動作鏈操

5 執行JavaScript

對於某些操作,Selenium API並沒有提供。比如,下拉進度條,它可以直接模擬運行JavaScript,此時使用execute_script()方法即可實現,代碼如下:

from selenium import webdriver
 
browser = webdriver.Chrome()
browser.get(https://www.jd.com/)
browser.execute_script(window.scrollTo(0, document.body.scrollHeight))
browser.execute_script(alert("123"))

6 獲取節點信息

通過page_source屬性可以獲取網頁的源代碼,接著就可以使用解析庫(如正則表達式、Beautiful Soup、pyquery等)來提取信息了。

不過,既然Selenium已經提供了選擇節點的方法,返回的是WebElement類型,那麽它也有相關的方法和屬性來直接提取節點信息,如屬性、文本等。這樣的話,我們就可以不用通過解析源代碼來提取信息了,非常方便。

from selenium import webdriver
from selenium.webdriver.common.by import By #按照什麽方式查找,By.ID,By.CSS_SELECTOR
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait #等待頁面加載某些元素

browser=webdriver.Chrome()

browser.get(https://www.amazon.cn/)

wait=WebDriverWait(browser,10)
wait.until(EC.presence_of_element_located((By.ID,cc-lm-tcgShowImgContainer)))

tag=browser.find_element(By.CSS_SELECTOR,#cc-lm-tcgShowImgContainer img)

#獲取標簽屬性,
print(tag.get_attribute(src))
#獲取標簽ID,位置,名稱,大小(了解)
print(tag.id)
print(tag.location)
print(tag.tag_name)
print(tag.size)


browser.close()

7 延時等待

在Selenium中,get()方法會在網頁框架加載結束後結束執行,此時如果獲取page_source,可能並不是瀏覽器完全加載完成的頁面,如果某些頁面有額外的Ajax請求,我們在網頁源代碼中也不一定能成功獲取到。所以,這裏需要延時等待一定時間,確保節點已經加載出來。這裏等待的方式有兩種:一種是隱式等待,一種是顯式等待。

隱式等待:

當使用隱式等待執行測試的時候,如果Selenium沒有在DOM中找到節點,將繼續等待,超出設定時間後,則拋出找不到節點的異常。換句話說,當查找節點而節點並沒有立即出現的時候,隱式等待將等待一段時間再查找DOM,默認的時間是0。示例如下:

from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By #按照什麽方式查找,By.ID,By.CSS_SELECTOR
from selenium.webdriver.common.keys import Keys #鍵盤按鍵操作
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait #等待頁面加載某些元素

browser=webdriver.Chrome()

#隱式等待:在查找所有元素時,如果尚未被加載,則等10秒
browser.implicitly_wait(10)

browser.get(https://www.baidu.com)
input_tag=browser.find_element_by_id(kw)
input_tag.send_keys(美女)
input_tag.send_keys(Keys.ENTER)

contents=browser.find_element_by_id(content_left) #沒有等待環節而直接查找,找不到則會報錯
print(contents)

browser.close()

顯示等待:

隱式等待的效果其實並沒有那麽好,因為我們只規定了一個固定時間,而頁面的加載時間會受到網絡條件的影響。這裏還有一種更合適的顯式等待方法,它指定要查找的節點,然後指定一個最長等待時間。如果在規定時間內加載出來了這個節點,就返回查找的節點;如果到了規定時間依然沒有加載出該節點,則拋出超時異常。

from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By #按照什麽方式查找,By.ID,By.CSS_SELECTOR
from selenium.webdriver.common.keys import Keys #鍵盤按鍵操作
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait #等待頁面加載某些元素

browser=webdriver.Chrome()
browser.get(https://www.baidu.com)


input_tag=browser.find_element_by_id(kw)
input_tag.send_keys(美女)
input_tag.send_keys(Keys.ENTER)


#顯式等待:顯式地等待某個元素被加載
wait=WebDriverWait(browser,10)
wait.until(EC.presence_of_element_located((By.ID,content_left)))

contents=browser.find_element(By.CSS_SELECTOR,#content_left)
print(contents)


browser.close()

關於等待條件,其實還有很多,比如判斷標題內容,判斷某個節點內是否出現了某文字等。更多查看

8 前進和後退

平常使用瀏覽器時都有前進和後退功能,Selenium也可以完成這個操作,它使用back()方法後退,使用forward()方法前進。示例如下:

# 模擬瀏覽器的前進後退
import time
from selenium import webdriver
 
browser=webdriver.Chrome()
browser.get(https://www.baidu.com)
browser.get(https://www.taobao.com)
browser.get(http://www.sina.com.cn/)
 
browser.back()
time.sleep(10)
browser.forward()
browser.close()

9 Cookies

使用Selenium,還可以方便地對Cookies進行操作,例如獲取、添加、刪除Cookies等。示例如下:

from selenium import webdriver
 
browser = webdriver.Chrome()
browser.get(https://www.zhihu.com/explore)
print(browser.get_cookies())
browser.add_cookie({name: name, domain: www.zhihu.com, value: germey})
print(browser.get_cookies())
browser.delete_all_cookies()
print(browser.get_cookies())

10 異常處理

from selenium import webdriver
from selenium.common.exceptions import TimeoutException,NoSuchElementException,NoSuchFrameException

try:
    browser=webdriver.Chrome()
    browser.get(http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable)
    browser.switch_to.frame(iframssseResult)

except TimeoutException as e:
    print(e)
except NoSuchFrameException as e:
    print(e)
finally:
    browser.close()

案例講解

滑動驗證碼的破解

技術分享圖片
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait # 等待元素加載的
from selenium.webdriver.common.action_chains import ActionChains  #拖拽
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from selenium.webdriver.common.by import By
from PIL import Image
import requests
import re
import random
from io import BytesIO
import time


def merge_image(image_file,location_list):
    """
     拼接圖片
    """
    im = Image.open(image_file)
    im.save(code.jpg)
    new_im = Image.new(RGB,(260,116))
    # 把無序的圖片 切成52張小圖片
    im_list_upper = []
    im_list_down = []
    # print(location_list)
    for location in location_list:
        # print(location[‘y‘])
        if location[y] == -58: # 上半邊
            im_list_upper.append(im.crop((abs(location[x]),58,abs(location[x])+10,116)))
        if location[y] == 0:  # 下半邊
            im_list_down.append(im.crop((abs(location[x]),0,abs(location[x])+10,58)))

    x_offset = 0
    for im in im_list_upper:
        new_im.paste(im,(x_offset,0))  # 把小圖片放到 新的空白圖片上
        x_offset += im.size[0]

    x_offset = 0
    for im in im_list_down:
        new_im.paste(im,(x_offset,58))
        x_offset += im.size[0]
    #new_im.show()
    return new_im

def get_image(driver,div_path):
    ‘‘‘
    下載無序的圖片  然後進行拼接 獲得完整的圖片
    :param driver:
    :param div_path:
    :return:
    ‘‘‘
    background_images = driver.find_elements_by_xpath(div_path)
    location_list = []
    for background_image in background_images:
        location = {}
        result = re.findall(background-image: url\("(.*?)"\); background-position: (.*?)px (.*?)px;,background_image.get_attribute(style))
        # print(result)
        location[x] = int(result[0][1])
        location[y] = int(result[0][2])

        image_url = result[0][0]
        location_list.append(location)
    image_url = image_url.replace(webp,jpg)
    # ‘替換url http://static.geetest.com/pictures/gt/579066de6/579066de6.webp‘
    image_result = requests.get(image_url).content
    image_file = BytesIO(image_result) # 是一張無序的圖片
    image = merge_image(image_file,location_list)

    return image


def get_track(distance):

    # 初速度
    v=0
    # 單位時間為0.2s來統計軌跡,軌跡即0.2內的位移
    t=0.2
    # 位移/軌跡列表,列表內的一個元素代表0.2s的位移
    tracks=[]
    tracks_back=[]
    # 當前的位移
    current=0
    # 到達mid值開始減速
    mid=distance * 7/8
    print("distance",distance)
    global random_int
    random_int=8
    distance += random_int # 先滑過一點,最後再反著滑動回來

    while current < distance:
        if current < mid:
            # 加速度越小,單位時間的位移越小,模擬的軌跡就越多越詳細
            a = random.randint(2,5)  # 加速運動
        else:
            a = -random.randint(2,5) # 減速運動
        # 初速度
        v0 = v
        # 0.2秒時間內的位移
        s = v0*t+0.5*a*(t**2)
        # 當前的位置
        current += s
        # 添加到軌跡列表
        if round(s)>0:
            tracks.append(round(s))
        else:
            tracks_back.append(round(s))


        # 速度已經達到v,該速度作為下次的初速度
        v= v0+a*t

        print("tracks:",tracks)
        print("tracks_back:",tracks_back)
        print("current:",current)

    # 反著滑動到大概準確位置

    tracks_back.append(distance-current)
    tracks_back.extend([-2,-5,-8,])

    return tracks,tracks_back


def get_distance(image1,image2):
    ‘‘‘
       拿到滑動驗證碼需要移動的距離
      :param image1:沒有缺口的圖片對象
      :param image2:帶缺口的圖片對象
      :return:需要移動的距離
      ‘‘‘
    # print(‘size‘, image1.size)

    threshold = 50
    for i in range(0,image1.size[0]):  # 260
        for j in range(0,image1.size[1]):  # 160
            pixel1 = image1.getpixel((i,j))
            pixel2 = image2.getpixel((i,j))
            res_R = abs(pixel1[0]-pixel2[0]) # 計算RGB差
            res_G = abs(pixel1[1] - pixel2[1])  # 計算RGB差
            res_B = abs(pixel1[2] - pixel2[2])  # 計算RGB差
            if res_R > threshold and res_G > threshold and res_B > threshold:
                return i  # 需要移動的距離


def main_check_code(driver,element):
    """
    拖動識別驗證碼
    :param driver:
    :param element:
    :return:
    """

    login_btn = driver.find_element_by_class_name(js-login)
    login_btn.click()

    element = WebDriverWait(driver, 30, 0.5).until(EC.element_to_be_clickable((By.CLASS_NAME, gt_guide_tip)))
    slide_btn = driver.find_element_by_class_name(gt_guide_tip)
    slide_btn.click()



    image1 = get_image(driver, //div[@class="gt_cut_bg gt_show"]/div)
    image2 = get_image(driver, //div[@class="gt_cut_fullbg gt_show"]/div)
    # 圖片上 缺口的位置的x坐標

    # 2 對比兩張圖片的所有RBG像素點,得到不一樣像素點的x值,即要移動的距離
    l = get_distance(image1, image2)
    print(l=,l)

    # 3 獲得移動軌跡
    track_list = get_track(l)
    print(第一步,點擊滑動按鈕)
    element = WebDriverWait(driver, 30, 0.5).until(EC.element_to_be_clickable((By.CLASS_NAME, gt_slider_knob)))
    ActionChains(driver).click_and_hold(on_element=element).perform()  # 點擊鼠標左鍵,按住不放
    import time
    time.sleep(0.4)
    print(第二步,拖動元素)
    for track in track_list[0]:
         ActionChains(driver).move_by_offset(xoffset=track, yoffset=0).perform()  # 鼠標移動到距離當前位置(x,y)
    #time.sleep(0.4)
    for track in track_list[1]:
          ActionChains(driver).move_by_offset(xoffset=track, yoffset=0).perform()  # 鼠標移動到距離當前位置(x,y)
          time.sleep(0.1)
    import time
    time.sleep(0.6)
    # ActionChains(driver).move_by_offset(xoffset=2, yoffset=0).perform()  # 鼠標移動到距離當前位置(x,y)
    # ActionChains(driver).move_by_offset(xoffset=8, yoffset=0).perform()  # 鼠標移動到距離當前位置(x,y)
    # ActionChains(driver).move_by_offset(xoffset=2, yoffset=0).perform()  # 鼠標移動到距離當前位置(x,y)
    print(第三步,釋放鼠標)
    ActionChains(driver).release(on_element=element).perform()
    time.sleep(1)

def main_check_slider(driver):
    """
    檢查滑動按鈕是否加載
    :param driver:
    :return:
    """
    while True:
        try :
            driver.get(https://www.huxiu.com/)
            element = WebDriverWait(driver, 30, 0.5).until(EC.element_to_be_clickable((By.CLASS_NAME, js-login)))
            if element:
                return element
        except TimeoutException as e:
            print(超時錯誤,繼續)
            time.sleep(5)

if __name__ == __main__:

    try:
        count = 3  # 最多識別3次
        driver = webdriver.Chrome()
        while count > 0:
            # 等待滑動按鈕加載完成
            element = main_check_slider(driver)
            main_check_code(driver,element)
            try:
                success_element = (By.CSS_SELECTOR, .gt_success)
                # 得到成功標誌
                success_images = WebDriverWait(driver,3).until(EC.presence_of_element_located(success_element))
                if success_images:
                    print(成功識別!!!!!!)
                    count = 0
                    import sys
                    sys.exit()
            except Exception as e:
                print(識別錯誤,繼續)
                count -= 1
                time.sleep(1)
        else:
            print(too many attempt check code )
            exit(退出程序)
    finally:
        driver.close()
View Code

爬蟲系列之第3章-Selenium模塊