寫了個google play
的排行榜爬蟲,本地運行沒問題,放服務器上一直被Killed
,後來發現是服務器內存滿了。本地排查後確定問題出在下面的代碼,把問題記錄下,提供一種解決方法,也等朋友們評判原本方案是否合理 或 是否又其它更貼近需求的方法。
原本設計和問題:
原本計劃是用一個循環一次性採集不同的國家列表,形成一個集合進行鏈接去重。
然後用一個循環把集合中的鏈接依次訪問格式化成需要的數據,形成格式化數據列表。
最後初始化ORM
,再用一個循環,把格式化數據列表中的數據集中上傳。
這樣做一來是不想維護數據庫長連接,二是時間複雜度可能低些。
但是內存佔用太厲害了,因爲Google play
一個頁面源碼包含大量js
,單個頁面就會佔用1到3M不等的內存,還是不包含任何圖片的情況下。
原本代碼:
"""代碼片段,不可運行"""
"""採集android 未設置限制條件,上限 50*國家數"""
recordLogger.info("> Collecting Android ranking query_date:{}".format(today))
app_list = []
format_data = []
# 訪問不同地區排行榜,構造應用鏈接的集合app_list
for country in country_list:
app_list += RankingDataHandler.format_android_list(crawler.get_android_list(country=country))
# 訪問應用集合中的鏈接,格式化數據,追加到format_data列表中
app_list = set(app_list)
for each_path in app_list:
origin = crawler.get_andorid_detail(path=each_path)
format_data.append(RankingDataHandler.format_android_detail(origin_data=origin))
# 初始化ORM,集中上傳format_data
uploader.init()
for each in format_data:
uploader.add(each)
uploader.close()
問題原因:
直接原因: origin
每個頁面大小在1M左右,且得不到釋放,每個循環階段存放在不同的內存id中,造成內存暴漲
嘗試在每次循環使用del(origin)
和gc.collec()
均無效。
根本原因? format_data[]
作爲列表定義在循環體外,每次append
都會對origin
添加一個引用,引用沒有歸0
所以不釋放format_data[]
就不會釋放origin
(不理解的地方在於每次循環 變量不應該被覆蓋了嗎?返回的不應該是函數處理後的結果嗎?爲什麼通過id()
函數查看,每次origin佔用內存地址都不同,內存實實在在地一直在增加,沒明白這裏)
解決方法: 使用生成器
"""代碼片段,不可運行"""
recordLogger.info("> Collecting Android ranking query_date:{}".format(today))
format_data = ( # 格式化數據的生成器
RankingDataHandler.format_android_detail(crawler.get_andorid_detail(each_path))
for per_country_app_list in ( # 不同國家排行榜列表的生成器
RankingDataHandler.format_android_list(crawler.get_android_list(country=country))
for country in country_list
)
for each_path in per_country_app_list
)
uploader.init()
for each in format_data:
uploader.add(each)
gc.collect() # 這樣釋放會快點,不寫也行,不過自動釋放大約內存每增加10兆才釋放一次,而我服務器內存實在是不多了
uploader.close()
結果: 使用生成器將內存穩定到了40M左右,之前每循環一個頁面增加1~2M,但也是犧牲了一些預期要求的。
一個是原本打算集中不同國家的列表,構成一個集合進行去重(有很多別的去重方法,這裏因爲圖方便用這種),現在取消了集合去重用數據庫做更新判斷,增加了請求次數,否則每個國家的頁面也會不停的漲內存,一個頁面2-3M左右。
再一個是原本打算等數據集中處理完以後,統一初始化數據庫進行集中上傳。現在只能是維持數據庫長連接,每抓取一個頁面的就上傳一個頁面的數據,等全部完成再釋放。
當然上述方法用生成器生成式造成代碼可讀性也降低了,其實它和三層嵌套循環是一樣的,把它拆成三層循環就容易理解了,不過會多寫代碼,縮進太多了挺醜的。
記錄問題,也希望有朋友們能幫忙指點下。