項目航海–A2D産品的前世今生
撰寫時間2018年8月10日 星期五 上午10:59至2018年8月16日 星期四 下午16:40
本文不同于作者之前寫過的任何博文,是作者純粹的研發經曆和思路手書,以前的技術博文或多或少會摘取網絡上的産品思路和技術想法,而此文更像是一個從無到有的産品經曆,而非産品介紹或者技術講解。作者本身是一個從業兩年出頭三年不到,脫離技術小白不久卻離大牛之路遙遙無期的技術人員,有過六七個大型項目的研發經曆,帶過三四個完整的中小型項目,自認頭腦不算聰慧卻還靈活,受限于從業經曆相對簡單,時限較短,眼光還尚顯稚嫩,故而純手書的此文或多或少會存在一些技術問題和産品設計漏洞,希望閱讀此文的讀者可以諒解,如果有好的想法和提議,歡迎和作者進行交流,促進共同的進步,在此表達感謝!
—-前言
浏覽本文,首要必須搞清楚幾個核心問題,A2D是什麼,爬蟲技術應用于何處,工具本身的行為出發點在哪,産品的切入點為何?
要解答這些問題,作者思前想後很久,還是得先從産品的來源和最初的需求說起。
A2D這個命名來源于鄙公司2017年一個幾近失敗的項目,該項目采用仿制市面上流行的某python爬蟲工具,進行了java重寫和功能重塑,技術核心部分中對于驗證登入是做了如下處理:
1.對于不需要登入的站點,無需填寫cookie;
2.對于需要登入的系統,則是采用填寫登入資訊後從浏覽器去直接複制cookie,然後填進工具避開登入。核心爬取功能則是通過使用者填寫正規表達式,從頁面中截取出本次采集所需要的站點資料,然後進行資料持久化。
該産品存在三個巨大的設計問題:
1.如果采集的系統子產品過于龐大繁雜,會出現記憶體溢出,系統卡死,使用者體驗性差;
2.非技術人員如果并未經過系統教育訓練,不清楚如何從浏覽器擷取cookie并複制進工具,不知道如何判斷正則以及編寫正則,就無法擷取到想要的資料,使用者體驗過于複雜難懂,學習成本高。
3.由于工具本身隻獲得資料,導緻UI及其簡單,從産品設計的角度看,拓展延伸性差,市場推廣難度大。
基于高層對于該産品的滿意度低,研發中心對于A2D提出了新的産品研發要求,将需求進行了重新整理,并給出了以下目标:
1.項目針對主要目标為政務系統,需要繞行登入驗證(所采集系統提供登入使用者名密碼);
2.采集的資料為系統各個子產品的表格頭部資訊(所采集系統提供各個子產品的url);
3.對于采集下來的表頭資訊進行預處理(轉化為政務系統中的資訊資源資訊項);
4.嘗試進行自動化采集的設計(定時進行系統子產品表頭資訊的擷取,由于cookie存在過期的情況,采用cookie登入的爬蟲系統無法嘗試在無人使用的情況下采集資料)。
由此,才有了這個産品接下去的一系列故事。
在接到這個産品之初,作者以及作者的團隊成員從來沒有接觸過爬蟲這類概念,使得作者鬧了盲人摸象的設計錯誤,一度按照傳統政務系統的方式進行項目管理和産品設計,在業務架構和工具構成上犯了衆多常識性錯誤,再加上當時需求的緊迫度很高以及一些現實狀況(疲于應付上級的檢查)的幹擾,導緻沒有合理運用投石問路的方式進行前期項目風險評估,也沒有全方位針對現在主流的工具進行分析,使得産品設計初期的業務架構非常的脆弱不穩定,簡單無亮點。
起初經過不到兩天的緊張調研和讨論後,采用了最初的結構劃分和業務流程:
當時的系統設計分為三個子產品:
1.登入繞行子產品;
2.頁面爬取子產品;
3.内容處理子產品。

