Board logo

標題: 用迴圈抓資料越跑越慢,該如何釋放記憶體? [打印本頁]

作者: sasho    時間: 2014-6-7 01:53     標題: 用迴圈抓資料越跑越慢,該如何釋放記憶體?

各位前輩大家好,

我有個程式使用迴圈及querytable.add的方式抓取外部資料(抓完會執行querytable.delete)
並在匯入資料後用worksheets.copy的方式另存,
此時程式會自動新增一個活頁簿,待存檔後再將新增的活頁簿關閉

但我發現如此一來,因為每次copy工作表時都會新增一個活頁簿
如果我的迴圈一共要抓好幾百次的資料
那就會增加好幾百個活頁簿
雖然之後會關閉,但不曉得記憶體有沒有釋放掉,因為活頁簿的編號會一直增加上去
而程式跑的速度也越來越慢....@@"

可以請各位前輩幫忙解答一下嗎? 謝謝
作者: GBKEE    時間: 2014-6-7 06:43

本帖最後由 GBKEE 於 2014-6-21 05:18 編輯

回復 1# sasho
  1. Option Explicit
  2. Sub Ex()
  3.     Dim i As Integer, rng As Range
  4.     With ActiveSheet
  5.         If .QueryTables.Count = 0 Then .QueryTables.Add "URL;", .[a1]
  6.         For i = 3 To 17 Step 2
  7.             '迴圈中ㄧ直的 querytable.add 檔案會胖起來,導致程式的速度越來越慢
  8.             With .QueryTables(1)
  9.                 .Connection = "URL;http://forum.twbts.com/thread-635-1-1.html"
  10.                 .WebSelectionType = xlSpecifiedTables
  11.                 .WebFormatting = xlWebFormattingNone
  12.                 .WebTables = i & ""
  13.                 .WebPreFormattedTextToColumns = True
  14.                 .WebConsecutiveDelimitersAsOne = True
  15.                 .WebSingleBlockTextImport = False
  16.                 .WebDisableDateRecognition = False
  17.                 .WebDisableRedirections = False
  18.                 .Refresh BackgroundQuery:=False
  19.                 Wb_Save .ResultRange, i
  20.             End With
  21.         Next
  22.    End With
  23. End Sub
  24. Private Sub Wb_Save(Rng As Range, i As Integer)  '副程式:新增活頁簿,存檔
  25.     With Workbooks.Add(1)
  26.         Rng.Copy .Sheets(1).[a1]
  27.         .Close True, "d:\test_" & i & ".xls"
  28.         '關閉存檔
  29.     End With
  30. End Sub
複製代碼

作者: c_c_lai    時間: 2014-6-7 07:01

本帖最後由 c_c_lai 於 2014-6-7 07:06 編輯
各位前輩大家好,

我有個程式使用迴圈及querytable.add的方式抓取外部資料(抓完會執行querytable.delete) ...
sasho 發表於 2014-6-7 01:53

依你描述作業過程,勢必會愈來愈慢,EXCEL 本身在執行時會另外產生一工作站暫存檔,
它會隨時動態地將你所有異動狀態紀錄於此檔案內,它承擔了所有驗對、變動、刪除、
新增以及記憶體的耗用,於檔案不斷增大時,其負荷亦跟隨之增大。
解決方法可將須不斷增加的檔案存於另一工作檔,不用時則隨時將它關閉,如此作業
再來評估成效。就如上 GBKEE 版大所展示的程式碼一樣,處理完畢即隨手將它關閉。
作者: sasho    時間: 2014-6-16 18:12

本帖最後由 sasho 於 2014-6-16 18:16 編輯

回復 2# GBKEE

