使用slenium+chromedriver實現無敵爬蟲

水木·圳烜發表於2018-02-06

@概述

  • 通常各大網站的後臺都會有一定的反爬機制,既為了資料安全,也為了減小伺服器壓力
  • 通常反爬的手段的方向,都是識別非瀏覽器客戶端,而selenium所做的事情,恰恰是驅動真正的瀏覽器去執行請求和操作,只不過訊號不是來源於滑鼠,而是來源於selenium的API(selenium本是一個自動化的測試工具)
  • 自然人使用者能做的一切,selenium幾乎都驅動瀏覽器取做,無論是否有介面,包括輸入、點選、滑動,等等
  • 然而到底是滑鼠操作的瀏覽器發起的請求還是API,對於服務端來說,是沒有任何差別的
  • 所以說:做人難,做男人難,做一個後臺開發的男人難上加難,讓我們開始對其實施蹂躪吧

@一些掌故

  • 早些的時候流行的組合並不是selenium+chrome瀏覽器驅動,而是selenium+phantomjs
  • phantomjs是一款沒有介面的瀏覽器,業界稱作無頭瀏覽器(headless),由於沒有介面和渲染,其執行速度要大大優於有介面的瀏覽器,這恰恰是爬蟲喜歡的,因此紅極一時
  • 後來chrome和火狐推出了無頭模式,且執行速度很流暢,phantomjs已然壽終正寢,因此我們表過不提

    @開發環境的搭建(基於ubuntu)

  • 安裝selenium:sudo pip install selenium

  • 如果沒有則安裝chrome瀏覽器(儘量更新到58以上):http://www.linuxidc.com/Linux/2016-05/131096.htm
  • 安裝chrome瀏覽器驅動(注意最新版本尾號是29而非9):https://www.cnblogs.com/Lin-Yi/p/7658001.html

@導包

# 匯入selenium的瀏覽器驅動介面
from selenium import webdriver

# 要想呼叫鍵盤按鍵操作需要引入keys包
from selenium.webdriver.common.keys import Keys

# 匯入chrome選項
from selenium.webdriver.chrome.options import Options

@第一個程式:抓取頁面內容,生成頁面快照

# 建立chrome瀏覽器驅動,無頭模式(超爽)
chrome_options = Options()
chrome_options.add_argument('--headless')
driver = webdriver.Chrome(chrome_options=chrome_options)

# 載入百度頁面
driver.get("http://www.baidu.com/")
# time.sleep(3)

# 獲取頁面名為wrapper的id標籤的文字內容
data = driver.find_element_by_id("wrapper").text
print(data)

# 列印頁面標題 "百度一下,你就知道"
print(driver.title)

# 生成當前頁面快照並儲存
driver.save_screenshot("baidu.png")

# 關閉瀏覽器
driver.quit()

@模擬使用者輸入和點選搜尋,跟真人操作一樣!

    # get方法會一直等到頁面被完全載入,然後才會繼續程式,通常測試會在這裡選擇 time.sleep(2)
    driver.get("http://www.baidu.com/")

    # id="kw"是百度搜尋輸入框,輸入字串"程式猿"
    driver.find_element_by_id("kw").send_keys(u"程式猿")

    # id="su"是百度搜尋按鈕,click() 是模擬點選
    driver.find_element_by_id("su").click()
    time.sleep(3)

    # 獲取新的頁面快照
    driver.save_screenshot("程式猿.png")

    # 列印網頁渲染後的原始碼
    print(driver.page_source)

    # 獲取當前頁面Cookie
    print(driver.get_cookies())

    # ctrl+a 全選輸入框內容
    driver.find_element_by_id("kw").send_keys(Keys.CONTROL, 'a')

    # ctrl+x 剪下輸入框內容
    driver.find_element_by_id("kw").send_keys(Keys.CONTROL, 'x')

    # 輸入框重新輸入內容
    driver.find_element_by_id("kw").send_keys("美女")

    # 模擬EnterEnter鍵
    driver.find_element_by_id("su").send_keys(Keys.RETURN)
    time.sleep(3)

    # 清除輸入框內容
    driver.find_element_by_id("kw").clear()

    # 生成新的頁面快照
    driver.save_screenshot("美女.png")

    # 獲取當前url
    print(driver.current_url)

    # 關閉瀏覽器
    driver.quit()

@模擬使用者登入

    # 載入微博登入頁
    driver.get("http://passport.weibo.cn/signin/login?entry=mweibo&r=http%3A%2F%2Fweibo.cn%2F&backTitle=%CE%A2%B2%A9&vt=")
    time.sleep(3)

    # 找到輸入框,鍵入使用者名稱和密碼
    driver.find_element_by_id('loginName').send_keys("worio.hainan@163.com")
    driver.find_element_by_id('loginPassword').send_keys("Qq94313805")

    # 點選登入按鈕
    driver.find_element_by_id('loginAction').click()
    time.sleep(3)

    # 快照顯示已經成功登入
    print(driver.save_screenshot('jietu.png'))
    driver.quit()

