【问题标题】:Parsing html using lxml.html使用 lxml.html 解析 html
【发布时间】:2012-10-29 13:13:07
【问题描述】:

我正在 scraperwiki.com 上开发一个 Python 抓取工具,我需要解析一个包含以下内容的 html 页面:

<div class="items">
  <div class="item">
       ItemLine1 ItemLine1 ItemLine1
       <br> 
       ItemLine2 ItemLine2 ItemLine2
 </div>
 <br>
</div>

我现在做的是:

import scraperwiki
import lxml.html

#.......................
raw_string = lxml.html.fromstring(scraperwiki.scrape(url_to_scrape))
my_line = ((raw_string.cssselect("div.items div.item")[0]).text)
print (my_line)

它只打印ItemLine1 ItemLine1 ItemLine1。当我将 [0] 更改为 [1] 时,它会引发异常。

我该如何抓取它?我应该使用 xpath 吗?

【问题讨论】:

  • 我的猜测是这里的问题是br打开但没有关闭。
  • 美容汤是不可接受的。 > 也许,那又如何?无论如何我必须解析它。

标签: python parsing screen-scraping lxml


【解决方案1】:

XPath 是最直接的解决方案:

items = raw_string.cssselect('div.items div.item')

texts = [item.xpath('br[1]/preceding-sibling::node()') for item in items]

XPath br[1] 选择div.item 的第一个br 孩子; preceding-sibling:: 轴包含出现在第一个 br 之前的所有节点; node() 选择该轴上的每种节点(文本或元素)。

如果您的更大目标是通过br 元素拆分节点的子节点,您可以采用几种不同的方法。之所以如此棘手,是因为 brhr 这样的元素是设计不当的标记。使用 sgml、html 或 xml 之类的树状标记语言,应该组合在一起的事物应该由一个共同的父元素分组,而不是由一个没有子元素的分隔符元素分割。

我将扩展你的测试用例来演示一些更复杂的情况:

html = """<div class="items">
  <div class="item">
   <br>
   ItemLine1 ItemLine1 ItemLine1
   <a href="">item</a>
   Itemline1-b
   <br> 
   <a class="z">item2</a>
   ItemLine2 ItemLine2 ItemLine2
   <br><br>
   Itemline3
 </div>
 <br>
</div>"""

doc = lxml.html.fromstring(html)
itemlist = doc.cssselect('div.items div.item')

第一种方法是简单地获取段落中的所有节点,并通过br将它们分成不同的列表。如果您使用这种方法,请不要使用 ElementTree API 的 texttail 属性,因为您最终可能会复制文本。

def paras_by_br_nodes(parent):
    """Return a list of node children of parent (including text nodes) grouped by "paragraphs" which are delimited by <br/> elements."""
    paralist = []
    paras = []
    for node in parent.xpath('node()'):
        if getattr(node, 'tag', None) == 'br':
            paralist.append(paras)
            paras = []
        else:
            paras.append(node)
        paralist.append(paras)
        return paralist


print paras_by_br_nodes(itemlist[0])

这会产生如下列表:

[['\n       '],
 ['\n       ItemLine1 ItemLine1 ItemLine1\n\t\t', <Element a at 0x10498a350>, '\n\t\tItemline1-b\n       '],
 [<Element a at 0x10498a230>, '\n       ItemLine2 ItemLine2 ItemLine2\n       '],
 [], 
 ['\n       Itemline3\n ']]

第二种方法是利用 ElementTree API 并将文本节点保留在 texttail 属性中。这种方法的缺点是,如果没有附加文本的元素,我们只需要包含文本节点。这个非同质类型列表使用起来有点麻烦。

def paras_by_br_text(parent):
    paralist=[]
    para=[parent.text]
    for item in parent:
        if item.tag=='br':
            paralist.append(para)
            para = [item.tail]
        else:
            para.append(item)
    paralist.append(para)
    return paralist

print paras_by_br_text(itemlist[0])

这会产生一个像这样的列表。请注意,与前一个列表相比,它仅在列表的第一个位置具有文本节点节点。这对应于br.tail 文本或parent.text(即第一个元素之前的文本)。

[['\n       '],
 ['\n       ItemLine1 ItemLine1 ItemLine1\n\t\t', <Element a at 0x1042f5170>],
 [<Element a at 0x1042f5290>],
 [],
 ['\n       Itemline3\n ']]

我认为最好的方法是引入新元素。这个 html 在它应该使用 p 或其他一些容器元素时使用 br。因此,让我们修复 html 并返回元素列表而不是节点列表:

def paras_by_br(parent):
    paralist = []
    para = lxml.html.etree.Element('para')
    if parent.text:
        para.text = parent.text
    for item in parent:
        if item.tag=='br':
            paralist.append(para)
            para = lxml.html.etree.Element('para')
            if item.tail:
                para.text = item.tail
        else:
            para.append(item)
    return paralist

paralist = paras_by_br(itemlist[0])

print "\n--------\n".join(lxml.html.etree.tostring(para) for para in paralist)

这将打印以下内容:

<para>
       </para>
--------
<para>
       ItemLine1 ItemLine1 ItemLine1
        <a href="">item</a>
        Itemline1-b
       </para>
--------
<para><a class="z">item2</a>
       ItemLine2 ItemLine2 ItemLine2
       </para>
--------
<para/>

查看项目如何按新的para 元素进行分组,该元素在原始文档中不存在。

【讨论】:

  • 谢谢,但它也只返回ItemLine1 ItemLine1 ItemLine1item2item2... 呢?
  • 我以为这就是你想要的?我将通过将它们分组的解决方案来扩展答案。
猜你喜欢
  • 2012-10-15
  • 1970-01-01
  • 2019-01-09
  • 2011-09-01
  • 2014-03-28
  • 2013-04-23
  • 2011-06-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多