感謝G大的指點,但可能我表達的不是很清楚,所以讓大家誤會我的問題了
跟G大所示範的一樣,程式再匯入網頁資料後都會另存成一個CSV檔案並關閉,
同時,將所新增的querytable delete掉,程式碼如下:
  1. Sub 取得資料(strURL As String, Table As String)

  2. Dim xlSheet As Excel.Worksheet
  3. Set xlSheet = Sheets("Temp")

  4. Do
  5.     Application.DisplayAlerts = False
  6.      With xlSheet.QueryTables.Add("URL;" & strURL, xlSheet.Cells(1, 1))
  7.          .WebFormatting = xlWebFormattingNone
  8.          .WebSelectionType = xlSpecifiedTables
  9.          .WebTables = Table
  10.          .BackgroundQuery = False

  11.          On Error Resume Next
  12.          Do
  13.              Err.Clear
  14.              .Refresh 0
  15.             
  16.              If Err.Number Then
  17.                  Application.Wait Now + TimeValue("00:00:01")
  18.              End If

  19.          Loop Until Err.Number = 0
  20.          
  21.          .Delete
  22.          'If Err.Number <> 0 Then Err.Clear: MsgBox Err.Number    '被免資料抓取不成功,而顯示訊息
  23.          
  24.          On Error GoTo 0
  25.      End With

  26.      If Err.Number = 0 Then
  27.          Application.DisplayAlerts = True
  28.          Exit Sub
  29.      End If
  30. Loop

  31. 儲存CSV DownloadDate,id
  32. End Sub

  33. Sub 儲存CSV(SaveDate As String, CSVname As String)
  34.    
  35. Dim TestObj As Object
  36. Dim CSVfile As String, CSVfolder As String
  37. Dim TestFolder As Boolean
  38.    
  39.     FilePath = "D:\TSE\"
  40.     CSVfolder = FilePath & SaveDate & "\"
  41.     CSVfile = CSVfolder & CSVname & "_" & SaveDate & ".csv"
  42.     'Debug.Print CSVfile
  43.    
  44.     Set TestObj = CreateObject("Scripting.FileSystemObject")
  45.     TestFolder = TestObj.FolderExists(CSVfolder)
  46.     If TestFolder = False Then TestObj.CreateFolder (CSVfolder)

  47.     On Error Resume Next
  48.     Kill CSVfile
  49.     On Error GoTo 0

  50.     Worksheets(Tempname).Copy
  51.     Application.DisplayAlerts = False
  52.     With ActiveWorkbook
  53.         .SaveAs FileName:=CSVfile, FileFormat:=xlCSV
  54.         .Close 0
  55.     End With
  56.     Application.DisplayAlerts = True
  57.    
  58. End Sub
複製代碼
我的問題是在於,由於我用迴圈一共要抓幾百筆的資料
我從儲存下來的檔案最後變動時間觀察,發現一開始可能一秒鐘可以抓五到六個檔案
但到後面,可能一秒鐘就只能抓兩個檔案,檔案大小都差不多
一直不知道為什麼速度會越來越慢,每抓完一筆檔案就執行一次 doevents
所以應該不至於造成CPU資源被吃光
唯一能想到的就是,每次另存一個檔案,就會新開一個活頁簿
比方說,活頁簿1、活頁簿2、活頁簿3....到最後的活頁簿999
雖然把資料複製過去後、另存新檔後該活頁簿就會關閉,但EXCEL好像還是會占用到記憶體資源   
是不是因為這樣才導致速度越來越慢呢?

還是說,是因為我複製的方式不正確才會導致記憶體增加的呢?
G大跟我的差別在於,匯入資料後G大是workbook.add然後複製匯入的range
而我則是直接將整個worksheet copy
不曉得這兩者的方式有差嗎

謝謝各位前輩指點 m(_ _)m
作者: stillfish00    時間: 2014-6-16 19:42

回復 4# sasho
我會先把呼叫
  1. 儲存CSV DownloadDate,id
複製代碼
改為
  1. t = timer
  2. 儲存CSV DownloadDate,id
  3. debug.print timer-t
複製代碼
先觀察確認 時間變長的地方 是在 存檔這部分。
作者: sasho    時間: 2014-6-16 23:02

回復 5# stillfish00

