聽說這個爬蟲面試題很難?看完你就知道怎麼做了

2022-01-30 NightTeam

遷移自我的知乎專欄《手把手教你寫爬蟲》

此面試題網站已被作者關閉,但處理方式依然可以拿來參考。

最近(2019年6月)有一個爬蟲面試題(http://shaoq.com:7777/exam)在圈內看起來挺火的,經常在各個爬蟲群裡看到它被提到,而幾乎所有提到這個面試題的人在題目限制的條件下就不知道該怎麼辦了,但這題目其實真的並不難,甚至可以說應該只是為了在招人時再過濾一遍只會寫解析,拿著Selenium和代理池硬懟的人罷了(之前招人的時候見過很多,甚至有很多2-3年經驗還處於這個水平)。

造成爬蟲圈子現在這個情況的原因我覺得可能是因為各種爬蟲書籍/培訓班/網課都沒有講到過關於逆向方面的知識,他們的教學更傾向於Python語法、正則表達式、XPath這些非常基礎的東西和常見爬蟲框架/工具的簡單用法,而讀者/學員學完之後的水平充其量也就只能爬爬豆瓣之類的簡單網站,面對有點簡單反爬的就一臉懵逼,只能拿著Selenium和代理池硬懟。那麼為了提升一下爬蟲圈內的平均水平,寫點別人沒講或者不想講的東西並分享出來就很有必要了,這個專欄也是因此而生的。

扯遠了,開始講這個面試題吧,請站穩扶好,老司機要開始飆車了。首先做好以下準備,等會兒會用上,括號內是文中所使用的工具名或版本號:

瀏覽器(Chrome)

Fiddler/Charles之類的抓包工具(Fiddler)

Python和JavaScript的IDE或編輯器(Pycharm + WebStorm)

Python3.x和NodeJS(Python3.6.5 + NodeJS10.15.1)

Python庫:pyexecjs、aiohttp、aiohttp_requests、lxml(最新版本)

NodeJS庫:jsdom(最新版本)

準備好了之後就可以開始了,先抓個包看看題目是啥樣的。

先是一個跳轉頁。

然後會跳轉到內容頁,已經可以看到需要的文字了。

看起來好像只需要拿到跳轉後的HTML就行了?實際並不是,這裡可以看到上面這一行字裡除了「python」和「題」以外,其他的標籤在HTML中都是沒有文本內容的,對應的內容全都顯示在了右邊的CSS樣式中。

但是抓包的時候也沒看到CSS,是不是把CSS嵌在了HTML中呢?打開這個HTML的代碼看看,一大坨加密的JS一眼可見,也並沒有看到style標籤,顯然這個CSS是通過JS生成後加進去的。

很多人對JS逆向毫無了解,看到這裡已經懵逼了,碰到這種情況還不讓用Selenium之類的工具,又要爬到內容,似乎完全沒辦法了啊。那應該怎麼辦呢?其實很簡單,看完這篇文章你就知道應該怎麼做了,下面我將用代碼對這個面試題的考點逐個擊破(完整代碼將在文章結尾處放出)。

先請求一下這個URL看看會返回什麼結果。

提示:aiohttp_requests庫能讓你在用aiohttp進行請求時能使用類似於requests庫的語法,並且能正常使用session功能,而不需要寫一層接一層的async with xxxxxxx。

請求返回的結果是最開始的跳轉頁,距離真正的內容頁還差一點距離。

斷點斷下來看看resp,已經可以看到一個名為session的Cookie被set了,之前抓包的時候也是有看到伺服器返回這個Cookie的。那麼直接帶著這個Cookie再次請求是不是就可以拿到那個內容頁了呢?我們將代碼改一下,對這個URL再次請求:

咦?有了這個Cookie之後的請求怎麼還是返回這個跳轉頁呢?

現在再回到抓包工具中仔細看看,是不是發現抓到的瀏覽器請求裡這兩個請求之間是有一堆圖片的,且第二次請求時,請求頭裡的東西也沒有啥變化?

是這樣的,其實它的服務端對客戶端是否加載了圖片進行了判斷,如果客戶端沒有加載圖片就直接開始取內容,那除了網速慢和刻意關閉了圖片的人以外,基本就可以確定是爬蟲了,所以這是一個簡單粗暴的反爬措施。

知道了這個考點之後就很簡單了,取出圖片的URL並和瀏覽器一樣進行請求就好了。再次修改代碼:

提示:因為這裡重用host部分的次數很多,我把host部分寫成了一個常量。

提示:f"{HOST}{image.get('src')}"是format string,python3的一個語法糖,最開始有這個語法糖的版本已經記不清了,如果你發現這段代碼在你的環境裡無法運行,可以把這裡改成"{}{}".format(HOST, image.get("src"))。

提示:asyncio.gather是asyncio庫的並發執行任務函數,傳入的是一個協程函數列表,所以裡面的requests.get不需要加await。

可以看到已經取到了內容頁的HTML,第一個考點我們已經跨過去了,接下來要想想怎麼拿到那個CSS的部分了。

那麼這個JS要怎麼處理呢?其實我們可以使用Python調用JS的方式去執行它頁面中的那段代碼,從而生成出標籤中對應文字部分的CSS。這裡推薦使用pyexecjs庫 + NodeJS來執行JS代碼,pyexecjs庫可以說是目前最好的Python執行JS代碼的庫了,另外一個比較常見的庫——PyV8,存在嚴重的內存洩漏BUG,不建議使用。

但是直接執行這段JS代碼是不可能有用的,我們還需要分析一下它的內容並按我們的使用方式修改一下。先把那段JS複製出來,打開JavaScript IDE/編輯器,並把它丟進去進行分析。

此處省略幾百行變量。

可以看到script標籤裡是一個匿名函數,傳入了一個document參數(函數內的uH),而實際這個匿名函數的主要流程代碼非常地少,只有兩個部分。

一個是開頭的這裡,

一個是靠近結尾位置的這裡。

第一部分沒有做什麼操作,只是創建了一個element,那麼核心部分應該就是第二部分,跳到它調用的jE_函數看看。

提示:WebStorm中可以用滑鼠中鍵或Ctrl+滑鼠左鍵點擊jE_,跳轉到對應的函數位置

這個jE_是這麼一坨看不懂的東西,看不懂就沒法搞了,怎麼辦呢?仔細看看上面那些用到的變量,是不是都是那一坨給變量賦值的地方出來的?那麼我們只需要把那一串加起來的東西寫成一個新的變量,打個斷點在下面然後運行一下,就能直接看出它是啥了。(更高級的加密JS在還原時需要用到AST解析庫和相關知識寫工具處理而非手動處理,這裡暫時還不需要用)

等一等,現在你還不能運行這段代碼,因為你沒有document,document是瀏覽器中特有的一個全局變量,而NodeJS中是不存在document這東西的,是不是覺得事情有點麻煩了起來?沒關係,問題不大,既然NodeJS中沒有,那我們就自己造一個,這裡使用jsdom庫來模擬瀏覽器中的dom部分,從而做到在NodeJS中使用document的操作。當然你如果想要自己造也是可以的,只需要按著報錯提示一個一個地實現這段JS代碼中調用的document.xxx即可。

這個jsdom庫的使用方式很簡單,只需要按照文檔上的說明導入jsdom,再new一個dom實例就可以了。

Basic usage

1const jsdom = require("jsdom");
2const { JSDOM } = jsdom;

 

To use jsdom, you will primarily use the JSDOM constructor, which is a named export of the jsdom main module. Pass the constructor a string. You will get back a JSDOM object, which has a number of useful properties, notably window:

1const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
2console.log(dom.window.document.querySelector("p").textContent); // "Hello world"

注意了,這裡的dom變量還並不是我們要的document變量,真正的document變量是dom.window.document,所以我們的代碼可以這樣寫:

執行一下看看效果。

原來上面的兩個參數分別是decodeURIComponent和%E6%81%AF%E6%95%B0%E9%9D%A2%E7%88%AC%E8%99%AB%E4%BF%A1%E6%8A%80%E5%88%9B%E8%AF%95%E7%A7%91,我們把後面那段一眼就能看出是經過urlencode的字符串還原一下看看。

嗯…其實就是頁面上的那句話了,只不過它是亂序的,我們接著往下執行看看它還做了什麼操作。

往下執行時報錯了,看起來是缺少了decodeURIComponent這個函數,那decodeURIComponent前面的那個uc_又是什麼呢?用同樣的方式可以看到,其實是window。

也就是說這句代碼還原成正常的樣子其實就是this.window.decodeURIComponent("%E6%81%AF%E6%95%B0%E9%9D%A2%E7%88%AC%E8%99%AB%E4%BF%A1%E6%8A%80%E5%88%9B%E8%AF%95%E7%A7%91"),而NodeJS的decodeURIComponent並不在this.window中,所以我們還是需要通過最開始造document的操作,再給它弄一個this.window.decodeURIComponent,代碼很簡單,改成這樣即可:

然後我們再執行一遍。

這次就能正常運行完畢了,但是我們要的東西去哪兒了呢?我們繼續往下打斷點看,vz_是亂序的文字,ti_是一個裡面只有數字的數組,SE_則只有兩個空字符串,KI_函數沒有進行賦值,而最後的return其實是沒有任何作用的,因為jE_在主流程中是最後一個被執行的函數,它返回的值賦給了xe_後並不會被使用。所以這裡似乎只有SE_和KI_比較可疑了,斷點進入給SE_賦值的Er_函數看看。

看來這個Er_函數並不會做什麼,那麼我們要的核心部分可以確定就是KI_這個函數了。接著追到下面的KI_函數。

這裡它又調用了一個叫Ks_的函數,跟著它繼續往下跳。

又是熟悉的Er_,還記得剛剛看到的嗎,它只是做了一個split操作而已,ti_是前面那個只有數字的數組,這裡的NL_只不過是按順序取了一個ti_裡的元素罷了,下面沒見過的BD_和Je_才是重點。

這裡斷下來看出BD_其實是一個取前面那串亂序字符串中其中一個文字的東西,繼續往下執行可以看到最終出來的YO_是一個字。

那麼Je_呢?繼續往下執行看看

Je_裡調用了ee_.insertRule,而ee_是前面被賦值的

所以實際上它是新建了一個element並往裡面寫了我們要的CSS。看到這裡,其實這個考點已經被破掉了,我們只需要讀出ee_返回給Python,就可以把那段文字給恢復出來了。

將JS代碼再修改一下:

然後我們試一下能不能用,記得將這裡的html字符串替換成你請求時返回的。(通常這種用到瀏覽器內特有的一些變量的JS都會埋下一些坑,建議讀者養成完全模擬瀏覽器環境的習慣,當然如果不怕遇到坑的話只給JS中需要用到的東西也可以,而這個題目本身並沒有這種坑,所以只弄一個空的dom並且魔改一下只傳入字符串和數組部分也能用。)

boom!CSS成功地被我們拿到手了,左邊的codexx對應右邊的content部分文字,與瀏覽器中的一模一樣,JS部分算是搞好了,我們要繼續寫我們的Python代碼,先把html=xxx開始的部分全部刪除掉,只保留上面導入包的部分和get_css這個函數的部分。

回到Python代碼部分,修改成調用JS得到CSS後處理一下CSS和HTML的對應關係,並取出所有文字內容再列印出來。

提示:這裡的dict(list)是一個Python的語法糖,可以快速地將[[1,2],[3,4]]轉成{1:2, 3:4}

提示:這裡可能會出現一個問題,之前直接用NodeJS執行沒問題的代碼,經過PyExecJS調用之後卻報錯了,這個問題似乎只有在Windows系統上才會出現,主要原因應該是Windows的編碼問題,碰到這種情況可以用Buffer.from(string).toString("base64");將返回的字符串編碼為Base64,在Python中再進行解碼。

執行一下看看,是不是已經拿到了需要的那行字了呢?

發送消息「shaoq爬蟲面試題」到我的公眾號【小周碼字】即可獲得文代碼下載地址~

這個時代各種東西變化太快,而網絡上的垃圾信息又很多,你需要有一個良好的知識獲取渠道,很多時候早就是一種優勢,還不趕緊關注我的公眾號並置頂/星標一波~ 

相關焦點

  • 看完這篇,再也不怕面試問樹了!
    一是因為樹的結構天然決定了它和遞歸聯繫緊密,很多樹相關的算法題都非常適合用遞歸來解;二是因為它的難度介於鍊表和圖之間,非常適合在 45 分鐘的面試裡進行考察,所以一場面試中遇到兩三輪問樹都是再正常不過的了。
  • 盛大公司PHP面試題
    這些基本的我想你都知道。說說看你對cookie安全方面有什麼考慮?比如他在client端會被篡改,你怎麼做?答:這個嘛。。。首先密碼和一些重要數據一定不能存。還有就是,把要存放的數據都加密了。比如用戶id,用戶名,角色什麼的。。。被打斷!MD5是不可逆的,你把用戶id加密了,怎麼比配啊?
  • Facebook 面試題全解析
    在這篇文章中,我來介紹一下你在面試Facebook的前端工程師職位時可能會遇到的面試題。在這個階段,我會首先與面試官澄清我對於該問題的假設。我需要知道,輸入是否保證為數組(沒有非法輸入或空輸入)。面試官說,我們不能做這種假設。另一個問題是:我們能否假設值一定為整數?儘管這個問題不太可能改變實現的細節,但在做出任何假設之前永遠要先澄清。我們假設嵌套在數組中的基本類型只有整數。
  • 刷完 LeetCode 是什麼水平?能拿到什麼水平的 offer?
    要知道題太多,有第三層次基本上面試都不會掛了,有這個時間還不如提升自己的系統設計能力,甚至工作和人際能力。綜合來說,「刷完」LeetCode,很多人只是停留在第一層次和第二層次,但如果大多數熱門題已經有第二層次了,也可勉強一戰,萬一混進大廠就舒服了對吧?
  • 你應該知道25道JavaScript面試題
    == 'undefined'));這題不難,IIFE 中的賦值過程其實是(賦值過程從右到左):(function(){ b = 3; var a = b;})();接下去就不難了,a 是局部變量,b 是全局變量。
  • 你面試穩了!通關LeetCode刷題完整攻略,省時又高效
    一般需要用到多個變量,一個變量指向頭結點(下圖中的current),另外一個(previous)則指向咱們剛剛處理完的那個節點。在這種固定步長的方式下,你需要先將當前節點(current)指向前一個節點(previous),再移動到下一個。同時,你需要將previous總是更新到你剛剛新鮮處理完的節點,以保證正確性。
  • Microsoft面試經歷與思考總結
    本科剛畢業的時候就聽說過谷歌、微軟等外企,當年只知道外企一般都要求英語口語和算法,感覺難度較大沒去深入了解和嘗試(尤其是英語口語沒有經過訓練真的有點難)。作為對比,國內的很多網際網路公司也不差,並且薪資待遇很有優勢,也沒想過要進外企。19年底脈脈上好多微軟同事發的內推廣告吸引了我的注意力,之後就開始了簡單的調研,發現了微軟這樣的外企的一些優點,所以有了嘗試進外企的想法。
  • 一道面試題考驗了你對Java的理解程度
    簡介最近看到一篇文章,關於一道面試題,先看一下題目,如下:public static void main(String[] args
  • 5道谷歌公司的面試智力題,你能答對多少?
    一些世界知名的企業在招聘時,可能會提供面試智力題,來篩選應聘者。以下5個問題,據說在谷歌的面試中,都曾用到過。看看你能答對多少?1.球的重量有8個球,其中1個比另外的要略重。在不用砝碼的前提下,你最少要稱幾次,才能找出這個球?
  • 【007期】JavaSE面試題(七):異常
    的提褲姐,今天這篇是面試系列的第七篇,主要總結了JavaSE中異常類相關面試題,在後續,會沿著第一篇開篇的知識線路一直總結下去,做到日更!如果我能做到百日百更,希望你也可以跟著百日百刷,一百天養成一個好習慣。
  • 5 道刁鑽的 Activity 生命周期面試題
    今天我們一起來看五道 Activity 生命周期的面試題,相信看完之後面試官再問到相關的問題,你就能胸有成竹了。A Activity 打開 B Activity 時都有哪些生命周期回調。彈出 Dialog 對生命周期有什麼影響我們知道,生命周期回調都是 AMS 通過 Binder 通知應用進程調用的;而彈出 Dialog、Toast、PopupWindow 本質上都直接是通過 WindowManager.addView() 顯示的(沒有經過 AMS),所以不會對生命周期有任何影響。
  • 微軟面試經歷和相關思考總結
    本科剛畢業的時候就聽說過谷歌、微軟等外企,當年只知道外企一般都要求英語口語和算法,感覺難度較大沒去深入了解和嘗試(尤其是英語口語沒有經過訓練真的有點難)。作為對比,國內的很多網際網路公司也不差,並且薪資待遇很有優勢,也沒想過要進外企。19年底脈脈上好多微軟同事發的內推廣告吸引了我的注意力,之後就開始了簡單的調研,發現了微軟這樣的外企的一些優點,所以有了嘗試進外企的想法。
  • 第四關:反爬蟲與分頁爬蟲(附練習題)
    查詢字符串用於信息的傳遞,伺服器通過它就能知道你想要什麼,從而給你返回對應的內容。比如你在知乎搜索 python,網址會變成 https://www.zhihu.com/search?type=content&q=python,後面的查詢字符串告訴伺服器你想要的是有關 python 的內容,於是伺服器便將有關 python 的內容返回給你了。
  • 準備面試的你,必看這8道JavaScript面試題,看清你的JavaScript基礎
    var聲明的變量,真正的數值初始化,是發生在你確定賦值的位置。同時,我們要知道,var聲明的變量是函數作用域的,也就是我們需要區分局部變量和全局變量,而let和const是塊作用域的。所以,如果你以處理條目的速度向這個隊列添加條目,那麼你就永遠在處理微任務。只有當微任務隊列為空時,事件循環才會重新渲染頁面。
  • 【每日一題】(39題)關於script標籤,你可能不知道的地方?
    【每日一題】(40題)關於script標籤,你可能不知道的地方?
  • 面試題:JS 獲取某月的天數
    回復交流,加入前端編程面試算法每日一題群面試官也在看的前端面試資料獲取某個月的天數
  • 2022北京、山東選調面試考情 | 附真題
    這一環節和同學們找工作面試的內容有些相似,可以提前做好準備。往年面試由各單位單獨組織,據津仕學員反饋,大部分會看簡歷,半結構化面試居多,以簡歷、學校、專業為主要的考察標準。有筆紙,無題卡,聽題作答,一般15分鐘3道題,也有20分鐘3道題。
  • 這個前端面試在搞事!
    金三銀四搞事季,前端這個近年的熱門領域,搞事氣氛特別強烈,我朋友小偉最近就在瘋狂面試,遇到了許多有趣的面試官,有趣的面試題,我來幫這個搞事 boy
  • 50道大廠筆經面經,面試前看完這些前輩的經驗,offer馬上來~
    講解筆試題的第二題和第四題最後我問有什麼需要提高的,面試管說我基礎還是挺不錯的,可以看出來是個好學生(竊喜),對一 些主流的技術還是需要了解一下,我又問現在主要使用的技術有哪些,想提前學一下,面試官詳細給我說了說,感覺面試官對我比較滿意。hr面,常規問題,非常友好。