[Python] 減少 BeautifulSoup 解析 HTML 時的記憶體用量

[Python] 減少 BeautifulSoup 解析 HTML 時的記憶體用量

之前在專案裡是使用 lxml 來解析 HTML 檔案,

速度很快,大部分狀況下也運作的不錯,

不過最近遇到有些比較怪異的 HTML 檔案,在 HTML 裡加入一堆二進位碼 ,

讓 lxml 沒辦法正確解析,導致後面的東西都沒能解出來…

 

後來是改用 BeautifulSoup,解析器也改用 Python 內建的 html.parser,

就可以正確處理這類的 HTML 檔案,速度變慢是當然,

但連記憶體的使用量也不小,只好來找找看有沒有改善的空間…

 

在 BeautifulSoup 的文件裡找到了一線曙光~

在 Parsing only part of a document 這一段裡面,

提到了如果只需要某些 tag 的話,就沒必要將所有的解析結果都儲存下來。

這樣對減少解析時間沒太大影響,但對於減少記憶體使用量就有很大的幫助~

 

先看一下原本的程式吧,這程式用 BeautifulSoup 解析 large_html.htm 這個檔案後,

印出它的 <base> 和所有的 <a> 連結:

from bs4 import BeautifulSoup

soup = BeautifulSoup(open("large_html.htm"), "html.parser")
print "Base", soup.base.attrs["href"]
for elem in soup.find_all("a"):
    print elem

 

在改善它之前,先來看一下它到底用了多少記憶體~

我的程式是在 Linux 上跑的,用 ps 去查的話時間有點難抓,

程式跑太快的話就看不到了,而且記憶體用量可能上上下下的…

查了一下,用 valgrind –tool=massif 的話,

可以比較準確的記錄 process 的記憶體用量,用法如下:

valgrind --tool=massif /usr/bin/python test_bs.py

 

執行完會產生一個 massif.out.<pid> 的檔案,

我們可以用 ms_print massif.out.xxx 來看一下結果,

最前面會用 ASCII 圖的方式秀出記憶體用量的時間軸,

大概可以看出來後來衝到了 19.72 MB:

    MB
19.72^                                                                       #
     |                                                                       #
     |                                                                       #
     |                                                                      @#
     |                                                                      @#
     |                                                                      @#
     |                                                                    @@@#
     |                                                                    @ @#
     |                                                                    @ @#
     | @:::::::::::::@::::::@:::::::@@::::::::@@::::           ::::::     @ @#
     | @: :::::: : : @::: : @: :: : @ : : : : @ : : :::::::::::: : : :::::@ @#
     | @: :::::: : : @::: : @: :: : @ : : : : @ : : :    :     : : : :   :@ @#
     | @: :::::: : : @::: : @: :: : @ : : : : @ : : :    :     : : : :   :@ @#
     | @: :::::: : : @::: : @: :: : @ : : : : @ : : :    :     : : : :   :@ @#
     | @: :::::: : : @::: : @: :: : @ : : : : @ : : :    :     : : : :   :@ @#
     | @: :::::: : : @::: : @: :: : @ : : : : @ : : :    :     : : : :   :@ @#
     | @: :::::: : : @::: : @: :: : @ : : : : @ : : :    :     : : : :   :@ @#
     | @: :::::: : : @::: : @: :: : @ : : : : @ : : :    :     : : : :   :@ @#
     | @: :::::: : : @::: : @: :: : @ : : : : @ : : :    :     : : : :   :@ @#
     | @: :::::: : : @::: : @: :: : @ : : : : @ : : :    :     : : : :   :@ @#
   0 +----------------------------------------------------------------------->Gi
     0                                                                   11.96

 

在 ms_print 輸出的最下面,也可以看到統計數值:

--------------------------------------------------------------------------------
  n        time(i)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
 54 12,846,640,583       20,094,264       18,411,862     1,682,402            0

 

接著我們來用文件裡提到的 SoupStrainer,限制 BeautifulSoup 要儲存下來的資料,

因為我們會用到 <base> 和 <a> 的東西,因此 SoupStrainer 就設定為 [“base”, “a”]:

from bs4 import BeautifulSoup, SoupStrainer

soup = BeautifulSoup(open("large_html.htm"), "html.parser", parse_only=SoupStrainer(["base", "a"]))
print "Base", soup.base.attrs["href"]
for elem in soup.find_all("a"):
    print elem

 

如果稍微比較一下執行時間的話,舊的寫法是 3.86 秒,

加上 SoupStrainer 後是 3.53 秒,有些微的改善。

重點的記憶體用量,可以看到加上 SoupStrainer 之後,

大約是 11 MB 左右,比原本的 19.72 MB 減少了 8 MB,節省了超過 1/3:

--------------------------------------------------------------------------------
  n        time(i)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
 81 12,529,311,161       11,828,064       11,264,844       563,220            0
 82 12,570,833,691       11,828,416       11,264,832       563,584            0
 83 12,612,354,220       11,828,880       11,265,724       563,156            0
 84 12,653,870,654       11,828,176       11,264,592       563,584            0
 85 12,695,390,535       11,828,496       11,264,956       563,540            0
 86 12,736,907,333       11,828,248       11,265,059       563,189            0
 87 12,778,423,947       11,829,592       11,266,056       563,536            0

 

當然記憶體用量的節省要看 HTML 檔案的內容,

不過如果只需要 HTML 裡特定元素的話,用 SoupStrainer 應該都可以有不錯的效果喔~

 

參考資料:stackoverflow: How to measure actual memory usage of an application or process?

 

 

(本頁面已被瀏覽過 821 次)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料