設(shè)計(jì)爬蟲Hawk背后的故事
五年之癢
2016年,能記入個(gè)人年終總結(jié)的事情沒幾件,其中一個(gè)便是開源了Hawk。我花不少時(shí)間優(yōu)化和推廣它,得到的評價(jià)還算比較正面,因?yàn)樨?fù)面評價(jià)也沒什么渠道進(jìn)我耳朵。
不過你知道我寫這個(gè)東西花了多久嗎? 掐頭去尾,這是第五個(gè)年頭了。
讀研究生伊始,實(shí)驗(yàn)室開始做數(shù)據(jù)挖掘,但我發(fā)現(xiàn)大家做研究,都是一段段的代碼,遇到新問題,就不得不再拷貝一份修改,很少想過復(fù)用。于是我便花了一年的時(shí)間,開發(fā)了一款現(xiàn)在看起來配色喪心病狂的“數(shù)據(jù)挖掘軟件”:
它居然能在上面刷微博,能把任何一個(gè)學(xué)姐學(xué)妹在微博的蛛絲馬跡全扒出來,渲染出配色更喪病的絢麗圖表。實(shí)驗(yàn)室老師最喜歡拿這套軟件給參觀的領(lǐng)導(dǎo)演示了。
我多少有點(diǎn)偏執(zhí),想用純圖形化的方式構(gòu)建數(shù)據(jù)挖掘的全部流程:從獲取清洗數(shù)據(jù),建模,訓(xùn)練到最后可視化。但以我當(dāng)時(shí)的算法和軟件水平,這樣的東西根本就不可能做完善,我還意淫著能把它商業(yè)化。最終結(jié)果,是它跟著我一起畢業(yè),除了我和幾個(gè)學(xué)弟之外,沒人用過。
工作后,靠業(yè)余時(shí)間維護(hù)它有很大的困難。數(shù)不清的bug和時(shí)間花銷,讓它成了拖后腿的包袱。一些考慮不周的接口,設(shè)計(jì)于幾年之前,后來想修改卻花費(fèi)巨大。更重要的是,它的未來在哪里?
時(shí)值2015年,桌面軟件已死,web都已過時(shí),移動(dòng)端才是兵家必爭之地。這種大雜燴和復(fù)雜度,普通人不可能會用,程序員沒有文檔不愛用,大神不屑于用。
我也不想讓它扔在歷史的故紙堆里無人問津,索性就開源吧!
在開源之前,我做了一件事,將它改名為Hawk,寓意為馳騁天空的鷹;把我自己用的次數(shù)少于10次的組件全部刪掉,連3D可視化,統(tǒng)計(jì)和預(yù)測模塊也不幸免:只保留爬蟲和數(shù)據(jù)清洗,因?yàn)榉治龊屯诰蛞呀?jīng)有其他工具做得更好了,比如Python那無比強(qiáng)大的工具鏈。我認(rèn)識到,只做一件事,并將其做好是多么重要。
老司機(jī)送它上路了,那一天,它正式出現(xiàn)在GitHub上(此處應(yīng)當(dāng)有掌聲^_^)
我對Hawk的評價(jià)
我就是用程序員的思路去設(shè)計(jì)Hawk的,好玩有趣是最重要的。
先說優(yōu)點(diǎn):起碼它能用,幾乎沒接觸過編程的人,也能在看過教程后,做出他自己的設(shè)計(jì),熟練后效率會很高。
我最喜歡的是手氣不錯(cuò),升級Hawk2.0之后,絕大多數(shù)網(wǎng)頁都可以通過一鍵提取數(shù)據(jù)。其實(shí)原理很簡單,就是樹結(jié)構(gòu)的模板匹配。
其次便是那個(gè)所見即所得的數(shù)據(jù)清洗,在調(diào)試模式下快速設(shè)計(jì)任務(wù),在執(zhí)行模式下并行執(zhí)行。恕我孤陋寡聞,這種基于流式管線思路,在其他軟件上用的并不多。
最后便是子任務(wù),再復(fù)雜的需求,都可以通過子任務(wù)分而治之,比如多次定向跳轉(zhuǎn)。這東西讓八爪魚和火車頭去試試看?
Hawk最大的價(jià)值,是將復(fù)雜的邏輯鏈條化和可視化了,你可以將代碼變得不那么耦合,像搭積木一樣方便地組合。當(dāng)然這些都拜于函數(shù)式編程的思想所賜。
再說缺點(diǎn):Hawk有很多的bug,沒單元測試,功能不完善。但這都不是最根本的缺點(diǎn),最根本是難于實(shí)現(xiàn)條件判斷和循環(huán),你很難寫出if,switch和while。 這也是函數(shù)式語言的共同問題,你不得不逼迫自己用另外的思路去解決。雖然通過引入子任務(wù),能“不夠優(yōu)雅”地解決它,但依舊要花費(fèi)不少腦筋。
有的人覺得Hawk不夠強(qiáng),這是因?yàn)樗菆D形化軟件。為什么具備UI的軟件很難和命令行相比?因?yàn)樵O(shè)計(jì)UI的新功能時(shí),不僅需要考慮它的算法,更要考慮它如何在UI上呈現(xiàn)。有些功能非常有用,比如爬蟲常見的BFS和DFS(深度和廣度優(yōu)先遍歷),但在UI上配置會異常繁瑣,我不得不將它們拋棄掉,只保留最常用的功能。
為什么呢?輕松寫B(tài)FS的人,會想用Hawk嗎?應(yīng)該不會,他們也許會直接敲代碼。
Python版本的Hawk: etlpy
因?yàn)閳D形界面和C#本身的諸多不足,我開始發(fā)展了Python的Hawk,稱為etlpy (Extract-Transform-Load in Python)
最初的想法,是將Hawk生成的配置文件(xml)交給etlpy去執(zhí)行,初看很酷,但這種思路被證明不靠譜,兩種語言,使用不同類庫,對同一個(gè)任務(wù)流,就因?yàn)樵诘讓拥募?xì)微偏差,會導(dǎo)致結(jié)果的完全不同。我花了大量的時(shí)間在解決兼容性問題上,發(fā)現(xiàn)難度不小,兩者互相制約牽絆。
最終,我放棄了所謂的兼容性,之后etlpy功能日新月異,甩了Hawk有10條街不止。Hawk的5分鐘拖拽,在etlpy上只要一句話:
- url=’www.cnblogs.com/p{0}’
- t=task().let(‘p’).range(‘1:20’).format(url).detect()\.let(‘dignum’).split(‘_’)[0].num().write(‘cnblogs.json’)
介紹etlpy的語法超過了本文的范疇,但它擁有Hawk的一切優(yōu)點(diǎn),同時(shí)能夠分布式并行抓取,任務(wù)隊(duì)列,超級代理,定時(shí)更新,與任意函數(shù)庫集成。執(zhí)行引擎(etlpy)的全部源代碼,也就1000行出頭。
也許你都沒有聽過Lisp這門語言,不過沒有關(guān)系:Hawk是用C#寫的,手敲的代碼大概有1萬行,用Python大概是1000行左右,用Lisp只要300行就能實(shí)現(xiàn)它的全部功能!當(dāng)然,沒有圖形界面。由此可見編程語言在表現(xiàn)力上的巨大差別。Hawk本質(zhì)上是個(gè)可視化的Lisp設(shè)計(jì)器。
我喜歡語言的純粹和精妙,用簡潔的語法就能代表復(fù)雜的邏輯。Hawk其實(shí)定義了一種爬蟲語言,對諸多常用操作進(jìn)行了模塊抽象,在拖拖拽拽中,你構(gòu)建了一張圖(Graph),數(shù)據(jù)在圖上流動(dòng),被生成,清洗和消費(fèi)。最近大熱的Tensorflow不也是一樣的思路嗎? 這張圖,既是數(shù)據(jù),也是代碼。
當(dāng)然,不論是Hawk和etlpy,你都不能把它們當(dāng)成廚子,給它指令,就能做出一道菜來。而應(yīng)當(dāng)成一套順手的廚具,而真正做菜的那個(gè)人還是你。自動(dòng)化只是解決了部分問題,而巧妙設(shè)計(jì)的源泉還是來自于操作者本人。最強(qiáng)大的工具,是自己。
我們用語言表達(dá)概念,模式和流程,在此之上構(gòu)建抽象。我假定每個(gè)用Hawk的人都能觸類旁通,因此通過一些通用的介紹,他應(yīng)該就能理解絕大多數(shù)的功能。那如果還是不懂呢?那不好意思,請用八爪魚,否則Hawk的很多設(shè)計(jì),對他來說就變得沒有意義。我自己從來不愿意把Hawk和etlpy定義為爬蟲,它們提供的是一組環(huán)境,語言和工具,爬蟲只是它的一個(gè)不錯(cuò)的應(yīng)用場景而已。
為什么要開源?
有人肯定會問,為什么不把Hawk商業(yè)化,去賺一筆錢呢?
理想狀態(tài)下,成立公司去完善推廣和銷售軟件,那么應(yīng)該行得通,否則單槍匹馬是不可能的。那我是不是值得為這套軟件成立公司呢?應(yīng)該不會,因?yàn)樗皇莻€(gè)工具,還不值得我去開一家公司去完善它。
我相信,這也是很多程序員面臨的類似困境,想靠賣軟件盈利,就像路上的煎餅攤一樣簡單樸素,問題是放在10年前還有可能(記不記得30元一套的金山詞霸?)而服務(wù)化盛行的今天,可能很低。到處推廣,最終只可能淪為某天我在咖啡廳里的幾句談資,不可能有什么大氣候。
有朋友說,為什么你要開源,對自己來說是不是有點(diǎn)像代碼外泄?谷歌的TensorFlow都開源了。前面的路還有很遠(yuǎn),大家都要忙著趕路呢。保護(hù)代碼最好的方式就是開源,沒開源時(shí)有人還想著破解研究,開源了大家fork一下然后就沒有然后了,“得不到的才是最好的”。
不得不說,開源才是最貴的。大家看到開源,歡欣雀躍,以為在路上撿到了錢,其實(shí)不是的,如果你真想用起來,一定會花更多的時(shí)間來學(xué)習(xí)它,因?yàn)殚_源者沒有義務(wù)像商業(yè)軟件那樣,提供完整的支持。時(shí)間和錢總是矛盾的,既想省錢還想省時(shí)間,天下沒有這樣的好事。
我以前天真地以為,開源的全部就是把源代碼公開在網(wǎng)上。之后難道不是理所應(yīng)當(dāng)?shù)穆曂?,一幫牛人幫你?yōu)化功能修復(fù)bug嗎?
哈哈,開源怎么可能那么簡單。
如果軟件寫得不錯(cuò),但沒有宣傳,那掛在網(wǎng)上完全無人問津,沒人知道那是什么鬼;寫得足夠好,宣傳也到位,那也很難有人幫忙貢獻(xiàn)開發(fā): 往上看,大神們都忙著造輪子;往下看一大幫實(shí)用主義者,抄起輪子就上路,誰顧得上看輪子是不是圓的呢?
開源不意味著坐視不管,依舊需要大量的精力去培養(yǎng)完善它和它的社區(qū)環(huán)境,通過郵件和論壇方式提供一定的服務(wù)。這件事情如果不用心,則一事無成。
如果pandas和ipython不開源,我現(xiàn)在也可能無法進(jìn)入數(shù)據(jù)分析的殿堂,沒有Linux和眾多開源工具,世界也不會發(fā)展成這個(gè)樣子。我明白了為什么開源是一種哲學(xué),它是一種“共贏”。對程序員來說,名譽(yù)和成長比賣軟件帶來的價(jià)值更大,那是認(rèn)可感和成就感。對其他人來說,開源帶來了時(shí)間的節(jié)省和實(shí)際的財(cái)富。這樣做必然是有價(jià)值的。
請理性使用爬蟲
開發(fā)完Hawk的那段時(shí)間,我也和很多人一樣,看到一個(gè)網(wǎng)站就想去試試。以我的熟練程度,20分鐘數(shù)據(jù)就都被下載下來了。然而數(shù)據(jù)保存在硬盤上,幾乎不產(chǎn)生價(jià)值。
Google的工程師,可以下載它的全部源代碼。可是為什么沒有另外一家Google,甚至另外一家百度(又黑了百度)出現(xiàn)呢? 因?yàn)楦匾氖侨瞬?,資源和商業(yè)模式,代碼在這件事情中最不重要了。拿到源代碼都沒用,更遑論掛在網(wǎng)上供大眾閱讀的數(shù)據(jù)呢?
爬蟲是灰色區(qū)域,所以大公司從來不公布任何爬蟲框架,但內(nèi)部用的比誰都多。兩年以來,網(wǎng)絡(luò)發(fā)生了巨大的變化,ajax化,SEO變成了關(guān)鍵詞廣告位,大量的網(wǎng)站開始強(qiáng)硬地反爬蟲,之前能隨便抓取的網(wǎng)站,現(xiàn)在都變得很困難。我相信這里有相當(dāng)一部分是Hawk的功勞。讓人郁悶的是,不少人抓數(shù)據(jù)僅僅用來玩。
可以想象,一小部分標(biāo)榜大數(shù)據(jù)和AI的公司,把Hawk的代碼弄下來改改,添一點(diǎn)數(shù)據(jù)報(bào)表功能,就可以盜走別人網(wǎng)站數(shù)據(jù),自立門戶,去到處忽悠。雖然Hawk開源協(xié)議是GPL(使用開源代碼的必須開源),但這完全限制不了,畢竟是中國嘛。可能他們不知道,數(shù)據(jù),工具和算法根本不值錢,理解,洞見和執(zhí)行才是最重要的,可是這部分卻需要真刀真槍的本事。
你肯定會問,那為什么etlpy后來不更新了?
第一它不穩(wěn)定,現(xiàn)在釋放出來可能砸招牌。其次,如果釋放出etlpy,能量和Hawk完全不是一個(gè)數(shù)量級,用它五分鐘寫出來三行代碼,就能迅速地部署在我司全國各地的千兆公網(wǎng)節(jié)點(diǎn),上萬QPS的壓力瞬間就能壓掛一個(gè)中小型網(wǎng)站。它的出現(xiàn)可能會讓網(wǎng)站變本加厲地防爬蟲。
總之,稍微理性一些,適可而止吧。
為什么不繼續(xù)優(yōu)化?
很簡單,因?yàn)闆]有時(shí)間。爬蟲本身沒太多技術(shù)含量,再深入就進(jìn)入了異構(gòu)Web數(shù)據(jù)集成的領(lǐng)域,大部分人根本用不到。這是個(gè)擁有數(shù)不清trick和dirty技巧的領(lǐng)域,是與網(wǎng)站設(shè)計(jì)者的攻防戰(zhàn)爭,玩到最后,你的能力曲線就像log函數(shù)一樣,被壓制在一個(gè)確定的上界。
現(xiàn)在是人工智能和深度學(xué)習(xí)的天下,以我所在的團(tuán)隊(duì)為例,身邊的大神們都在努力地優(yōu)化Tensorflow,改造算法的底層來提升效率,在有趣的數(shù)據(jù)集上做大規(guī)模并行訓(xùn)練。
幾年之前,如果我沒有去做爬蟲和Hawk,沒有去設(shè)計(jì)正則解析器tnpy,而是緊跟潮流去做大規(guī)模機(jī)器學(xué)習(xí)的話,眼界和身價(jià)可能就完全不一樣了呢?
當(dāng)然,我不后悔去做Hawk,它是實(shí)實(shí)在在的能讓大眾用起來東西?;蛟S在萬里之外,有個(gè)一樣戴著眼鏡充滿激情的和我一樣的nerd,看到我的代碼開心地拍了桌子,大喊“f**king nice work!”到那個(gè)時(shí)候,其他事情還重要么?
如果說我后悔沒做什么,是我沒有把軟件做成英文,曾經(jīng)有一段時(shí)間我個(gè)人非常崇尚中文編程,從而釀成了現(xiàn)在軟件國際化異常困難的后果;再者,沒有直接開發(fā)Python或者js這種能跨平臺語言的版本,Hawk只能局限于Windows桌面應(yīng)用,而不同的社群就風(fēng)氣又不一樣了。
它的設(shè)計(jì)有不少問題,不過更多來自于多種因素的妥協(xié)。它注定是不完美的。因?yàn)槁殬I(yè)生涯的關(guān)系,我無法像前幾年那樣,花大把的時(shí)間去優(yōu)化它。既然開源了,我希望有人能把它做得更好。
其實(shí)我還是個(gè)程序員
我之前從來沒想過自己會成為一名程序員。
高中時(shí)候理科不錯(cuò),語文巨差。高考完,老媽扯著嗓子問我,報(bào)哪所學(xué)校什么專業(yè)吧? 正在打游戲的我根本無暇顧及,不耐煩地說,隨便你們吧! 糊里糊涂地進(jìn)了通信工程,幾年后通信稍顯疲軟,就自學(xué)計(jì)算機(jī),然后一點(diǎn)都不偶然地成了一名程序員。
網(wǎng)上黑程序員的段子太多了。我都一笑而過。但最近幾年我堅(jiān)決不買任何尺寸和顏色的格子衫。如果看到我穿,好吧,那一定是我本科時(shí)候買的。
你知道那幫每天不茍言笑,整天盯著顯示屏的怪人每天在想什么嗎?
敲完這些文字的時(shí)候,2017年的九點(diǎn)半我還在杭州的酒店里睡眼惺忪,誰讓昨天夜里11點(diǎn)才回來的呢?讓我開心的,是Hawk的star接近800,有點(diǎn)擔(dān)憂的,是公司的新產(chǎn)品馬上要上線,會不會出什么狀況呢?