歡迎光臨
每天分享高質量文章

如何用 Python 解析 HTML | Linux 中國

用一些簡單的指令碼,可以很容易地清理檔案和其它大量的 HTML 檔案。但是首先你需要解析它們。
— Greg Pittman


致謝
編譯自 | https://opensource.com/article/18/1/parsing-html-python 
 作者 | Greg Pittman
 譯者 | Liang Chen (Flowsnow) ? ? ? 共計翻譯:18 篇 貢獻時間:838 天

用一些簡單的指令碼,可以很容易地清理檔案和其它大量的 HTML 檔案。但是首先你需要解析它們。

作為 Scribus 檔案團隊的長期成員,我要隨時瞭解最新的原始碼更新,以便對檔案進行更新和補充。 我最近在剛升級到 Fedora 27 系統的計算機上使用 Subversion 進行檢出操作時,對於下載該檔案所需要的時間我感到很驚訝,檔案由 HTML 頁面和相關影象組成。 我恐怕該專案的檔案看起來比專案本身大得多,並且懷疑其中的一些內容是“僵屍”檔案——不再使用的 HTML 檔案以及 HTML 中無法訪問到的影象。

我決定為自己建立一個專案來解決這個問題。 一種方法是搜尋未使用的現有影象檔案。 如果我可以掃描所有 HTML 檔案中的影象取用,然後將該串列與實際影象檔案進行比較,那麼我可能會看到不匹配的檔案。

這是一個典型的影象標簽:

  1. src="images/edit_shapes.png" ALT="Edit examples" ALIGN=left>

我對 src= 之後的第一組引號之間的部分很感興趣。 在尋找了一些解決方案後,我找到一個名為 BeautifulSoup[1] 的 Python 模組。 指令碼的核心部分如下所示:

  1. soup = BeautifulSoup(all_text, 'html.parser')

  2. match = soup.findAll("img")

  3. if len(match) > 0:

  4.    for m in match:

  5.        imagelist.append(str(m))

我們可以使用這個 findAll 方法來挖出圖片標簽。 這是一小部分輸出:

  1. src="images/pdf-form-ht3.png"/> src="images/pdf-form-ht4.png"/> src="images/pdf-form-ht5.png"/> src="images/pdf-form-ht6.png"/> align="middle" alt="GSview - Advanced Options Panel" src="images/gsadv1.png" title="GSview - Advanced Options Panel"/> align="middle" alt="Scribus External Tools Preferences" src="images/gsadv2.png" title="Scribus External Tools Preferences"/>

到現在為止還挺好。我原以為下一步就可以搞定了,但是當我在指令碼中嘗試了一些字串方法時,它傳回了有關標記的錯誤而不是字串的錯誤。 我將輸出儲存到一個檔案中,併在 KWrite[2] 中進行編輯。 KWrite 的一個好處是你可以使用正則運算式(regex)來做“查詢和替換”操作,所以我可以用 \n 替換 ,這樣可以看得更清楚。 KWrite 的另一個好處是,如果你用正則運算式做了一個不明智的選擇,你還可以撤消。

但我認為,肯定有比這更好的東西,所以我轉而使用正則運算式,或者更具體地說 Python 的 re 模組。 這個新指令碼的相關部分如下所示:

  1. match = re.findall(r'src="(.*)/>', all_text)

  2. if len(match)>0:

  3.    for m in match:

  4.        imagelist.append(m)

它的一小部分輸出如下所示:

  1. images/cmcanvas.png" title="Context Menu for the document canvas" alt="Context Menu for the document canvas" />


title=“EPS preview in a file dialog” alt=“EPS preview in a file dialog” images/epsimp5.png” title=”Colors imported from an EPS file” alt=”Colors imported from an EPS file” images/eps-imp4.png” title=“EPS font substitution” alt=“EPS font substitution” images/epsimp2.png” title=”EPS import progress” alt=”EPS import progress” images/eps-imp3.png” title=“Bitmap conversion failure” alt=“Bitmap conversion failure”

