2017-06-15 76 views
0

我正在做一些從許多來源(有數百萬條記錄)的爬蟲,但我有問題涉及內存不足。我GOOGLE了一下,發現了一些資源,但它並沒有解決我的問題。堆滿內存時抓取數百萬條記錄

similar question沒有解決不了我的問題

這裏是我的示例代碼:

function getContent() { 

    let d = q.defer(); 

    let urls = []; 

    array.forEach(function(mang, index) { 
     for (let i = 1; i <= 600000; i++) { 
      urls.push(function (callback) { 
       setTimeout(function() { 
        let link = 'http://something.com/' + i; 
        let x = link; 

        let options = { 
         url: link, 
         headers: { 
          'User-Agent': 'something' 
         } 
        }; 

        function callback1(error, response, html) { 
         if (!error) { 
          let $ = cheerio.load(html); 
          let tag_name = $('h1').text(); 
          tag_name = tag_name.trim(); 
          let tag_content = $('#content-holder').find('div').text(); 
           let tagObject = new Object(); 

           tagObject.tag_name = tag_name; 
           tagObject.tag_content = tag_content; 
           tagObject.tag_number = i; 

           tagArray.push(tagObject); 

            for (let v = 0; v < tagArray.length; v++) { 
             //console.log("INSERT INTO `tags` (tag_name, content, story_id, tag_number) SELECT * FROM (SELECT " + "'" + tagArray[v].tag_name + "'" + "," + "'" + tagArray[v].tag_content + "','" + array[c].story_id + "','" + tagArray[v].tag_number + "' as ChapName) AS tmp WHERE NOT EXISTS (SELECT `tag_name` FROM `tags` WHERE `tag_name`=" + "'" + tagArray[v].tag_name + "'" + ") LIMIT 1"); 
             db.query("INSERT INTO `tags` (tag_name, content) " + 
              "SELECT * FROM (SELECT " + "'" + tagArray[v].tag_name + "'" + "," + "'" + tagArray[v].tag_content + "','" + "' as TagName) AS tmp " + 
              "WHERE NOT EXISTS (SELECT `tag_name` FROM `tags` WHERE `tag_name`=" + "'" + tagArray[v].tag_name + "'" + ") " + 
              "LIMIT 1", (err) => { 
              if (err) { 
               console.log(err); 
              } 
             }); 
            } 
            urls = null; 
            global.gc(); 

           console.log("Program is using " + heapUsed + " bytes of Heap.") 

         } 
        } 

        request(options, callback1); 
        callback(null, x); 
       }, 15000); 
      }); 
     } 
    }); 

    d.resolve(urls); 
    return d.promise; 
} 

getContent() 
    .then(function (data) { 
     let tasks = data; 
     console.log("start data"); 
     async.parallelLimit(tasks, 10,() => { 
      console.log("DONE "); 
     }); 
    }) 

我試圖用global.gc()函數,但似乎無法有效是

+1

你*真的*需要創建一個最小可行的問題。展示你的工作,解釋你在找什麼等等。這個問題需要我們對你的方法做出太多的假設。 – Paul

+0

@Paul我編輯了一些示例代碼:( –

+0

添加了一個答案,但我不明白,'數組'是什麼?你正在做一個forEach,但我看不到你在哪裏使用其中的項目來完成任何事情,並且你意識到你正在向'array'中的堆*每項*添加至少120萬個函數,對嗎?我說至少因爲我不記得callback1是否會創建一次或每個範圍一次,很確定它是後者。 – Paul

回答

1

啊,我現在看到你的問題。你正試圖在一個循環中完成所有內容。因爲你創建的每個匿名函數都被添加到堆中,所以這種方式讓任何非常繁重的工作都變得瘋狂。另外,它不是很健壯。如果您在第45萬次抓取中發生網絡中斷,會發生什麼情況?你失去了一切,重新開始?

看起來有一個小批量運行的工作。我之前使用過像Kue這樣的任務管理器,但坦率地說,您需要做的就是先開始通過一些合理的數字(例如10或25)填充您的URL數組。一種方法是創建一個包含所有網址的表格,一個已成功抓取的標誌,或者如果您計劃再次抓取它們的最後抓取日期,我有時間了。

然後查詢所有未被抓取的網址(或者最近一次抓取的日期早於某個日期,例如一週前),並將結果限制爲10或25或任何其他值。首先抓取並存儲這些內容,我可能會使用像async.js#map或Promise.all這樣的工具,而不是您當前使用的循環。

如果所有的URL都在同一個域中,那麼您可能希望在每個請求之間有一個短暫的超時,以便尊重這些資源。

一批完成後,查詢您的數據庫的下一批,並重復。

根據你的架構,讓這個程序變得更簡單,除了得到一個批次並解決一個批次的抓取問題外,什麼也不做。然後,您可以在cron作業或Windows服務上運行它,以每5分鐘或每15分鐘或其他方式運行。

現在在移動設備上,但我會盡力在稍後的筆記本電腦上爲您提供一個代碼示例,如果您需要的話。