如何用Python解析HTML?

开发 后端 前端
用一些简单的脚本,可以很容易地清理文档和其它大量的 HTML 文件。但是首先你需要解析它们。我决定为自己创建一个项目来解决这个问题。 一种方法是搜索未使用的现有图像文件。

如何用Python解析HTML?

用一些简单的脚本,可以很容易地清理文档和其它大量的 HTML 文件。但是首先你需要解析它们。

作为 Scribus 文档团队的长期成员,我要随时了解***的源代码更新,以便对文档进行更新和补充。 我最近在刚升级到 Fedora 27 系统的计算机上使用 Subversion 进行检出操作时,对于下载该文档所需要的时间我感到很惊讶,文档由 HTML 页面和相关图像组成。 我恐怕该项目的文档看起来比项目本身大得多,并且怀疑其中的一些内容是“僵尸”文档——不再使用的 HTML 文件以及 HTML 中无法访问到的图像。

我决定为自己创建一个项目来解决这个问题。 一种方法是搜索未使用的现有图像文件。 如果我可以扫描所有 HTML 文件中的图像引用,然后将该列表与实际图像文件进行比较,那么我可能会看到不匹配的文件。

这是一个典型的图像标签:

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

我对 src= 之后的***组引号之间的部分很感兴趣。 在寻找了一些解决方案后,我找到一个名为 BeautifulSoup 的 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. <img src="images/pdf-form-ht3.png"/><img src="images/pdf-form-ht4.png"/><img src="images/pdf-form-ht5.png"/><img src="images/pdf-form-ht6.png"/><img align="middle" alt="GSview - Advanced Options Panel" src="images/gsadv1.png" title="GSview - Advanced Options Panel"/><img align="middle" alt="Scribus External Tools Preferences" src="images/gsadv2.png" title="Scribus External Tools Preferences"/>

到现在为止还挺好。我原以为下一步就可以搞定了,但是当我在脚本中尝试了一些字符串方法时,它返回了有关标记的错误而不是字符串的错误。 我将输出保存到一个文件中,并在 KWrite 中进行编辑。 KWrite 的一个好处是你可以使用正则表达式(regex)来做“查找和替换”操作,所以我可以用 \n<img 替换 <img,这样可以看得更清楚。 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" /></td></tr></table><br images/eps-imp1.png" title="EPS preview in a file dialog" alt="EPS preview in a file dialog" images/eps-imp5.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/eps-imp2.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. <img 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 开始),所以我在下面没有出现索引错误,并且实际上,在每一行的第四个字符之前不会有等号。 ***个测试是看字符串中是否出现了 =,如果没有,我们就会前进。 如果我们确实看到一个等号,那么我们会看前三个字符是否是 src。 如果全都匹配了,就调用函数 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.  
  5. import os
  6.  
  7. imagelist = []
  8. for line in open('/tmp/imagelist_parse4.txt').xreadlines():
  9. imagelist.append(line)
  10.  
  11. imagelist.sort()
  12.  
  13. outfile = open('/tmp/imagelist_parse4_sorted.txt', 'w')
  14. outfile.writelines(imagelist)
  15. 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.  
  5. import os
  6.  
  7. def imagefound(all_text, imagelist, index):
  8. end = 0
  9. index += 2
  10. newimage = ''
  11. while end == 0:
  12. if (all_text[index] != '"'):
  13. newimage = newimage + all_text[index]
  14. index += 1
  15. else:
  16. newimage = newimage + '\n'
  17. imagelist.append(newimage)
  18. end = 1
  19. return
  20.  
  21. htmlnames = []
  22. imagelist = []
  23. tempstring = ''
  24. filenames = os.listdir('/home/gregp/development/Scribus15x/doc/en/')
  25. for name in filenames:
  26. if name.endswith('.html'):
  27. htmlnames.append(name)
  28. #print htmlnames
  29. for htmlfile in htmlnames:
  30. all_text = open('/home/gregp/development/Scribus15x/doc/en/' + htmlfile).read()
  31. linelength = len(all_text)
  32. index = 3
  33. while index < linelength:
  34. if (all_text[index] == '='):
  35. if (all_text[index-3] == 's') and (all_text[index-2] == 'r') and
  36. (all_text[index-1] == 'c'):
  37. imagefound(all_text, imagelist, index)
  38. index += 1
  39. else:
  40. index += 1
  41. else:
  42. index += 1
  43.  
  44. outfile = open('/tmp/imagelist_parse4.txt', 'w')
  45. outfile.writelines(imagelist)
  46. outfile.close()
  47. imageno = len(imagelist)
  48. print str(imageno) + " images were found and saved"

脚本名称为 parseimg4.py,这并不能真实反映我陆续编写的脚本数量(包括微调的和大改的以及丢弃并重新开始写的)。 请注意,我已经对这些目录和文件名进行了硬编码,但是很容易变得通用化,让用户输入这些信息。 同样,因为它们是工作脚本,所以我将输出发送到 /tmp 目录,所以一旦重新启动系统,它们就会消失。

这不是故事的结尾,因为下一个问题是:僵尸 HTML 文件怎么办? 任何未使用的文件都可能会引用图像,不能被前面的方法所找出。 我们有一个 menu.xml 文件作为联机手册的目录,但我还需要考虑 TOC(LCTT 译注:TOC 是 table of contents 的缩写)中列出的某些文件可能引用了不在 TOC 中的文件,是的,我确实找到了一些这样的文件。

***我可以说,这是一个比图像搜索更简单的任务,而且开发的过程对我有很大的帮助。 

责任编辑:庞桂玉 来源: Linux中国
相关推荐

2020-07-10 09:49:53

数据清理数据分析查找异常

2023-02-08 07:09:40

PythonChatGPT语言模型

2023-12-30 13:33:36

Python解析器JSON

2022-12-02 08:17:27

JavaHTML标签

2019-11-28 09:23:17

Python机器学习数据库

2020-05-09 10:38:31

Python透视表数据

2018-05-17 10:05:24

运行iPadPython

2011-12-31 14:24:13

2020-12-10 10:46:23

PythonExcel图片

2021-06-02 15:10:20

PythonScrapy视频

2020-11-06 17:42:02

Python开发工具

2023-07-05 16:07:02

JavaScriptWeb 应用程序

2017-06-29 11:11:17

2019-08-01 15:08:37

PythonLine操作系统

2020-10-10 08:49:02

JS

2020-10-10 08:01:38

JS HTML 表单

2021-07-20 09:45:58

PythonEV短视频

2021-05-07 13:20:39

Python机器人编程语言

2018-05-13 21:34:00

Python妹子网页

2019-01-15 14:21:13

Python数据分析数据
点赞
收藏

51CTO技术栈公众号