Python爬蟲在一個循環體訪問頁面、處理數據並構造數據列表時造成的內存不斷增加而Killed, 如何釋放?

寫了個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左右。

再一個是原本打算等數據集中處理完以後,統一初始化數據庫進行集中上傳。現在只能是維持數據庫長連接,每抓取一個頁面的就上傳一個頁面的數據,等全部完成再釋放。

當然上述方法用生成器生成式造成代碼可讀性也降低了,其實它和三層嵌套循環是一樣的,把它拆成三層循環就容易理解了,不過會多寫代碼,縮進太多了挺醜的。

記錄問題,也希望有朋友們能幫忙指點下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章