乍一看,它看起來與上面的輸出類似,並且附帶有去除影象的標簽部分的好處,但是有令人費解的是還夾雜著表格標簽和其他內容。 我認為這涉及到這個正則運算式 src="(.*)/>,這被稱為貪婪,意味著它不一定停止在遇到 /> 的第一個實體。我應該補充一點,我也嘗試過 src="(.*)",這真的沒有什麼更好的效果,我不是一個正則運算式專家(只是做了這個),找了各種方法來改進這一點但是並沒什麼用。

做了一系列的事情之後,甚至嘗試了 Perl 的 HTML::Parser 模組,最終我試圖將這與我為 Scribus 編寫的一些指令碼進行比較,這些指令碼逐個字元的分析文字內容,然後採取一些行動。 為了最終目的,我終於想出了所有這些方法,並且完全不需要正則運算式或 HTML 解析器。 讓我們回到展示的那個 img 標簽的例子。

  1. src="images/edit_shapes.png" ALT="Edit examples" ALIGN=left>

我決定回到 src= 這一塊。 一種方法是等待 s 出現,然後看下一個字元是否是 r,下一個是 c,下一個是否 =。 如果是這樣,那就匹配上了! 那麼兩個雙引號之間的內容就是我所需要的。 這種方法的問題在於需要連續識別上面這樣的結構。 一種檢視代表一行 HTML 文字的字串的方法是:

  1. for c in all_text:

但是這個邏輯太亂了,以至於不能持續匹配到前面的 c,還有之前的字元,更之前的字元,更更之前的字元。

最後,我決定專註於 = 並使用索引方法,以便我可以輕鬆地取用字串中的任何先前或將來的字元。 這裡是搜尋部分:

  1.    index = 3

  2.    while index < linelength:

  3.        if (all_text[index] == '='):

  4.            if (all_text[index-3] == 's') and (all_text[index-2] == 'r') and (all_text[index-1] == 'c'):

  5.                imagefound(all_text, imagelist, index)

  6.                index += 1

  7.            else:

  8.                index += 1

  9.        else:

  10.            index += 1

我用第四個字元開始搜尋(索引從 0 開始),所以我在下麵沒有出現索引錯誤,並且實際上,在每一行的第四個字元之前不會有等號。 第一個測試是看字串中是否出現了 =,如果沒有,我們就會前進。 如果我們確實看到一個等號,那麼我們會看前三個字元是否是 sr 和 c。 如果全都匹配了,就呼叫函式 imagefound

  1. def imagefound(all_text, imagelist, index):

  2.    end = 0

  3.    index += 2

  4.    newimage = ''

  5.    while end == 0:

  6.        if (all_text[index] != '"'):

  7.            newimage = newimage + all_text[index]

  8.            index += 1

  9.        else:

  10.            newimage = newimage + '\n'

  11.            imagelist.append(newimage)

  12.            end = 1

  13.            return

我們給函式傳送當前索引,它代表著 =。 我們知道下一個字元將會是 ",所以我們跳過兩個字元,並開始向名為 newimage 的控制字串新增字元,直到我們發現下一個 ",此時我們完成了一次匹配。 我們將字串加一個換行符(\n)新增到串列 imagelist 中並傳回(return),請記住,在剩餘的這個 HTML 字串中可能會有更多圖片標簽,所以我們馬上回到搜尋迴圈中。

以下是我們的輸出現在的樣子:

  1. images/text-frame-link.png

  2. images/text-frame-unlink.png

  3. images/gimpoptions1.png

  4. images/gimpoptions3.png

  5. images/gimpoptions2.png

  6. images/fontpref3.png

  7. images/font-subst.png

  8. images/fontpref2.png

  9. images/fontpref1.png

  10. images/dtp-studio.png

啊,乾凈多了,而這隻花費幾秒鐘的時間。 我本可以將索引前移 7 步來剪下 images/ 部分,但我更願意把這個部分儲存下來,以確保我沒有剪下掉影象檔案名的第一個字母,這很容易用 KWrite 編輯成功 —— 你甚至不需要正則運算式。 做完這些並儲存檔案後,下一步就是執行我編寫的另一個指令碼 sortlist.py

  1. #!/usr/bin/env python

  2. # -*- coding: utf-8  -*-

  3. # sortlist.py

  4. import os

  5. imagelist = []

  6. for line in open('/tmp/imagelist_parse4.txt').xreadlines():

  7.    imagelist.append(line)

  8. imagelist.sort()

  9. outfile = open('/tmp/imagelist_parse4_sorted.txt', 'w')

  10. outfile.writelines(imagelist)

  11. outfile.close()

