从HTML页面提取嵌套节时出现问题

2024-05-17 06:22:34 发布

您现在位置:Python中文网/ 问答频道 /正文

正在尝试从html页面提取嵌套节。我想最终为每个部分创建wiki页面。只提取文本不会成为问题,但提取嵌套部分将是一个问题。在

我试图从中提取部分的页面是-http://goo.gl/xb7Ydd

我计划将这些部分提取到XML(或json?)看起来像这样-

<1.1> Section 1.1
 <1.1.1> Subsection of 1.1 </1.1.1>
 <1.1.2> Subsection of 1.1 </1.1.2>
</1.1>

除了复杂的正则表达式,有人能提出其他方法吗?在


Tags: of方法文本jsonhttphtmlwikisection
2条回答

使用requests和{a2}

import requests
from bs4 import BeautifulSoup

r=requests.get("http://docs.oasis-open.org/cmis/CMIS/v1.1/os/CMIS-v1.1-os.html") # get page using requests

soup=BeautifulSoup(r.content)

s = soup.find_all(text=re.compile('\.pdf'))# find all .pdf's
print s

[u'http://docs.oasis-open.org/cmis/CMIS/v1.1/os/CMIS-v1.1-os.pdf', u'http://docs.oasis-open.org/cmis/CMIS/v1.1/csprd01/CMIS-v1.1-csprd01.pdf', u'http://docs.oasis-open.org/cmis/CMIS/v1.1/CMIS-v1.1.pdf']

编辑1:你可以跳到第3步,看看我是如何处理解析HTML的。在

我先假设您以前没有使用过HTML解析器。请原谅我,如果下面的话是傲慢的或冗长的;我只是试图彻底。在

在我们开始之前,我应该告诉您,我们在这里处理的是格式错误的HTML(例如,没有看到一个</p>),所以我们不能采取直接的方法。我们还要处理一个大文件,所以执行过程可能有点慢,而且容易出错。在

如果您选择在您的项目中使用Python,那么有一个名为BeautifulSoup的流行模块可以帮助您处理糟糕的HTML。如果我们想用它从你的页面的某个部分(比如说,简介)捕获文本,我们将遵循以下步骤:

  1. 获取HTML
  2. 查找相关的标记、id和类
  3. 编写一个脚本来提取信息并加载到XML中

第1步

首先,您需要查看页面的HTML。在浏览器中,将页面向上拉后,右键单击该页面并单击“查看页面源代码”。在弹出的窗口中,您应该看到一个文本墙(超过38000行)。将文本保存到名为“CMIS-v1.1”的文件中-操作系统.html,并将该文件放在C驱动器的根文件夹中(为方便起见;如果不使用Windows,请将其放在其他位置)。我们会仔细检查一下,找出解析器需要什么。在

第2步

让我们试着弄清楚导言的内容。当我搜索第一行文本(“内容管理互操作性服务…”)时,我被带到第129行。这是摘要,所以我转到下一个实例:第617行。这就是我要找的文本,所以现在我需要找出它在HTML中是如何标记的。在

