1. 主角出場:Hawk介紹
Hawk是沙漠之鷹開發的一款數據抓取和清洗工具,目前已經在Github開源。詳細介紹可參考:http://www.cnblogs.com/buptzym/p/5454190.html
強烈建議先讀這篇文章,該文介紹了詳細原理和抓取鏈家二手房的攻略,以此為基礎,才能較好的理解整個操作。
GitHub地址:https://github.com/ferventdesert/Hawk
本文將講解通過本軟件,獲取大眾點評的所有美食數據,可選擇任一城市,也可以很方便地修改成獲取其他生活門類信息的爬蟲。
在這個過程中你可以學會:
- 自動登錄
- 自動翻頁
- 組合多種情況,以破解翻頁限制
- 自動獲取頁面的表格數據
本文將省略原理,一步步地介紹如何在20分鍾內完成爬蟲的設計,基本不需要編程,還能自動並行抓取。
看完這篇文章,你應該就能舉一反三地抓取絕大多數網站的數據了。Hawk是一整套工具,它的能力取決於你的設計和思路。希望你會喜歡它。
詳細過程視頻可參考:http://v.qq.com/page/z/g/h/z01891n1rgh.html,值得注意的是,由於軟件不斷升級,因此細節和視頻可能有所出入。
准備好了么?Let's do it!
2. 菜場買菜:編譯和安裝
編譯可使用VS2015(推薦),否則可直接從網盤下載可執行程序:
http://pan.baidu.com/s/1c8zBiQ 密碼:4iy0
之后雙擊Hawk.exe,即可運行。
依賴環境要求.NET Framework 4.5, win7和以上版本。沒有其他依賴項。
3. 做飯先生火:自動設置cookie:
我們先打開大眾點評的美食列表頁面:
http://www.dianping.com/search/category/2/10/g311
這是北京的"北京菜"列表,但你會注意到,只能抓取前50頁數據(如箭頭所示),是一種防爬蟲策略,我們之后來破解它。
我們雙擊打開一個網頁采集器:
之后在最上方的地址欄里填寫地址:
但會發現遠程服務器拒絕了請求,原因是大眾點評認為Hawk是爬蟲而不是瀏覽器。
沒有關系,我們讓Hawk來監控瀏覽器的行為,在右側的自動嗅探窗口中,填寫url過濾和內容篩選,之后點擊開始。瀏覽器會自動打開該網頁,程序后台自動記錄了所有的行為,之后點擊關閉按鈕(切記點擊關閉)。
(此處大概介紹原理:Hawk在點擊開始之后,會自動成為代理,所有的瀏覽器請求都會經過Hawk,在輸入特定的URL篩選前綴和關鍵字,則Hawk會自動攔截符合要求的Request,並將其詳細信息記錄下來,並最終模擬它)。
之后,我們點擊右方的“高級設置”里,能夠看到Hawk已經把這次訪問的cookie和headers自動保存下來:
我們再次點擊刷新網頁,可以看到已經能成功獲取網頁內容:
完成這一步之后,我們就能夠像普通網頁那樣免登陸抓取信息了。這也適合需要登錄的各類網站。
4.洗菜切菜:獲取門店列表
我們通過自動和手動兩種方式來獲取門店列表,你可以兩種都試試。
4.1 全自動獲取
直接點擊手氣不錯即可,不需要其他操作:
4.2 純手工獲取
我們先手工輸入篩選條件吧:
輸入上面的關鍵字3774,命名為點評:
接着,填入89,你會發現是下面這樣:
注意XPath表達式和點評的表達式不大一樣,這是因為89太普通,在網頁中出現多次,再次點擊搜索XPath,即可找到正確的位置。
類似地,將所有你認為需要的屬性添加進去,加上合適的命名,大概是這個樣子:
當然,我們還需要門店的ID,但在頁面上並不提供,那在瀏覽器上點擊那個“四季民福烤鴨店”(沙漠君厭惡吃鴨子),你會看到它的鏈接為:
我們將18002657添加並搜索,發現不論怎么點都搜索不到,此時勾選提取標簽,系統會在標簽中搜索:
之后獲取了全部屬性后,點擊提取測試,系統會自動優化XPath,列表父節點會顯示在下方。
筆者建議自動加手動配合的方式,自動抓取大部分數據,再用手動修改調整,手氣不錯雖然智能,但並不是什么時候都管用。
將本模塊命名為門店列表,供之后備用:
5.餐前甜點:獲取50頁數據
我們先用50頁數據試試手,在剛才那個瀏覽器頁面的最下方,點擊翻頁,可以發現是如下的結構:
http://www.dianping.com/search/category/2/10/g311p2
http://www.dianping.com/search/category/2/10/g311p3
http://www.dianping.com/search/category/2/10/g311p4
...
好,新建數據清洗,隨便給它起個名字,從左面拖入生成區間數,雙擊配置列名為page,最大值填50,再拖入合並多列到page列,配置如下:
其中Format設置為:
http://www.dianping.com/search/category/2/10/g311p{0}
這是C#的一種字符串替換語法,{0}會被依次替換為1,2,3...
最后,拖從爬蟲轉換到url列,奇跡出現了吧?
為了保存結果,我們拖寫入數據表到任意一列,這里選擇了名稱列,配置如下:
之后,在右側選擇並行或串行模式(隨你),點擊執行即可。
數據采集完成了!
如果看到這一步累了,可以不看下面的內容,但如果想獲取全部內容,步驟就復雜多了,如果你下決心學習,我們接着往下看
5. 准備蔥蒜:獲取城市的美食門類
解決問題的辦法是分而治之,獲取每個區縣(如北京的海淀區)下的某一種美食門類(東北菜),自然就沒50頁那么多了。所以,要獲取美食門類,再獲取所有的區域。
先找到所有美食門類的位置:
http://www.dianping.com/shopall/2/0
為了獲取此頁面上的信息,我們再新建一個網頁采集器,命名為通用采集器,它的目標是獲取整個HTML頁面,因此
讀取模式改成One,將剛才門店列表采集器里的高級設置->Parameters的內容拷貝到本采集器對應的窗口中。
(其實也可以做嗅探,但這個更快一些)。
之后,我們來獲取這個頁面上的所有美食門類,新建數據清洗,命名為門類,然后從左側拖從文本生成到右側任意一列,命名如下:
再拖入從爬蟲轉換,配置如下:
即可調用剛才的通用采集器。另外,左側的工具欄支持搜索,直接關鍵字即可快速定位,結果如下:
為了獲取下圖的北京菜所在的位置,雖然可以用Hawk,但為了方便可以使用Chrome,搜狗和360瀏覽器的F12開發者工具功能,找到對應的元素,點擊右鍵,拷貝XPath:
內容為://*[@id="top"]/div[6]/div/div[1]/dl[1]/dd/ul/li[1]
,
因為要獲取所有的子li,在剛才的數據清洗中,向Content列拖入XPath篩選器,配置如下:
由於要獲取所有的li子節點,所以去掉了最后的1,可以適當復習XPath語法。
奇跡出現了:
接下來步驟很簡單,我不截圖了:
-
拖入HTML字符轉義到Text列,可以清除該列的亂碼
-
再拖入字符串分割到Text,勾選空格分割,可對該數據用空格分割,並獲取默認的第一個子串
-
拖入刪除該列到OHTML,該列沒有用
-
再拖入正則轉換器到HTML,配置如下:
g\d+代表匹配那個門類的ID,比如剛才的g311 -
拖入刪除該列到HTML
-
直接在Text列的上方修改名稱為門類
最終結果如下:
6.獲取北京的區域
這一步和上一步非常類似,因此我很簡明地介紹一下。
區域在這個頁面:
http://www.dianping.com/search/category/2/10/g311p2
這些節點的XPath是://*[@id="region-nav"]/a
你可以按照剛才類似的步驟進行,也是創建新的數據清洗,把這個子模塊命名為區域,最終結果如下:
如果自己做不下來,也沒有關系,加載Github上大眾點評的教程.xml
,可以直接用這個現成的模塊,也可以單步調試之,看看它是怎么寫的。
7.正菜開始:主流程
下面是最難也是最復雜的部分。我們的思路是,組合所有的門類和區域,構成m*n的一組序對,如海淀區-北京菜
,朝陽區-火鍋
等等,獲取對應序對的頁數,再將所有結果拼接起來。
准備好了么?我們繼續。
新建數據清洗,命名為主流程,我們要調用剛才定義的模塊,拖入子流-生成到任意一列,配置如下:
記得要勾選啟用,這些模塊默認是不啟用的。
再拖入子流-生成到任意一列,配置如下:
注意生成模式改為Cross。
具體不同模式的工作方式,可參考這篇文章:http://www.cnblogs.com/buptzym/p/5501003.html
之后,就是這個樣子:
我們將兩列組合起來,可看到Url為如下的形式:
http://www.dianping.com/search/category/2/10/g311r14
拖合並多列到type,配置如下:
{0}{1}相當於組合了多個元素,拖入的當前列為第0元素,其他項用空格分割,分別代表第1,2...個元素。
為了獲取每個門類的頁數,需要在頁面上找一下:
它的XPath是/html[1]/body[1]/div[6]/div[1]/span[7]
- 拖入從爬蟲轉換到url列,配置爬蟲選擇為通用采集器,就能獲取對應的HTML
- 拖入XPath篩選器到HTML所在的Content列,XPath表達式如上
/html[1]/body[1]/div[6]/div[1]/span[7]
。只獲取一個數據,新列名為count - 拖入刪除該列到Content列。
- 拖入提取數字到count列
- 拖入Python轉換器到count列,這是本文唯一要寫的代碼:
配置如下:
代碼在下面:
v=int(value)
50 if v>750 else int(v/15)+1
Python代碼很好理解吧,大概是說超過750個就按50頁處理,頁數等於數量除以每頁15個,取整后+1,截圖中的代碼是錯誤的,感謝熱心網友提醒
你會發現即使這樣,每個門類還是超過了50頁,這個問題我們之后再討論。
為了方便並行,拖入流實例化到任意一列,配置如下:
。
執行器會將每一個門類區縣對分配一個獨立的線程,注意方括號[url]的寫法,系統會把url列的內容賦值到這里,如果你只寫url,那所有的線程名稱都叫url了。你可以不添加流實例化,看看系統最后是怎么工作的。
接下來,我們要把page列展開,生成[0-page]的區間數,一頁一頁去抓取。拖生成區間數到page列,配置如下:
注意Cross和[page],我就不多解釋了。
把剛才的url和現在的p列合並,就構成了每一頁的真實url.
拖入合並多列到url,配置如下:
仔細理解一下配置的意思,尤其是{0}p{1},我覺得讀者到了這一步,已經對整個系統有點感覺了。
雄關漫道真如鐵,我們馬上到達目的地了。
現在url列已經是這個樣子了(點擊查看樣例即可)
將從爬蟲轉換到url,配置爬蟲來源為門店列表!然后等待奇跡出現
(賣個關子,我就不截圖了)
然后拖入寫入數據表到任意一列,為表名起個名字,點擊執行去跑就可以了。
如果你到這一步就滿意了,那么文章可以不用往下看了。
8.注重細節
一道大菜要非常注意細節,爬蟲也一樣。
8.1 保留原始表的信息
你看到數據表里沒有這家店的區縣,也沒有所在的頁數,感覺從爬蟲轉換丟失了原始表的一部分信息,事實上它在1轉多的時候,原始表默認都會丟掉。
因此在下圖的位置,點擊編輯集合,選擇最后的那個從爬蟲轉換,配置如下:
它會將p和區域兩列,添加到新表中。
8.2 我想寫入數據庫
目前Hawk沒有強的自動建表功能,因此建議使用MongoDB,如果你已經安裝了,在模塊管理的數據源哪里,點擊右鍵,可新建MongoDB連接器。
可以在主流程的最后位置,在拖入寫入數據庫,即可。
8.3 還是沒有獲取所有數據
即使是剛才這樣的復雜操作,依然不能獲取所有的美食,因為火鍋太火,朝陽海淀的火鍋都超過了50頁,解決方法是再細分商區,比如朝陽的三元橋,國貿,望京...這樣就能完整解決了。但本文限於篇幅就不討論了。
8.4 如何將數據表導出到文件?
在右下角的數據管理,在要導出的表上點右鍵,建議輸出為xml,json和txt文件,excel文件在數據量較大(5萬以上)會有性能問題。
8.5 這種圖形化操作有什么優勢?
效率!所見即所得!你可以試着用任意一種代碼去寫,煩死你
8.6 如何保存所有操作?
會將所有剛才的操作保存在工程文件中。
8.6 我的服務器在Linux上,怎么辦
Hawk是WPF,C#開發的,因此只能在Windows上運行,不過它生成的xml可以被Python解釋,參考github上的etlpy.
8.7 Hawk是你一個人寫的嗎?用了多久
目前來看是這樣的。業余時間四年
8.8我想獲取各個城市的,不限於美食的數據
這個就更復雜了,可以借助腳本實現,這是下一篇的話題。
9.總結
為了方便大家學習使用,剛才的整個操作已經上傳到了Github。地址為https://github.com/ferventdesert/Hawk-Projects
大眾點評-教程.xml
本軟件是我在.NET領域最后的絕唱,估計以后不會再繼續開發C#相關的東西了。
有任何問題,歡迎留言。