@使用cookies登入

    # 載入知乎主頁,檢視快照知此時處於未登入狀態
    driver.get("https://www.zhihu.com")
    time.sleep(1)
    print(driver.save_screenshot("zhihu_nocookies.png"))

    # 操作瀏覽器登入知乎並抓包cookies
    zhihu_cookies = {
        # 'aliyungf_tc' : 'AQAAAAR4YFOeswAAnLFJcVRd4MKOTTXu',
        'l_n_c': '1',
        'q_c1': '8572377703ba49138d30d4b9beb30aed|1514626811000|1514626811000',
        'r_cap_id': 'MTc5M2Y0ODUzMjc0NDMzNmFkNTAzZDBjZTQ4N2EyMTc=|1514626811|a97b2ab0453d6f77c6cdefe903fd649ee8531807',
        'cap_id': 'YjQyZTEwOWM4ODlkNGE1MzkwZTk3NmI5ZGU0ZTY2YzM=|1514626811|d423a17b8d165c8d1b570d64bc98c185d5264b9a',
        'l_cap_id': 'MGE0NjFjM2QxMzZiNGE1ZWFjNjhhZmVkZWQwYzBkZjY=|1514626811|a1eb9f2b9910285350ba979681ca804bd47f12ca',
        'n_c': '1',
        'd_c0': 'AKChpGzG6QyPThyDpmyPhXaV-B9_IYyFspc=|1514626811',
        '_xsrf': 'ed7cbc18-03dd-47e9-9885-bbc1c634d10f',
        'capsion_ticket': '2|1:0|10:1514626813|14:capsion_ticket|44:NWY5Y2M0ZGJiZjFlNDdmMzlkYWE0YmNjNjA4MTRhMzY=|6cf7562d6b36288e86afaea5339b31f1dab2921d869ee45fa06d155ea3504fe1',
        '_zap': '3290e12b-64dc-4dae-a910-a32cc6e26590',
        'z_c0': '2|1:0|10:1514626827|4:z_c0|92:Mi4xYm4wY0FRQUFBQUFBb0tHa2JNYnBEQ1lBQUFCZ0FsVk5DNjAwV3dCb2xMbEhxc1FTcEJPenpPLWlqSS1qNm5KVEFR|d89c27ab659ba979a977e612803c2c886ab802adadcf70bcb95dc1951bdfaea5',
        '__utma': '51854390.2087017282.1514626889.1514626889.1514626889.1',
        '__utmb': '51854390.0.10.1514626889',
        '__utmc': '51854390',
        '__utmz': '51854390.1514626889.1.1.utmcsr=zhihu.com|utmccn=(referral)|utmcmd=referral|utmcct=/',
        '__utmv': "51854390.100--|2=registration_date=20150408=1'3=entry_date=20150408=1",
    }

    # 將使用者登入產生的cookies全部新增到當前會話
    for k, v in zhihu_cookies.items():
        driver.add_cookie({'domain': '.zhihu.com', 'name': k, 'value': v})

    # 再次訪問知乎主頁並拍照,此時已經是登入狀態了
    driver.get("https://www.zhihu.com")
    time.sleep(3)
    print(driver.save_screenshot("zhihu_cookies.png"))

    # 退出瀏覽器
    driver.quit()

@模擬滾動條的滾動(這個用常規的爬蟲很難實現)

    # 載入知乎主頁
    driver.get("https://www.zhihu.com")
    time.sleep(1)

    # 載入本地cookies實現登入
    for k, v in zhihu_cookies.items():
        driver.add_cookie({'domain': '.zhihu.com', 'name': k, 'value': v})

    # 以登入狀態再次發起訪問
    driver.get("https://www.zhihu.com")
    time.sleep(3)

    # 將頁面滾動到最後,執行多次
    for i in range(3):
        js = "var q=document.documentElement.scrollTop=10000"
        driver.execute_script(js)
        time.sleep(3)

    # 截圖並退出,頁面側邊滾動條已經下滑了許多畫素
    print(driver.save_screenshot("zhihu_scroll.png"))
    driver.quit()

@一邊滾動一邊載入

  • 唯品會首頁的女裝圖片,是一邊滾動一邊進行ajax非同步載入的
  • 這個靠常規的抓包實現起來很麻煩
  • 使用selenium我們只需模擬使用者多次下拉滾動條,一段時間之後再重新拿取渲染好的頁面原始碼,就可以像爬取靜態頁面那樣去爬取圖片了
  • 類似這種操作,其實質就是開掛,是幾乎無法防守的
    # 唯品會女裝圖片連結無法直接獲得
    # 請求唯品會頁面
    driver.get("https://category.vip.com/search-3-0-1.html?q=3|30036||&rp=30074|30063&ff=women|0|2|2&adidx=1&f=ad&adp=65001&adid=326630")
    time.sleep(3)

    # 逐漸滾動瀏覽器視窗,令ajax逐漸載入
    for i in range(1, 10):
        js = "var q=document.body.scrollTop=" + str(500 * i)  # PhantomJS
        js = "var q=document.documentElement.scrollTop=" + str(500 * i)  # 谷歌 和 火狐

        driver.execute_script(js)
        print('=====================================')
        time.sleep(3)

    # 拿到頁面原始碼
    html = etree.HTML(driver.page_source)
    all_img_list = []

    # 得到所有圖片
    img_group_list = html.xpath("//img[contains(@id,'J_pic')]")
    # img_group_list = html.xpath("//img[starts-with(@id,'J_pic')]")
    # 正規表示式匹配
    # img_group_list = html.xpath(r'//img[re:match(@id, "J_pic*")]',namespaces={"re": "http://exslt.org/regular-expressions"})

    # 收集所有圖片連結到列表
    for img_group in img_group_list:
        img_of_group = img_group.xpath(".//@data-original | .//@data-img-back | .//@data-img-side")
        print(img_of_group)
        all_img_list.append('\n'.join(img_of_group) + '\n')

    # 將收集到的資料寫入檔案
    with open('vip.txt', 'w', encoding='utf-8') as f:
        f.write('\n'.join(all_img_list))

    # 退出瀏覽器
    driver.quit()

相關文章