最近接手一個新項目,爬亞馬遜分類、商品數據。記得大學的時候,自己瞎玩,寫過一個爬有緣網數據的程序,那個時候沒有考慮那么多,寫的還是單線程,因為網站沒有反爬,就不停的一直請求,記得放到實驗室電腦上一天,跑了30w+的數據。然后當前晚上有緣網網站顯示維護中。。。。
畢竟小打小鬧,沒有真正的寫過爬蟲。就翻別人博客了解了下爬蟲所用到的技術、技巧、套路。然后就翻到這個老哥寫的博客, 雖然語言是有點囂張,但是我還是比較認同的 哈哈哈哈。
下面從爬蟲涉及的幾任務調度、數據去重、數據解析、並發控制、斷點續爬、代理來聊聊項目遇到的坑。
一、數據解析
數據解析就是提取網頁上的有效數據。.Net下有個HtmlAgilityPack組件,可以很好地解析HMTL。想都沒想 就直接用了它(這就為后面挖了一個大坑)。剛開始單線程測試的時候,一切正常,但是當我開了50個線程的時候,內存在90s內飆升到了3G,而且持續爬升。
用.Net Memory工具分析發現 內存被大對象沾滿了,所以每次GC的時候內存並沒有被回收,有5w多HtmlNode,每個對象大小都超過 85000byte。
因為亞馬遜的圖片不是通過鏈接外鏈的,而是通過base64編碼的,所以導致下載的網頁Html超過1M,而85000byte就算大對象了。導致每爬取一個商品詳情頁,都會加載到HtmlNode中變成一個大對象,由於GC不壓縮大對象,而C對大對象的回收只有在回收2代的時候才觸發。所以只能改變策略,通過正則、切分字符串來處理。
二、任務調度、並行爬取
目的是爬取亞馬遜的分類和分類下的商品,我做了個3個任務,
1、找到分類入口,爬取分類Id、標題、url 、分類等級存儲到數據庫。
2、根據1任務爬取的分類Id,獲取分類下商品列表。爬取列表上商品部分信息,包括商品的Id、名稱、縮略圖。
3、根據2任務爬取的商品Id,獲取商品詳情頁,爬取商品詳情頁的其他信息。
一個分類下有幾百頁商品列表,而每個列表一般有22個商品。所以1任務爬取完一次,2任務要爬取幾百次(當然不會將分類下的所有頁碼都爬完,設定只爬20頁) ,3任務要爬 20 x 22次。這樣分任務的好處就是 3個任務中,不論哪個任務掛了,其他2個任務是不受影響的,可以繼續跑。
比如2任務掛掉了,1任務不受它影響,雖然3任務的需要2任務的數據,但是3任務的速度是比2任務慢了22倍還多(獲取詳情的時候 還需要在請求其他頁面)所以任務線程相同的情況下 2任務的會有很多剩余商品 3還沒有來得及跑。
調度采用了QuartZ 使用Cron配置定時任務。使用Parallel並行爬取,線程數量可以配只需要將方法和方法所需要的參數集合放到ForEach
合理配置每個任務的線程數量,我設置爬取分類線程數1,商品列表的線程是2,商品詳情爬取線程為50。爬取的速度不同線程數量就不同,而且並不是線程越高越好,這個值是不斷的調試采集相同時間的數據分析得出來的。
三、代理
現在有很多代理商,普遍分為兩種:
第一種通過接口返回代理IP和這個代理的可用時間,在這個時間段內,這個代理是可用的。注意:這種代理方式需要有個代理池,因為爬蟲一般都是多線程,如果在代理IP可用時間段內,多個線程一直使用同一個代理IP,很大可能會被封。所以保險的做法就是一個線程一個代理,降低每個代理的請求次數。
第二種代理直接給你一個固定的IP,這個代理IP沒有時間限制,代理商那邊會幫你自動幫你換不同的IP請求目標地址。
.Net Core中使用代理很簡單,因為我使用的是HttpClientFactory,所以在添加服務的時候配置 HttpClientHandler的代理就可以,需要實現一個IWebProxy類,返回對應的代理IP和端口號就可以了。
剛開始使用的是第一種代理,為了實現一個線程一個代理,我創建了一個代理池,在程序啟動的時候,每個線程都會從代理商那獲取到一個代理IP,然后放到代理池中,每次獲取代理的時候,通過代理池中隨機挑選一個代理IP,在挑選前會判斷當前代理池中的代理數量,如果小於線程數據,就會去獲取填充到代理池中。
后面發現國內的代理商的IP訪問國外網址太慢了,就換了國外代理,國外代理使用的是第二種方案。但是這個代理商不是自動幫你更換IP,而是需要每次傳遞一個隨機數 SessionId,隨機數不同,代理商訪問目標網站的IP就不同,而且要傳用戶名和密碼。
開始 我是這么做的:
發現這種只有在第一次創建HttpClient的時候才會去走配置ConfigurePrimaryHttpMessageHandler方法,之后創建HttpClient都不會走。就導致一直在使用同一個IP請求。
所以沒辦法,只能放棄使用HttpClientFactory,自己手動創建HttpClient ,然后釋放。但這種又隨之帶來一個問題HttpClient雖然釋放了,但是端口還是被占用着,目前還沒有好的解決辦法。
四、斷點續爬、數據去重
我這個業務中這兩個功能就很好實現,每次爬取完成商品頁碼,就存儲下一頁的頁碼和當前爬取的頁碼數。配置一個參數是否是斷點續爬,如果是斷點續爬,就從上次記錄的頁碼爬取商品。
數據去重,因為直接可以拿到亞馬遜的商品Id和分類Id,所以去重就變的很簡單,任務啟動的時候,會將已經爬取過的商品Id和分類Id放到緩存中,爬取的時候對比數據。
項目在服務器上跑了2個晚上,表現還是可以的,數據都正確采集到了117w數據(包含未爬取詳情的商品),最后的最后。。。。項目被砍了,因為亞馬遜的商品詳情頁數據太大,導致爬了12個小時用了40G流量,1G=6美元 ,一個月就要40x2x6x30=14400美元。忙碌了兩周,也算從零寫了一個小小的爬蟲,還算有所得。