時間變長的部分,應該是在匯入網頁資料所花的時間
一開始匯入很順,但越到後面就越卡,不知道是什麼原因

同時我有觀察EXCEL在工作管理員中占用的記憶體容量
從程式執行前到程式執行後,整個記憶體增加了不少
一樣也是不知道原因

懇請各位大大解救一下
作者: stillfish00    時間: 2014-6-17 09:55

回復 7# GBKEE
Hi 版大,
我不曉得是不是EXCEL版本差異,
我在Excel2010中,刪除QueryTable物件就會直接刪除Names中的項目了。
作者: stillfish00    時間: 2014-6-17 14:08

本帖最後由 stillfish00 於 2014-6-17 14:09 編輯

回復 9# GBKEE
QueryTables.Delete 只會刪除連線和定義的名稱,不會刪除取得的資料

而樓主用迴圈執行多筆時資料會一直往右累積增加
這也許可以改RefreshStyle為xlOverwriteCells來防止(預設是xlInsertDeleteCells)
像這樣
  1. Sub Ex()
  2.     Dim i As Integer, Rng As Range
  3.     With ActiveSheet
  4.         For i = 3 To 7 Step 2
  5.             With .QueryTables.Add("URL;http://forum.twbts.com/thread-635-1-1.html", .Cells(1, 1))
  6.                 .WebSelectionType = xlSpecifiedTables
  7.                 .WebFormatting = xlWebFormattingNone
  8.                 .WebTables = i & ""
  9.                 .WebPreFormattedTextToColumns = True
  10.                 .WebConsecutiveDelimitersAsOne = True
  11.                 .WebSingleBlockTextImport = False
  12.                 .WebDisableDateRecognition = False
  13.                 .WebDisableRedirections = False
  14.                
  15.                 .RefreshStyle = xlOverwriteCells
  16.                 .Refresh BackgroundQuery:=False
  17.                 Wb_Save .ResultRange, i
  18.                
  19.                 .Delete
  20.             End With
  21.         Next
  22.    End With
  23. End Sub
複製代碼

作者: sasho    時間: 2014-6-17 17:16

回復 8# stillfish00

我使用的是EXCEL 2010,執行QueryTables.delete並不會將匯入資料刪除,只會把連線刪除而已

另外,我在另存匯入資料後,也會在執行worksheets.cells.delete,所以資料是不會向右累積的

感謝
作者: GBKEE    時間: 2014-6-18 06:17

回復 9# sasho

參考 這裡
作者: sasho    時間: 2014-6-18 17:03

回復 10# GBKEE


感謝G大的分享

不過小弟我還是不太了解,為什麼在EXCEL中用迴圈匯入資料會越匯越慢....
我使用的是win 7、64位元、6G記憶體
平常有清除系統垃圾的習慣,且在執行程式時,不論CPU或是記憶體都顯示尚有多餘的資源(從工作管理員觀察)
況且,如果是因為系統資源不足所導致的問題,應該不會出現執行程式初期速度較快,而執行到後面速度就整個拖下來的情況才是
應該是從頭到尾都很慢才是
不曉得我這樣推論是否正確呢?
作者: GBKEE    時間: 2014-6-18 17:13

回復 11# sasho
你使用的是EXCEL 2010 ,可否上傳你的檔案.
我來試試  XP、32位元、1G記憶體 ,2003版本的速度
作者: sasho    時間: 2014-6-24 18:45

回復 12# GBKEE


感謝G版大的幫忙,因為我無法上傳檔案,所以把檔案放在如下的dropbox的網址
另外,最近幾天測試的結果,發現問題是出在兩個部分
一個是匯入網頁資料,另一個是將匯入資料另存成csv檔
根據記錄的資料,這兩個動作都會隨著迴圈越跑越多而越來越慢
還請G版大指點一下,謝謝

https://www.dropbox.com/s/n0ycnc7q4gqtbrb/%E6%8A%93%E5%8F%96%E4%B8%8A%E5%B8%82%E8%B3%87%E6%96%99.xls
作者: GBKEE    時間: 2014-6-25 13:20