這會讀取檔案內容,並儲存為串列,對其排序,然後另存為另一個檔案。 之後,我可以做到以下幾點:

  1. ls /home/gregp/development/Scribus15x/doc/en/images/*.png > '/tmp/actual_images.txt'

然後我需要在該檔案上執行 sortlist.py,因為 ls 方法的排序與 Python 不同。 我原本可以在這些檔案上執行比較指令碼,但我更願意以可視方式進行操作。 最後,我成功找到了 42 個影象,這些影象沒有來自檔案的 HTML 取用。

這是我的完整解析指令碼:

  1. #!/usr/bin/env python

  2. # -*- coding: utf-8  -*-

  3. # parseimg4.py

  4. import os

  5. def imagefound(all_text, imagelist, index):

  6.    end = 0

  7.    index += 2

  8.    newimage = ''

  9.    while end == 0:

  10.        if (all_text[index] != '"'):

  11.            newimage = newimage + all_text[index]

  12.            index += 1

  13.        else:

  14.            newimage = newimage + '\n'

  15.            imagelist.append(newimage)

  16.            end = 1

  17.            return

  18. htmlnames = []

  19. imagelist = []

  20. tempstring = ''

  21. filenames = os.listdir('/home/gregp/development/Scribus15x/doc/en/')

  22. for name in filenames:

  23.    if name.endswith('.html'):

  24.        htmlnames.append(name)

  25. #print htmlnames

  26. for htmlfile in htmlnames:

  27.    all_text = open('/home/gregp/development/Scribus15x/doc/en/' + htmlfile).read()

  28.    linelength = len(all_text)

  29.    index = 3

  30.    while index < linelength:

  31.        if (all_text[index] == '='):

  32.            if (all_text[index-3] == 's') and (all_text[index-2] == 'r') and

  33. (all_text[index-1] == 'c'):

  34.                imagefound(all_text, imagelist, index)

  35.                index += 1

  36.            else:

  37.                index += 1

  38.        else:

  39.            index += 1

  40. outfile = open('/tmp/imagelist_parse4.txt', 'w')

  41. outfile.writelines(imagelist)

  42. outfile.close()

  43. imageno = len(imagelist)

  44. print str(imageno) + " images were found and saved"

指令碼名稱為 parseimg4.py,這並不能真實反映我陸續編寫的指令碼數量(包括微調的和大改的以及丟棄並重新開始寫的)。 請註意,我已經對這些目錄和檔案名進行了硬編碼,但是很容易變得通用化,讓使用者輸入這些資訊。 同樣,因為它們是工作指令碼,所以我將輸出傳送到 /tmp目錄,所以一旦重新啟動系統,它們就會消失。

這不是故事的結尾,因為下一個問題是:僵屍 HTML 檔案怎麼辦? 任何未使用的檔案都可能會取用影象,不能被前面的方法所找出。 我們有一個 menu.xml 檔案作為聯機手冊的目錄,但我還需要考慮 TOC(LCTT 譯註:TOC 是 table of contents 的縮寫)中列出的某些檔案可能取用了不在 TOC 中的檔案,是的,我確實找到了一些這樣的檔案。

最後我可以說,這是一個比影象搜尋更簡單的任務,而且開發的過程對我有很大的幫助。

關於作者

Greg Pittman 是 Kentucky 州 Louisville 市的一名退休的神經學家,從二十世紀六十年代的 Fortran IV 語言開始長期以來對計算機和程式設計有著濃厚的興趣。 當 Linux 和開源軟體出現的時候,Greg 深受啟發,去學習更多知識,並實現最終貢獻的承諾。 他是 Scribus 團隊的成員。更多關於我[3]


via: https://opensource.com/article/18/1/parsing-html-python

作者:Greg Pittman[3] 譯者:Flowsnow 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出

贊(0)

分享創造快樂

© 2024 知識星球   網站地圖