在浏览器中回顾页面(呈现的页面,而不是墙)。本节以短语“1简介”(第615行)开头。在墙上,它被标记为“h2”(也就是说,它在<h2>和{}之间。现在,'h2'标签在墙上很常见,但是这个标签有'chapterHead'类,这不是很常见。除此之外,头和文本看起来像是在一个带有'noindent'类的'p'标记内。(注意:我边打字边写;这些信息中的一些可能对我们提出的任何代码都没有帮助,但它应该有助于你的想法)。在

我们知道如何查找章节(搜索“chapterHead”类)。我们现在找一个章节。在呈现的页面上,我看到了“1.4示例”。在墙上搜索第一行(“一组请求和响应…”)将我带到第878行。在第876行中,我在带有“sectionHead”类的“h3”标记中看到了该节的标题。此策略还将用于查找标记子节和子节的标记。在

理想情况下,每个章节节都包含在表示章节的标签中,小节将包含在表示章节的标签中,等等。这种理想的结构可以这样表示:

  • 1
    • 1.1条
      • 1.1.1条
      • 1.1.2条
    • 1.2条
    • 1.3款
      • 1.3.1条
    • 2.1条

不幸的是,本文档中不存在这种结构。我们拥有的是:

  • 1
  • 1.1条
  • 1.1.1条
  • 1.1.2条
  • 1.2条
  • 1.3款
  • 1.3.1条
  • 2.1条

我们必须按顺序检查这些标记,在遇到这些标记时对它们作出反应。在

根据我们所见,我们的战略如下:

  • 查找具有“chapterHead”类的“h2”标记
  • 浏览我们找到的<p>,留意“h3”、“h4”和“h5”标记
  • 存储文本,直到遇到另一个“h2”标记。在

第3步

如果您没有安装Python,我会去here并下载Python3.x。完成后,打开一个命令控制台并执行以下操作:

pip install beautifulsoup4

现在,运行python在控制台中。我们接下来要做的任何事情都可以在控制台中逐行键入,或者存储在.py文件中并在以后运行。在

要设置:

^{pr2}$

前两行导入我们需要的模块。最后两行只是将HTML文件的内容读入BeautifulSoup类。soup有很多方法可以使标记和类的搜索变得容易。执行这些行可能需要一段时间,因为文件太大。在

first_chapter = soup.find('h2', class_='chapterHead')

first_chapter包含第一个“h2”标记及其内容。由于此标记与其他章节、章节和小节处于同一级别,因此我们将使用它作为起点。在

^{4}$

siblings是一个生成器,它将把文档的内容逐段传递给我们。如果您在控制台中键入print(element),您应该会看到“h2”标记后面的文本(即,简介的第一段)。理想情况下,此文本将位于“p”标记中,但我们处理的不是格式良好的HTML。在

每次运行element = next(siblings),生成器都会传递文档树的同一级别上的元素(它不会传递任何嵌入的标记)。如果我们想知道标记的名称,我们可以使用element.name获得它;如果元素是文本或注释标记,则它没有名称。在

现在,为了构建XML,我们将使用xml.etree.ElementTree。在

intro = Element('1')
intro.text = element
sect1 = SubElement(intro, '1.1')
print(tostring(intro))
intro.remove(sect1)

如果在控制台中输入tostring(intro),应该会看到一些好看的XML。我们暂时删除sect1。在

最后,我们需要一种方法来跟踪遍历siblings时所处的文本级别。根据我在你的页面上看到的,小节在章节级别下上升到四个级别。让我们设置一些控制结构来检查标记的名称,以确定我们处于什么级别(这是为了激发灵感):

depth = {'h2': 1, 'h3': 2,  'h4': 3, 'h5': 4, 'h6': 5}
depth_tags = ['h3', 'h4', 'h5', 'h6']
level = [1]
sects = [intro]
xml_tag = ''
last_sect = intro

for element in siblings:
    old_depth = len(level)
    tag = element.name
    if tag == 'h2':
        break
    if tag in depth_tags:
        if old_depth < depth[tag]:
            level.append(1)
        else:
            level = level[0:depth[tag]]
            level[-1] += 1
            sects = sects[0:depth[tag]-1]
        last_sect = sects[-1]
        xml_tag = '.'.join(level)
        sects.append(SubElement(last_sect, xml_tag))
    sect = sect[-1]
    if tag == 'p':
        sect.text = element.text

我来解释一下逻辑:

  • 初始化有用的变量:
    1. depth用于将header标记转换为深度级别(假设头与子节类型完全对应;如果这个假设是错误的,您可以尝试使用element.get('class')检查元素的类)。在
    2. depth_tags用于存储表示深度变化的标记(不包括'h2',因为我们只是做介绍)。在
    3. level用于跟踪当前深度级别。在
    4. sects用于跟踪当前的XML节点,我们可以向其中添加文本。在
    5. xml_tag用于存储当前XML节点的标签。在
    6. last_sect用于存储当前XML节点的父节点。在
  • 迭代所有元素,从第一段后面的元素开始(我们在前面的代码中捕捉到)。
    1. 如果下一个元素名为'h2',我们知道我们已经到了当前章节的末尾,所以我们将它叫停。在
    2. 如果下一个元素是depth_tags中的一个,我们将适当地更改XML节点:
      • 如果元素的级别低于前一个元素的级别(我们将深入到树中),我们将在level列表中添加一个“1”(因此,如果该列表之前是[1, 1],那么现在它将是[1, 1, 1])。在
      • 如果元素的级别与前一个元素的级别相同或更高(我们将从侧面或更高的位置进入树),我们将把level列表缩短为与级别对应的长度;我们还将增加更新列表的最后一个数字。(示例:最后一个标记是“h5”,当前标记是“h3”。如果level列表以前是[1, 1, 3, 2],现在是[1, 2]),我们还将缩短sects列表,为新的XML节点腾出空间。在
      • 我们将向sects列表添加一个新的XML节点。'.'.join(level)将{}转换为“1.2.3”。在
    3. 如果下一个元素是'p'标记,我们将把它的内容写入当前的XML节点。在

我还没有测试过这个,但是即使它能完美地工作,我确信你需要小心处理'p'标记之外的文本(比如第一段我们找到)。在

结束语

我已经介绍了如何将(看起来很难看的)HTML转换为XML的基本原理。其他几点我没有提到:

  • HTML中有许多注释标记。这些元素在BeautifulSoup中表示为没有名称但有文本内容的元素。在
  • 您可能会注意到element中有很多“span”和“a”标记。使用element.text将使它们消失,这对于只获取文本非常有用。如果您想保留它们,请尝试''.join(str(t) for t in element.contents)。在
  • 正如您所知,字符“fi”(两个字符)在页面中显示为“fi”(一个字符)。只是说说而已。在

相关问题 更多 >