本帖最後由 GBKEE 於 2014-6-25 13:39 編輯

回復 13# sasho
你原本的 Sub 執行(),在我的PC沒有你所說的越來越慢的情形.速度與Main() 的記錄檔差不多.

整理一下,附上 Sub Main() 的記錄檔

[attach]18553[/attach]
  1. Option Explicit
  2. Dim IE As Object, Query_Sh As Worksheet, CsvPath As String, SaveDate As String
  3. Dim t As Date, StartTime As Date, 記錄檔 As String, stockid As Range, spListCount As Integer
  4. Sub Main()
  5.     Dim i As Integer
  6.     t = Time
  7.     StartTime = Time
  8.     CsvPath = "D:\TSE\"
  9.     目錄 CsvPath
  10.     記錄檔 = CsvPath & "Main_Record.TXT"
  11.     If Dir(記錄檔) <> "" Then Kill 記錄檔
  12.     暫存頁 "temp"
  13.     xRecond 0, "程式開始執行" & vbCrLf
  14.     Set stockid = Sheets("工作表1").Range("A2")
  15.     stockid.Parent.Activate
  16.     Do While stockid <> ""
  17.         Application.ScreenUpdating = True
  18.         stockid.Select
  19.         Application.ScreenUpdating = False
  20.         StartTime = Time
  21.         spListCount = 資料頁數
  22.         If spListCount > 0 Then
  23.             i = i + 1
  24.             xRecond i, stockid & vbTab & "資料匯入"
  25.             資料匯入
  26.             整理
  27.             存檔
  28.             xRecond i, stockid.Value & vbTab & "存檔完畢 " & Format(Time - StartTime, "共SS秒") & vbCrLf
  29.         End If
  30.         Set stockid = stockid.Offset(1)
  31.     Loop
  32.     IE.Quit
  33.     Application.DisplayAlerts = False
  34.     Query_Sh.Delete
  35.     Application.DisplayAlerts = True
  36.     Workbooks.Open 記錄檔
  37.     MsgBox "共存 ""(" & i & ") csv檔完畢" & vbTab & "費時 " & Format(Time - t, "nn分ss秒")
  38. End Sub
  39. Private Sub 暫存頁(temp As String)
  40.     On Error Resume Next
  41.     Set Query_Sh = Sheets(temp)
  42.     If Err.Number = 9 Then
  43.         Sheets.Add(, Sheets(1)).Name = temp
  44.         Set Query_Sh = Sheets(temp)
  45.     End If
  46. End Sub
  47. Private Sub 資料匯入()
  48.     Dim strURL As String
  49.     strURL = "URL;" & "http://bsr.twse.com.tw/bshtm/bsContent.aspx?StartNumber=" & stockid & "&FocusIndex=All_" & spListCount
  50.     With Query_Sh
  51.         .UsedRange.Clear
  52.         With .QueryTables.Add(strURL, Query_Sh.[a1])
  53.             .WebFormatting = xlWebFormattingNone
  54.             .WebSelectionType = xlSpecifiedTables
  55.             .WebTables = "5,table2"
  56.             .Refresh 0
  57.             .Delete
  58.         End With
  59.     End With
  60. End Sub
  61. Private Sub 整理()
  62.     Dim i As Integer
  63.     With Sheets("temp")
  64.         SaveDate = Format(.Range("B1"), "YYYYMMDD")
  65.         With .UsedRange.Range("A:A")
  66.             .SpecialCells(xlCellTypeConstants, xlTextValues).EntireRow.Delete
  67.             .SpecialCells(xlCellTypeBlanks).EntireRow.Delete
  68.         End With
  69.         .UsedRange.Columns("F:J").Cut
  70.         .Range("A" & .Rows.Count).End(xlUp).Offset(1).Insert Shift:=xlDown
  71.         .UsedRange.Sort Key1:=.Range("A1"), Order1:=xlAscending, Header:=xlNo
  72.         .UsedRange.Columns("B:B").Insert Shift:=xlToRight
  73.         .UsedRange.Columns(1) = SaveDate
  74.         .UsedRange.Columns(2) = stockid
  75.          For i = 1 To .UsedRange.Rows.Count
  76.             .Cells(i, 3) = Left(.Cells(i, 3), 4)
  77.             .Cells(i, 5) = .Cells(i, 5).Value / 1000
  78.             .Cells(i, 6) = .Cells(i, 6).Value / 1000
  79.         Next
  80.     End With
  81. End Sub
  82. Private Sub 目錄(xPath As String)
  83.     Dim SP As Variant, P As String, i As Integer
  84.     SP = Split(xPath, "\")
  85.     P = SP(0)
  86.     With CreateObject("Scripting.FileSystemObject")
  87.         For i = 1 To UBound(SP)
  88.             P = P & "\" & SP(i)
  89.             If .FolderExists(P) = False Then .CreateFolder (P)
  90.         Next
  91.     End With
  92. End Sub
  93. Private Sub 存檔()
  94.     Dim CSVfolder As String, CSVfile As String
  95.     CSVfolder = CsvPath & SaveDate & "\"
  96.     目錄 CSVfolder
  97.     CSVfile = CSVfolder & stockid & "_" & SaveDate & ".csv"
  98.     If Dir(CSVfile) <> "" Then Kill CSVfile
  99.     Query_Sh.Copy
  100.     With ActiveWorkbook
  101.         .SaveAs Filename:=CSVfile, FileFormat:=xlCSV
  102.         .Close 0
  103.     End With
  104. End Sub
  105. Private Sub xRecond(i As Integer, xSub As String)
  106.     Dim S As String
  107.     S = Time & vbTab & Format(Time - t, " 第nn分ss秒") & vbTab & " 第 " & i & " 個Csv檔 " & xSub
  108.     Close #1
  109.     Open 記錄檔 For Append As #1
  110.     Print #1, S
  111.     Close #1
  112.     Application.StatusBar = S
  113. End Sub
  114. Private Function 資料頁數() As Integer   '取得頁數
  115.     If IE Is Nothing Then
  116.         Set IE = CreateObject("InternetExplorer.Application")
  117.         IE.Navigate "http://bsr.twse.com.tw/bshtm/bsMenu.aspx"
  118.         IE.Visible = True  '可不顯示
  119.     End If
  120.     With IE
  121.         Do:  Loop While .Busy Or IE.ReadyState <> 4
  122.         With .document
  123.             .getElementByID("txtTASKNO").Value = stockid
  124.             .getElementByID("btnOK").Click
  125.             Do: Loop While IE.Busy Or IE.ReadyState <> 4 Or .getElementByID("sp_ListCount") Is Nothing
  126.             資料頁數 = Val(.getElementByID("sp_ListCount").innertext)
  127.         End With
  128.     End With
  129. End Function
複製代碼

作者: sasho    時間: 2014-6-27 21:36

回復 14# GBKEE

實際測試G大所提供的程式,確實比之前的那個還要快很多
仔細比對後,我發現主要的問題是在於調整格式這部分

因為這些資料我會再匯入到mysql當中,所以資料格式不能出錯
所以在最一開始我所提供的程式中,在sub 調整格式()的最後,我有再加上一段指令
cells.NumberFormatLocal = "G/通用格式"
而這段指令是G大所提供的程式碼中所沒有的
目前推測就是因為這段敘述,導致整個程式速度降了下來
以及程式執行過程,EXCEL所占用的記憶體不斷攀高

感謝G大的指點,受教了!
作者: GBKEE    時間: 2014-6-28 04:39

回復 15# sasho
  1. Cells.NumberFormatLocal = "G/通用格式"
  2. 可改成(縮小範圍)
  3. UsedRange.NumberFormatLocal = "G/通用格式"
複製代碼





歡迎光臨 麻辣家族討論版版 (http://forum.twbts.com/)