以前在學校曾經用過request+xpath的方法作過一些爬蟲腳原本玩,從ios正式轉前端以後,出於興趣,我對爬蟲和反爬蟲又作了一些瞭解,而且作了一些爬蟲攻防的實踐。
咱們在爬取網站的時候,都會遵照 robots 協議,在爬取數據的過程當中,儘可能不對服務器形成壓力。但並非全部人都這樣,網絡上仍然會有大量的惡意爬蟲。對於網絡維護者來講,爬蟲的肆意橫行不只給服務器形成極大的壓力,還意味着本身的網站資料泄露,甚至是本身刻意隱藏在網站的隱私的內容也會泄露,這也就是反爬蟲技術存在的意義。
下面開始個人攻防實踐。css
先從最基本的requests開始。requests是一經常使用的http請求庫,它使用python語言編寫,能夠方便地發送http請求,以及方便地處理響應結果。這是一段抓取豆瓣電影內容的代碼。前端
import requests from lxml import etree url = 'https://movie.douban.com/subject/1292052/' data = requests.get(url).text s=etree.HTML(data) film=s.xpath('//*[@id="content"]/h1/span[1]/text()') print(film)
代碼的運行結果,會輸出node
['肖申克的救贖 The Shawshank Redemption']
這就是最簡單的完整的爬蟲操做,經過代碼發送網絡請求,而後解析返回內容,分析頁面元素,獲得本身須要的東西。
這樣的爬蟲防起來也很容易。使用抓包工具看一下剛纔發送的請求,再對比一下瀏覽器發送的正常請求。能夠看到,二者的請求頭差異很是大,尤爲requests請求頭中的user-agent,赫然寫着python-requests。這就等因而告訴服務端,這條請求不是真人發的。服務端只須要對請求頭進行一下判斷,就能夠防護這一種的爬蟲。
固然requests也不是這麼沒用的,它也支持僞造請求頭。以user-agent爲例,對剛纔的代碼進行修改,就能夠很容易地在請求頭中加入你想要加的字段,假裝成真實的請求,干擾服務端的判斷。python
import requests from lxml import etree user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers = { 'User-Agent' : user_agent } url = 'https://movie.douban.com/subject/1292052/' data = requests.get(url,headers=headers).text s=etree.HTML(data) film=s.xpath('//*[@id="content"]/h1/span[1]/text()') print(film)
現階段,就網絡請求的內容上來講,爬蟲腳本已經和真人同樣了,那麼服務器就要從別的角度來進行防護。
有兩個思路,第一個,分析爬蟲腳本的行爲模式來進行識別和防護。
爬蟲腳本一般會很頻繁的進行網絡請求,好比要爬取豆瓣排行榜top100的電影,就會連續發送100個網絡請求。針對這種行爲模式,服務端就能夠對訪問的 IP 進行統計,若是單個 IP 短期內訪問超過設定的閾值,就給予封鎖。這確實能夠防護一批爬蟲,可是也容易誤傷正經常使用戶,而且爬蟲腳本也能夠繞過去。
這時候的爬蟲腳本要作的就是ip代理,每隔幾回請求就切換一下ip,防止請求次數超過服務端設的閾值。設置代理的代碼也很是簡單。ios
import requests proxies = { "http" : "http://111.155.124.78:8123" # 代理ip } user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers = { 'User-Agent' : user_agent } url = 'https://movie.douban.com/subject/1292052/' res = requests.get(url = http_url, headers = headers, proxies = proxies)
第二個思路,經過作一些只有真人能作的操做來識別爬蟲腳本。最典型的就是以12306爲表明的驗證碼操做。
增長驗證碼是一個既古老又至關有效果的方法,可以讓不少爬蟲望風而逃。固然這也不是萬無一失的。通過多年的發展,用計算機視覺進行一些圖像識別已經不是什麼新鮮事,訓練神經網絡的門檻也愈來愈低,而且有許多開源的計算機視覺庫能夠無償使用。例如能夠在python中引入的tesseract,只要一行命令就能進行驗證碼的識別。web
import pytesseract from PIL import Image ... #get identifying code img ... im=Image.open('code.png') result = pytesseract.image_to_string(im)
再專業一點的話,還能夠加上一些圖像預處理的操做,好比降噪和二值化,提升驗證碼的識別準確率。固然要是驗證碼本來的干擾線, 噪點都比較多,甚至還出現了人類肉眼都難以辨別的驗證碼(12306),計算機識別的準確度也會相應降低一些。但這種方法對於真實的人類用戶來講實在是太不友好了,屬因而殺敵一千自損八百的作法。後端
驗證碼的方法雖然防爬效果好,可是對於真人實在是不夠友好,開發人員在優化驗證操做的方面也下了不少工夫。現在,不少的人機驗證操做已經再也不須要輸入驗證碼,有些只要一下點擊就能夠完成,有些甚至不須要任何操做,在用戶不知道的狀況下就能完成驗證。這裏其實包含了不一樣的隱形驗證方法。
有些隱形驗證採用了基於JavaScript的驗證手段。這種方法主要是在響應數據頁面以前,先返回一段帶有JavaScript 代碼的頁面,用於驗證訪問者有無 JavaScript 的執行環境,以肯定使用的是否是瀏覽器。例如淘寶、快代理這樣的網站。一般狀況下,這段JS代碼執行後,會發送一個帶參數key的請求,後臺經過判斷key的值來決定是響應真實的頁面,仍是響應僞造或錯誤的頁面。由於key參數是動態生成的,每次都不同,難以分析出其生成方法,使得沒法構造對應的http請求。
有些則更加高級一些,經過檢測出用戶的瀏覽習慣,好比用戶經常使用 IP 或者鼠標移動狀況等,而後自行判斷人機操做。這樣就用一次點擊取代了繁瑣的驗證碼,並且實際效果還更好。
對於這類的反爬手段,就輪到selenium這個神器登場了。selenium是一個測試用的庫,能夠調用瀏覽器內核,也就是說能夠打開一個真的瀏覽器,而且能夠手動進行操做。那就完美能夠完美應對上述兩種隱形驗證手段。
selenium的使用也很簡單,能夠直接對頁面元素進行操做。配合根據頁面元素等待頁面加載完成的時延操做,基本上把人瀏覽頁面的過程整個模擬了一遍。並且由於selenium會打開一個瀏覽器,因此若是有點擊的驗證操做,通常這種操做也就在開始的登陸頁會有,人來點一下就是了。瀏覽器
from selenium import webdriver browser = webdriver.Chrome() browser.get("url") #得到dom節點 node = browser.find_elements_by_id("id") nodes = browser.find_elements_by_css_selector("css-selector") nodelist = browser.find_elements_by_class_name("class-name") #操做dom元素 browser.find_element_by_xpath('xpath-to-dom').send_keys('password') browser.find_element_by_xpath('xpath-to-dom').click() #等待頁面加載 locator = (By.CLASS_NAME, 'page-content') try: WebDriverWait(driver, 10, 0.5).until(EC.presence_of_element_located(locator)) finally: driver.close()
這麼看起來彷彿selenium就是無解的了,實際上並非。較新的智能人機驗證已經把selenium列入了針對目標中,使得即便手動點擊進行人機驗證也會失敗。這是怎麼作的呢?事實上,這是對於瀏覽器頭作了一次檢測。若是打開selenium的瀏覽器控制檯輸入window.navigator.webdriver
,返回值會是「true」。而在正常打開的瀏覽器中輸入這段命令,返回的會是「undefined」。在這裏,我找到了關於webdriver的描述:navigator.webdriver
)。能夠看到,webdriver屬性就是用來表示用戶代理是否被自動化控制,也就是這個屬性暴露了selenium的存在,人機驗證就沒法經過。並且,這個屬性仍是隻讀的,那麼就不能直接修改。固然硬要改也不是不行,經過修改目標屬性的get方法,達到屬性修改的目的。這時的webdriver屬性就是undefined了,而後再進行智能人機驗證,就能夠經過了。但這是治標不治本的,此時若是瀏覽器打開了新的窗口,或者點擊連接進入新的頁面,咱們會發現,webdriver又變回了true。固然,在每次打開新頁面後都輸入這段命令也能夠,不過事實上,雖然點擊驗證能夠被繞過去,但若是直接在頁面中加入檢測webdriver的JS代碼,一打開頁面就執行,那麼在你改webdriver以前,網站已經知道你究竟是不是爬蟲腳本了。服務器
道高一尺,魔高一丈。事實上即便這樣的反爬手段,也仍是能夠繞過去。在啓動Chromedriver以前,爲Chrome開啓實驗性功能參數excludeSwitches,它的值爲['enable-automation'],像這樣網絡
from selenium.webdriver import Chrome from selenium.webdriver import ChromeOptions option = ChromeOptions() option.add_experimental_option("excludeSwitches", ["enable-automation"]) driver = Chrome(options=option) driver.get('url')
這時候,無論怎麼打開新頁面,webdriver都會是undefined。對於這個級別的爬蟲腳本,還不知道要怎麼防護,檢測的成本過高了。
不過,事實上,換個思路,還有一些有趣的反爬方法。好比貓眼電影的實時票房和起點中文網,在瀏覽器裏能看到內容,可是打開網頁代碼一看,全變成了方塊。這就是一種很好地反爬方法,簡單地說就是後端搭一套字體生成接口,隨機生成一個字體,而後返回這個字體文件,以及各個數字的unicode對應關係,前端頁面進行數據填充,就能夠隱藏敏感數據。
還有些充分利用了css進行的反爬,腦洞更大。搞兩套數據,顯示的時候用css定位將真實的覆蓋假的。或者搞一些干擾字符,顯示的時候將opacity設爲0進行隱藏。甚至還有設置一個背景,讓它和顯示的內容拼接在一塊兒,成爲真正要展現的內容。這些都是很是有趣的反爬手段。不過對於前端來講,畢竟全部的數據和代碼,都給到了客戶端,爬蟲腳本老是能想出辦法來爬到數據,各類反爬的手段,也就是加大爬數據的難度而已。主要仍是要自覺,拒絕惡意爬蟲。