當作者以及團隊成員飽含信心開始進行功能細化,準備進行工具開發時,登時就被潑了一盆冷水,在登入繞行子產品,就受到了阻礙,自以為合理的登入繞行并沒有一個可行的方案,以下列舉出當時作者和副手商量的幾個嘗試性方案:
1.在工具的站點配置功能,将目标系統的登入頁内置進來,輸入登入資訊後請求登入,一旦成功則通過某種方式自動從浏覽器去讀取目前可用的cookie,儲存後完成登入的繞行;
2.避免擷取cookie的方式,爬取登入頁的使用者名密碼驗證碼的字段名,在配置頁面讓使用者輸入使用者名密碼驗證碼,試圖從請求後拼接username和password來進行後端模拟登入。
第二個方案從一開始就被确認為幾乎不可行,因為我們沒法真正繞過附骨之蛆的驗證碼,就是這個讨厭的驗證碼讓作者耗盡心思,對于爬蟲來說,驗證碼就像是殺蟲劑。而且,并非所有系統的登入采用的請求方式都是一樣的,我們是無法去操作我們的目标系統,這就導緻這個方案無疾而終了。
那就嘗試第一個方案吧,在當時的情況下做出了無奈之舉,作者和副手認真研究了市面上的八抓魚,發現它可以做到類似于這個方案的效果,這讓當時的我們欣喜若狂,但是這個歡喜持續了不到兩天就消失了,這個方式完美得避開了驗證碼,卻套入了浏覽器的大坑中,為了做安全控制,防止xss攻擊,浏覽器有内置的httponly屬性,而我們無法從前端更改它,甚至讀取不了此屬性,借用其他第三方的浏覽器插件強制更改後的成功更是讓當時的我們接受不了,甚至于當時的作者打算産品中加上一個插件下載下傳連結和使用手冊(這當然是開玩笑的,并不可行),對此,作者得出的結論是八抓魚與其說是一個爬蟲工具,更像是一個套着工具外殼的自制簡化版浏覽器,站在上帝的視角處理打開的網頁,執行想要的操作然後擷取想要的資料,幾乎很少存在限制(此處評論是來自于作者短淺的見解),而我們卻無法像它一樣去做一個自己的浏覽器,讓人徒呼奈何。之後我們又發現了一個問題,即使我們有了cookie中的jsessionid,也可能無法處理單使用者登入的系統,當然這個并沒有做深入嘗試,隻是提出了一個猜想,因為當時我們的測試遇到了一個和url相關的問題,并且這個問題讓人印象深刻。
問題是由于我們所測試系統是鄙公司開發的政務服務系統,我們開始模拟使用者使用環境下系統的可行性,但是,當我們嘗試第一個系統時,問題就出來了,當我們登入系統,人工選擇其中一個子產品想要擷取它的url時,發現浏覽器位址欄并未發生變化,我們明白這是系統本身的過濾器起到了作用,那麼從f12直接去找前端的子產品路徑總可以吧,結果讓人啼笑皆非,我們嘗試在浏覽器輸入得到的子產品完整url,結果卻得不到iframe中的頁面,這個結果讓作者一頭霧水,開發過程中我們往往會在浏覽器輸入get請求的方法測試自己的對外接口是否正确,為何會無結果,經過一系列艱苦卓絕(毫無頭緒進行中,作者以及副手都是java背景程式員出身,前端技能相對薄弱)地排查,發現原來我們所測試系統的前端在頁面渲染前,對字典資料進行了一次初始化加載,将其放在了iframe外,如果是整個系統是可以顯示的,但是如果是隻取iframe中的一部分,會導緻下拉選擇的控制器讀取字典值失敗,讓前端程式死在了胚胎中。這個排查結果大大打擊了當時的研發情緒,懷疑論一度彌漫開來。
如果前端有各種各樣奇奇怪怪的資料處理方式,而且各種架構進階速度那麼快,怎麼保證我們的産品有足夠的容錯率。直至今日,這個問題仍然是我思考的一個重大方向,且沒有啥突破性進展,隻能自己安慰自己,任何技術都是存在時效性的。
好在作者以及副手當時對于登入模拟方式,在交流中迸發了靈光一現,才使得項目程序出現了一絲轉機。經過一下午的研讨以及嘗試,我們提出了第三個方案,之後陸陸續續修正過,但是當時的大綱并未發生變化,方案如下:
3.同樣需要繞開cookie,先爬取登入頁,擷取到使用者名密碼驗證碼的精準定位,得到輸入的使用者名密碼驗證碼,然後采用PhantomJS模拟浏覽器進行機器模拟人為登入操作。
這裡簡單介紹下PhantomJS,PhantomJS是一個無界面的,可腳本程式設計的WebKit浏覽器引擎。它原生支援多種web 标準:DOM 操作,CSS選擇器,JSON,Canvas 以及SVGPhantomJS是一個無界面的,可腳本程式設計的WebKit浏覽器引擎。它原生支援多種web 标準:DOM 操作,CSS選擇器,JSON,Canvas 以及SVG。簡單來說,PhantomJS就是一個可程式設計的無頭浏覽器.而無頭浏覽器就是一個完整的浏覽器核心,包括了js解析引擎,渲染引擎,請求處理等,但是不包括顯示和使用者互動頁面的浏覽器。
介紹就到此,現在來說一下為何作者和副手會想到使用PhantomJS。為了對付惹人讨厭的驗證碼,那麼我們就需要先擷取到這個驗證碼的圖檔,無論是想要破解它或者做其他操作,這一步都是必不可少的(當然這句話可以再探讨下正确性),當時有一個同僚偶然間提示驗證碼我們并不一定需要繞過去,我們是否可以讓機器去進行識别填寫,比如使用其他的打碼平台。
這個思路讓我們眼前一亮,在提出這個想法之前,我們就曾經嘗試過驗證碼的破解,當然,破解驗證碼是老生常談的一個話題,驗證碼識别正常的處理方式有四種,1.人工智能2.模式識别3.機器視覺4.圖像處理。這裡簡單介紹一下模式識别的原理和過程:
1.圖像采集:驗證碼呢,就直接通過HTTP抓HTML,然後分析出圖檔的url,然後下載下傳儲存就可以了。
2.預處理:檢測是正确的圖像格式,轉換到合适的格式,壓縮,剪切出ROI,去除噪音,灰階化,轉換色彩空間這些。
3.檢測:主要是找出文字所在的主要區域。
4.前處理:“一般”要做文字的切割。
5.訓練:通過各種模式識别,機器學習算法,來挑選和訓練合适數量的訓練集。不是訓練的樣本越多越好。過學習,泛化能力差的問題可能在這裡出現。這一步不是必須的,有些識别算法是不需要訓練的。
6.識别:輸入待識别的處理後的圖檔,轉換成分類器需要的輸入格式,然後通過輸出的類和置信度,來判斷大概可能是哪個字母。識别本質上就是分類。
模式識别一般具有如圖所示結構,主要包括預處理、特征提取、訓練和識别幾個部分,驗證碼識别系統也不例外。根據驗證碼的特點,研究表明驗證碼識别的難點和重點在于預處理,即去除噪聲、幹擾和字元分割。
模式識别系統原理結構框圖
以下為作者嘗試過的驗證碼識别技術的具體細節。
1.1預處理
預處理是在驗證碼學習和識别之前對驗證碼圖檔進行前期處理,主要包括Jpeg解碼、二值化、去除噪聲和幹擾、字元分割、歸一化等。預處理的好壞極大地影響到識别性能,其中去除幹擾和字元分割尤為重要。
Jpeg解碼:并非所有驗證碼的識别都需要進行Jpeg解碼,但是本驗證碼圖檔為Jpeg格式,隻有先經過Jpeg解碼才能進行處理。采用GDI++進行Jpeg解碼,通過調用相關函數,使Jpeg格式的驗證碼圖檔首先轉換為bmp格式,暫存于臨時檔案中,然後再打開檔案進行處理。
二值化:二值化是将驗證碼圖檔的灰階值,以某一門檻值為限,轉換為0或255,即黑和白,以便于處理。因為本驗證碼的字元灰階偏白,是以在進行二值化之前先進行了灰階反置,即讓背景變白,讓字元變黑。二值化門檻值根據具體驗證碼分析所得,選擇合理的門檻值可消除很多背景、噪聲,同時不損傷字元筆畫。二值化門檻值設為120,可以很好的去除背景。
去除噪聲和幹擾:二值化後大部分噪聲都已經去除,但是還有很多幹擾線,不去除這些幹擾線就不可能進行後續的處理。通過對驗證碼幹擾線的特點進行分析,發現幹擾線很細,比字元筆畫細很多,幹擾線的高度為1個象素,幹擾線相交處的高度一般為2個象素。首先去除了所有高度為1的點線,然後去除了高度為2且與高度為1的象素相通的點線。
字元分割:字元分割即把驗證碼圖檔分割成單個的字元,這是有效識别的基礎。先利用種子填充算法得到幾個連通線,這樣未粘連的字元即可分割。對于粘連字元,還需要進一步分割。粘連字元的判别主要依據字元的點數和寬高比特征,大于某一門檻值則初步判斷為字元粘連。門檻值根據驗證碼特征統計分析所得。對于初步判斷為粘連的字元,為了防止判斷錯誤,還用預識别的方法進行了進一步判斷,如果能很好地識别也不認為是粘連字元。對于粘連字元的分割,本程式采用在垂直投影圖中找谷點的方法進行分割。
歸一化:驗證碼字元存在位置偏移、大小不一、旋轉不定的特點,如果不進行歸一化就不能很好識别。采用質心對齊和線性插值放大的方法進行歸一化,使字元變為統一的規格,以便于進行比對識别。
1.2特征提取
特征提取是從經過預處理的字元圖檔中,提取出一定維數的特征向量,這樣能提高字元比對和識别的存儲量和運算速度。字元有很多特征,選用合适的特征才能達到正确識别的目的。采用字元的區域密度的特征,即将字元分為5*5的25個方格區域,計算每個方格中的點數與字元總點數之比,以得到25維特征向量。該特征反映了字元筆畫的空間分布情況,并且對字元筆畫的粗細不敏感。
1.3 訓練
訓練是從訓練集驗證碼中提取出标準模闆,即标準特征庫的過程。采用大量驗證碼進行訓練,使每個字元都有200個左右的标準模闆。通過預處理和特征提取後,将訓練集驗證碼的特征向量存入檔案中。訓練時需要指明各驗證碼的正确值。為了不出現錯誤的标準模闆,對于分割時發現有字元粘連的訓練集驗證碼不加入模闆庫。
1.4 識别
采用最近鄰判别法進行識别,即将每個字元的特征向量與标準特征庫中的特征向量進行比對,與哪個字元的比對最好就判别為哪個字元。具體比對方法是計算特征向量之間的歐幾裡德距離,如以下公式所示:
Length=∑|CurrPara[i]−StdPara[i]|2 L e n g t h = ∑ | C u r r P a r a [ i ] − S t d P a r a [ i ] | 2
其中CurrPara為待識别字元的特征向量,StdPara為标準模闆的特征向量,i=0,1,2,…,24。待識别字元與哪個标準模闆的歐幾裡德距離最小,就表示和哪個模闆最比對,即判别為哪個字元。依次對驗證碼的各個字元進行識别,即可識别出驗證碼字元串。還傳回了識别結果的可信度reliability。可信度依據驗證碼的最大最近鄰距離計算所得,最大最近鄰距離指驗證碼中各字元最近鄰距離的最大值。根據多個門檻值對最大最近鄰距離進行分段,每個分段傳回一個可信度。最大最近鄰距離的門檻值根據驗證碼識别情況統計分析所得。Reliability=80表示識别結果80%可信。測試表明Reliability≥80時有很高的可信度。
介紹到此為止,當然作者并非此中高手,具體的算法也是從他人已有的demo中擷取的(在這找不到具體的出處,實在不好意思),但是這種方式經過我們的測試,正确率會随着字母或者數字驗證碼的位數發生巨大差異,4位的數字準确率能達到67%,而6位的隻有可憐的46%了,加上字母後更加悲慘,o和0的差別還有複雜橫線等幹擾都會大幅度幹擾命中率,在這種情況下,我們一直避開考慮驗證碼,但是事實證明我們過于偏執。在得到新的想法後,經過一番搜尋,我們找到了幾個打碼平台,掙碼以及超級鷹。
這裡首先介紹一下什麼叫做打碼,有個遠端用戶端把你所要打的碼通過某個平台用戶端來進行的人工代打,平台則負責分發和稽核。這種被稱之為人工智能的方式卻很好得解決了我們的問題,對于簡單驗證碼可以達到秒級回應和高到95%以上的正确率,經過再三的嘗試和研讨後,作者決定采用某一個打碼平台來解決我們某些破解不了驗證碼的問題。
最開始作者想使用掙碼,但是找了大半天,也沒有找到這個平台的javaAPI,而超級鷹這個平台有完整的javaAPI,那麼還猶豫什麼呢,超級鷹(chaojiying)的內建接入馬上提上了日程。
說了這麼多,回到最初的情形,為何會使用PhantomJS呢,原因很離奇,我們接入chaojiying後發現了一個很有意思的問題,我們直接取到的驗證碼圖檔發送給平台後,得到的結果都是錯誤的,那麼原因是什麼呢?因為圖檔太大了,為此作者和chaojiying的客服對于這個問題纏鬥了好久,現在回想起來還真是滑稽的問題啊。為了解決圖檔過大的問題,我們開始搜尋java的自動截屏程式(造輪子對于開發應用型産品來說是最後考慮的方式),結果不出意外的發現了PhantomJS,很多問題迎刃而解。
在使用PhantomJS的過程中,首先接觸到的是Selenium,這是個什麼東西呢?
Selenium是一系列基于Web的自動化工具,提供一套測試函數,用于支援Web自動化測試。函數非常靈活,能夠完成界面元素定位、視窗跳轉、結果比較。Selenium架構由多個工具組成,包括:Selenium IDE,Selenium RC,Selenium WebDriver和SeleniumRC。本文不詳細介紹這些具體工具的使用。畢竟這個架構絕大部分使用場景都在于自動化測試,本文隻簡述在爬蟲中的應用以及我們為何需要它。
本文閱讀到這,對于爬蟲,不知道大家有沒有發現有一個巨大的問題,就是動态網頁。什麼是動态網頁?在此簡單普及一下,就是網頁最後的内容不是一開始就是完整的,而是等代碼都加載完畢之後再執行一段js代碼來補充網頁的内容。比如說網頁最後的内容是A,最初的代碼是B,B裡面包含一段js代碼,這段代碼執行之後可以産生C,這樣B+C才等于A。而我們通過網絡通路網頁的url得到的隻是B。這樣做在很大程度上防止了一些簡單爬蟲的通路。
js代碼是依賴于浏覽器的,是以我們使用了PhantomJS,通過它我們可以執行通路網頁,執行js代碼,得到的網頁内容都可以通過自動化的程式得到。不過這些必須通過另一個架構來操作,也就是Selenium。在Selenium庫裡面有一個WebDriver的API。WebDriver有點像可以加載網站的浏覽器,但是它也可以像其他的Selector對象一樣用來查找頁面元素,與頁面上的元素進行互動(如:點選,發送文本等),以及執行其他動作來運作爬蟲。
到此,作者和副手終于有了出鞘的刀劍,可以大殺四方了,A2D項目也慢慢進入了一個穩定的開發流程,但是,接下去就真的一帆風順了嗎?故事還遠遠沒有結束,困難就像跋山,翻過一座又入一座。看似解決了的模拟登入問題遇到了性能上的瓶頸,每一輪的測試過程都達到了20秒以上,這是災難級的,使用者有效的等待時間大約為3秒,超過7秒就會産生厭煩感,大大提高不滿度,但是A2D單單一個模拟登入功能耗時就超過了合理時長的8,9倍,那麼問題到底在何處呢?是超級鷹傳回驗證碼太慢,還是模拟浏覽器的代碼有問題,亦或者是爬取架構的問題,這無疑給當時的作者産生了巨大的壓力,作者和副手針對這個問題檢查了全部的流程,終于找到了問題所在。超級鷹确實存在一定程度的性能損耗,第一在于對于複雜驗證碼的處理緩慢,第二在于判斷錯誤的回饋接口存在重複的性能損耗,但是這些并不是導緻模拟登入和蝸牛比速度的原因。真正的原因是phantomjs本身的問題,我們記錄了phantomjs驅動的耗時,執行個體化一個對象需要近2秒的時間,而裡面的顯氏等待我們設為2.4秒,而我們模拟點選操作和加載其餘代碼的時間也大約在2秒左右,超過7秒執行一次,這還不算爬取頁面,管道自動處理資料,持久化存儲以及錯誤重載這些時間,絕望感油然而生。根據作者和副手的再三讨論,得出以下結論,誠如PhantomJS提供了一系列的API來讓我們程式設計控制。這些API看似可以幫我們實作一些不易實作的功能,但是真的是屠龍刀倚天劍了嗎?看一下phantomJS問題具體在哪:
1.單線程處理,Phantomjs在處理頁面的時候,實際上同浏覽器不同。它是單線程下載下傳的。是以通常很慢。也就是說PhantomJS的加載速度并不完全等于使用者通路頁面的加載速度
2.JS嵌套,在Phantom中API本身提供的功能相當的有限。這時我們可能需要借助JS來實作一些功能。但是在JS中又不能引用Phantomjs的函數。調用起來極不友善
3.用于爬蟲處理并沒有想象中那麼完美,PhantomJS的一個重要的功能就是擷取JS執行後的内容。但是真的所有的都可以擷取到麼?其實不然,比如a标簽是觸發s函數來實作跳轉的。js的實作是使用window.location.href=”xxx”.我們會發現 對于這種寫法,我們是沒有辦法擷取到請求位址的。a(按鈕)标簽點選後修改了目前頁面,我們會發現也沒有辦法直接一次性得到所有的連結。
第二第三個問題尚可接受,第一個問題是其核心原有的政策,作者和副手還沒有能力去改變它,那麼怎麼提升性能和效率呢?我們項目的副手(也是核心技術開發工程師)提出了對象池的應用政策來優化性能,這個想法的起源在于每執行個體化一個驅動對象就要走完其完整的生命周期,那麼我們能不能使用池技術進行生命周期的管理呢,事實上是可以的。這裡簡單介紹下對象池技術。
Java對象的生命周期大緻包括三個階段:對象的建立,對象的使用,對象的清除。
是以,對象的生命周期長度可用如下的表達式表示:T = T1 + T2 +T3.其中T1表示對象的建立時間,T2表示對象的使用時間,而T3則表示其清除時間。由此,我們可以看出,隻有T2是真正有效的時間,而T1、T3則是對象本身的開銷。對象池技術基本原理的核心有兩點:緩存和共享,即對于那些被頻繁使用的對象,在使用完後,不立即将它們釋放,而是将它們緩存起來,以供後續的應用程式重複使用,進而減少建立對象和釋放對象的次數,進而改善應用程式的性能。事實上,由于對象池技術将對象限制在一定的數量,也有效地減少了應用程式記憶體上的開銷。
雖然采用了對象池技術有效得消滅了驅動反複執行個體化和回收的性能損耗,但是功能綜合的耗時還是超過了15秒,于是作者給出了第二個政策,業務核心子產品功能的分離。
分離操作是将模拟登入,爬取頁面,資料處理三個部分通過流程控制分離出來,提高使用者體驗,讓系統級操作通過使用者級操作進行控制,分離後具體的流程變為:
流程中将兩次爬取的過程分離開來,讓使用者自己參與進系統,将原本超過15秒的超長等待變為兩個不到7秒的等待動畫享受時間,并且可以選擇性操作這兩個步驟,至此,模拟登入性能上的重大挑戰告一段落了。在這個過程中,有一個技術被反複得提到,這就是xpath提取技術。那麼這又是什麼呢?
XPath是一門在XML文檔中查找資訊的語言。XPath用于在XML文檔中通過元素和屬性進行導航,并且可用來在XML文檔中對元素和屬性進行周遊,結構關系包括父、子、兄弟、先輩、後代等。爬蟲程式的核心是對網頁進行解析,從中提取出自己想要的資訊資料。這些資料可能是網址(url、href)、圖檔(image)、文字(text)、語音(MP3)、視訊(mp4、avi……),它們隐藏在網頁的html資料中,在各級等級分明的element裡面,通常是有迹可循的。我們傳統去提取頁面中的有效資訊采用的是正規表達式,而正規表達式卻無法讓我們心滿意足地精準定位到具體的元素,但是隻要取到了頁面中我們所需元素的xpath,我們就能通過它提取出我們真正想要的資訊。于是,我們引入了xpath以及它的資訊提取技術。
文章進行到此,有一個小的插曲要簡單講述下,因為項目前期工期緊張,作者項目組的前端擷取xpath并未按照作者預想的監聽鍵盤的起落事件和滑鼠的點選事件,在模拟登入頁面采用了form下的各個元素拼出xpath,這樣就存在一個嚴重問題,如果采集的登入頁面沒有form元素,而是采用div或者a标簽,那麼就找不到元素,當然也無法拼出想要的xpath了。為此系統模拟登入的命中率就大幅度降低。
這當然是一個小插曲,但是卻是A2D最緻命的問題,模拟登入的命中成功率和采集到資料準确率,截止到本文撰寫時,這個問題仍然在不斷得進行優化處理。
項目終于在一個問題接一個問題解決的過程中,慢慢步入了正軌,一開始的擔憂和驚慌已經消減,團隊成員的效率也出現了很大程度得提高,行文至此,作者一直避而不談一個最核心的技術架構,那就是我們的基礎爬蟲架構webmagic,為何到現在才提起這個技術,主要原因是如果将這個架構像前文提到的那些技術一樣結合項目本身分析刨解,文章就脫離了原先的初衷,是以這裡我們就簡要介紹下這個架構。
一個好的架構必然凝聚了領域知識。WebMagic的設計參考了業界最優秀的爬蟲Scrapy,而實作則應用了HttpClient、Jsoup等Java世界最成熟的工具,它幾乎使用Java原生的開發方式,隻不過提供了一些子產品化的限制,封裝一些繁瑣的操作,并且提供了一些便捷的功能。WebMagic最大的特點就是微核心和高可擴充性,WebMagic的核心并不複雜,主要是将這些元件結合并完成多線程的任務。
WebMagic的核心在webmagic-core包中,其他的包可以了解為對WebMagic的一個擴充。WebMagic以擴充的方式,實作了很多可以幫助開發的便捷功能。例如基于注解模式的爬蟲開發,以及擴充了XPath文法的Xsoup等,而這些功能在WebMagic中是可選的。WebMagic由Downloader、PageProcessor、Scheduler、Pipeline四大元件,通過Spider将它們彼此組織起來構成的。這四大元件對應爬蟲生命周期中的下載下傳、處理、管理和持久化等功能。Spider可以認為是一個大的容器,同時也是WebMagic邏輯的核心。這也是為何作者的項目名命名為spider的緣故。下圖是webmagic的技術架構圖:
四大元件介紹如下:
1.Downloader
Downloader負責從網際網路上下載下傳頁面,以便後續處理。WebMagic預設使用了apache httpclient作為下載下傳工具。
2.PageProcessor
PageProcessor負責解析頁面,抽取有用資訊,以及發現新的連結。WebMagic使用Jsoup作為HTML解析工具,并基于其開發了解析XPath的工具Xsoup。在這四個元件中,PageProcessor對于每個站點每個頁面都不一樣,是以團隊的核心技術工程師(副手)對這一部分進行了定制開發。
3.Scheduler
Scheduler負責管理待抓取的URL,以及一些去重的工作。WebMagic預設提供了JDK的記憶體隊列來管理URL,并用集合來進行去重。
4.Pipeline
Pipeline負責抽取結果的處理,包括計算、持久化到檔案、資料庫等。Pipeline定義了結果儲存的方式,如果你要儲存到指定資料庫,則需要編寫對應的Pipeline。這裡有A2D開發過程中的另一個小插曲,副手實作完pipeline的資料處理後發現并不能直接将資料傳回給調用的service,那麼就無法讓資料得到展示,而設計之初,第一次爬取到的目标頁資料是需要進行一些選擇和資訊處理的,那麼得不到展示的資料就使得這個功能泡了湯。其實問題的原因是WebMagic預設隻提供了“輸出到控制台”和“儲存到檔案”兩種結果處理方案。當時副手提議改造pipeline使得資料能得到回調,這樣做确實能實作功能,并且不會破壞本身的程式結構,但是作者細思後拒絕了這種涉及面極大的改造,于是有了以下的資料流轉思路:
思路的核心是将資料庫作為一個中間媒介,類似于檔案的效果,因為對于臨時資料我們也需要有一個進行統計版本的預留選擇,多調用一次資料庫雖然會損失一些性能,但是帶來的好處也是不言而喻的。
講過四大元件,需要介紹下WebMagic的核心部分,webmagic-core隻包含爬蟲基本子產品和基本抽取器。webmagic-extension是WebMagic的主要擴充子產品,提供一些更友善的編寫爬蟲的工具。包括注解格式定義爬蟲、JSON、分布式等支援。同時WebmMgic提供與Selenium的結合,WebMagic依賴Selenium進行動态頁面的抓取。提供了WebMagic與Saxon的結合。Saxon是一個XPath、XSLT的解析工具,webmagic依賴Saxon來進行XPath2.0文法解析支援。正是因為有了強大并且靈活的webmagic,作者對于A2D産品的健壯性和拓展性一直抱以極大的期望。
行文至此,A2D開發需要用到的核心爬蟲技術已經基本介紹完畢,因為在最初的需求中呈現的性能要求不高,是以并未考慮高并發,爬取資料量過大等情況,是以多線程,消息隊列,記憶體處理以及緩存機制都并未在産品中得到應用。作者以及團隊終于攻破了模拟登入和資料爬取兩個核心子產品,雖然至今或多或少仍然存在一些問題和不足,但是這不妨礙項目進入到了下一個階段,資料處理,這個階段雖然技術含量大幅度降低,但是對于産品設計和業務了解的要求卻提高了甚多。由于鄙公司有目錄系統,擁有一套完整的資訊資源資訊項的處理體系,作者在參考了系統的共通性後,重新調整了A2D的輸出方式用以豐富産品的可用性。
具體的調整思路主要為兩步,第一步是通過轉換采集到的表頭資料,讓其擁有資訊項的元素特性,第二步是多次采集的結果需要一個标準的對比過程,那麼設定一個模版制造版本子產品和比對功能就成為可行。這就不得不介紹A2D第二個最為重要的亮點,自動化采集。首先作者抛出幾個問題,何為自動化采集?自動化采集如何實作?自動化采集的難點和問題在哪?要解答這幾個問題,作者先介紹一下定時任務在程式中的使用。
定時任務的場景可以說非常廣泛,比如某些視訊網站,購買會員後,每天會給會員送成長值,每月會給會員送一些電影券;比如在保證最終一緻性的場景中,往往利用定時任務排程進行一些比對工作;比如一些定時需要生成的報表、郵件;比如一些需要定時清理資料的任務等。由于作者采用的系統基礎架構是springboot2.0架構,對于實作簡單的定時任務十分容易,隻需要在我需要定時執行的功能方法上加上一個Scheduled注解就可以按照我設定的時間執行任務,但是僅僅擁有這些并不能實作我們自動化采集的功能,使用者需要自主配置時間來執行對于某個站點的定時采集,為此我們引入了與spring無縫對接的定時任務架構Quartz。
對于這個定時架構的具體實作作者就不在本文中贅述了,Quartz擁有強大的排程功能、靈活的使用方式、還具有分布式叢集能力,每一個熟悉spring的讀者在需要的時候恐怕都會想到它吧。在使用它的過程中,作者前前後後也碰到了不少問題,例如更改了資料庫的定時任務資訊并未讓排程器進行重新掃描的問題,再例如配置的方式中如何将排程器交給spring的IOC去進行管理,還有鎖問題等等,但是随着深入研究使用,這些問題都隻是沿路的絆腳石而已,畢竟守得雲開見月明嘛。
寫到這裡,作者的A2D項目進展到了一個關鍵時期,那就是需要進行測試和試用了,我們嘗試了鄙公司所開發的各套系統,得到的結果讓作者還是得到了一些心理上的滿足,絕大部分的系統和子產品在我們一次次的嘗試和修改中完成了它們資料的抽取,但是對于做了安全反爬以及那些久遠的遠古系統,A2D卻無可奈何了起來,當一個登入頁面中出現了7,8張幹擾驗證碼擷取的無用圖檔,當驗證碼并不是圖而是一個跳轉,當登入按鈕不是一個按鈕,而是一張圖檔時,模拟登入暴露了它的局限性,時至本文撰寫,仍然沒有特别的解決方式,命中率仍然是一個大考驗。即使模拟登入成功,一些遠古系統詭異的頁面展示方式,并不準确的url路徑,也讓A2D的爬取難度重重,于是作者對于A2D的未來提出了更多的要求,同樣這些要求也會在A2D的後續版本中一一得到實作:
1.爬取資料過于單一,僅僅限于表頭的爬取對于産品本身,顯得過于單薄,在後續版本開發中,将資料拓展到編輯框中的字段以及實體資料,而前端對于所爬資料的方式也将得到一些優化和提升,增加一定使用者的人機交流,不完全過濾掉所爬頁面的js事件。
2.爬取目标url擷取過于專業,使用者很難提供準确的子產品路徑,A2D将連結爬取融入系統中來,可以嘗試站點子產品的整體或者選擇性爬取,這裡将引入性能優化的幾種方式,多線程,記憶體管理,隊列等。
3.對于pipeline處理所爬頁面的方式進行優化,将引入mongoDB來應對突然增大的爬取量,采用檔案存儲讀取的方式而不是通過資料庫存取臨時表,這樣穩定性将得到很大提高。
4.将正式打通與目錄系統的資料通道,提供真實可靠的資料入口。
5.優化部署方式,由于是前後端分開部署,現在采用的部署方式存在一些隐性問題,另外breakpipe問題出現在了linux部署的系統日志,而windows安然無恙,問題的根源還需要深入研究。
6.将UI進一步優化,提升使用者體驗,增加一些等待時間的動畫效果等。
如果A2D進入到下一階段的疊代周期,作者也難以估量高複雜度的産品是不是會暴露出無法解決和無法規避的問題,但是風險和機遇總是并存的,這也是我們作為技術人員,一直孜孜不倦得理由。最後作者再此感謝A2D産品所有的參與人員,強烈感謝我的項目組成員,感謝技術敏感,頭腦聰慧,靈性十足的團隊核心技術工程師,作者的副手佳兒,身在遠端,交流不便但是效率超群的老前端老周,工作韌性十足,潛力巨大的背景程式員耀暖。再次感謝所有讀本文的讀者,謝謝你們的耐心和肯定,希望得到你們的回饋和建議,期待作者還能在之後的生涯中繼續撰寫我的項目航海經曆!
作者 毛鑫敏
聯系方式 15168082750
qq 331228638
郵箱 [email protected]