带有 Xpath/BeautifulSoup 的 h3/h2 标签之间的 HTML

Posted

技术标签:

【中文标题】带有 Xpath/BeautifulSoup 的 h3/h2 标签之间的 HTML【英文标题】:HTML in between h3/h2 tags with Xpath/BeautifulSoup 【发布时间】:2015-10-25 02:01:00 【问题描述】:

我在一个项目中使用Scrapy,我得到以下html

<h3><span class="my_class">First title</span></h3>
<ul>
    <li>Text for the first title... li #1</li>
</ul>
<ul>
    <li>Text for the first title... li #2</li>
</ul>
<h3><span class="my_class">Second title</span></h3>
<ul>
    <li>Text for the second title... li #1</li>
</ul>
<ul>
    <li>Text for the second title... li #2</li>
</ul>

现在,当我使用 response.xpath(".//ul/li/text()").extract() 时,它确实有效,它给了我 ["Text for the first title... li #1", "Text for the first title... li #2", "Text for the second title... li #1", "Text for the second title... li #2"] 但这部分是我想要的。

我想要两个列表,一个用于First title,另一个用于Second title。 这样的结果将是:

first_title = ["Text for the first title... li #1", "Text for the first title... li #2"]
second_title = ["Text for the second title... li #1", "Text for the second title... li #2"]

我仍然不知道如何实现这一点。我目前正在使用Scrapy 来获取 HTML;使用xpath 和纯Python 的解决方案对我来说是理想的。但不知何故,我相信BeautifulSoup 将对这类任务有用。

您对如何在 Python 中执行此操作有任何想法吗?

【问题讨论】:

【参考方案1】:

使用 Beautiful Soup 的方法如下。 (我将结果存储在字典中,而不是单独命名的列表中,以防您事先不知道您将拥有多少。)

from bs4 import BeautifulSoup

soup = BeautifulSoup(url)
groups = soup.find_all('ul')
results = 
for group in groups:
   results[group.find_previous_sibling().text] = [e.text for e in a.find_all('li')]

【讨论】:

【参考方案2】:

如果你想使用 BeautifulSoup,你可以使用 findNext 方法:

h3s = soup.find_all("h3")
for h3 in h3s:
    print h3.text
    print h3.findNext("ul").text

在这种情况下,BS 更容易使用,因为它可以更轻松地找到元素的兄弟。

使用简单的 XPath,您可以执行以下操作:

h3s = data.xpath('//h3')
for h3 in h3s:
    print h3.xpath('.//text()')
    h3.xpath('./following-sibling::ul')[0].xpath('.//text()')

这是为您上面的示例修复的。如果您需要一些通用方法,我会说 BS 是正确的工具,因为有可用的方法。

【讨论】:

【参考方案3】:

您可以在 Scrapy 中使用 XPath 和 CSS 选择器。

这是一个示例解决方案(在 ipython 会话中;我只将第二块中的 #1 和 #2 更改为 #3 和 #4 以使更明显):

In [1]: import scrapy

In [2]: selector = scrapy.Selector(text="""<h3><span class="my_class">First title</span></h3>
   ...: <ul>
   ...:     <li>Text for the first title... li #1</li>
   ...:     <li>Text for the first title... li #2</li>
   ...: </ul>
   ...: <h3><span class="my_class">Second title</span></h3>
   ...: <ul>
   ...:     <li>Text for the second title... li #3</li>
   ...:     <li>Text for the second title... li #4</li>
   ...: </ul>""")

In [3]: for title_list in selector.css('h3 + ul'):
   ...:         print title_list.xpath('./li/text()').extract()
   ...:     
[u'Text for the first title... li #1', u'Text for the first title... li #2']
[u'Text for the second title... li #3', u'Text for the second title... li #4']

In [4]: for title_list in selector.css('h3 + ul'):
        print title_list.css('li::text').extract()
   ...:     
[u'Text for the first title... li #1', u'Text for the first title... li #2']
[u'Text for the second title... li #3', u'Text for the second title... li #4']

In [5]: 

编辑,在评论中OP的问题之后:

每个&lt;li&gt; 标签都包含在自己的&lt;ul&gt; 中(...)有没有办法扩展它以使其查找h3 标签下方的所有ul 标签?

如果h3ul 都是兄弟姐妹,选择在下一个h3 之前的uls 的一种方法是计算preceding h3 siblings

考虑这个输入 HTML sn-p:

<h3><span class="my_class">First title</span></h3>
<ul><li>Text for the first title... li #1</li></ul>
<ul><li>Text for the first title... li #2</li></ul>

<h3><span class="my_class">Second title</span></h3>
<ul><li>Text for the second title... li #3</li></ul>
<ul><li>Text for the second title... li #4</li></ul>

第一行 &lt;ul&gt;&lt;li&gt; 前面有 1 个 h3 兄弟,第三行 &lt;ul&gt;&lt;li&gt; 前面有 2 个 h3 兄弟。

因此,对于每个 h3,您需要关注 ul 兄弟姐妹,它们的数量与您目前看到的 h3 的数量完全相同。

第一:

following-sibling::ul[count(preceding-sibling::h3)=1]

那么,

following-sibling::ul[count(preceding-sibling::h3)=2]

等等。

enumerate()h3 选择(记住XPath positions start at 1,而不是0)的帮助下,这是这个想法:

In [1]: import scrapy

In [2]: selector = scrapy.Selector(text="""
<h3><span class="my_class">First title</span></h3>
<ul><li>Text for the first title... li #1</li></ul>
<ul><li>Text for the first title... li #2</li></ul>

<h3><span class="my_class">Second title</span></h3>
<ul><li>Text for the second title... li #3</li></ul>
<ul><li>Text for the second title... li #4</li></ul>
""")

In [3]: for cnt, title in enumerate(selector.css('h3'), start=1):
   ...:     print title.xpath('following-sibling::ul[count(preceding-sibling::h3)=%d]/li/text()' % cnt).extract()
   ...: 
[u'Text for the first title... li #1', u'Text for the first title... li #2']
[u'Text for the second title... li #3', u'Text for the second title... li #4']

【讨论】:

我真的很欣赏这个优雅的解决方案,我从这段代码中学到了很多东西。问题是我犯了一个错误。每个&lt;li&gt; 标签都包含在自己的&lt;ul&gt; 中。例如:&lt;ul&gt;&lt;li&gt;Text for the first title... li #1&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;Text for the second title... li #3&lt;/li&gt;&lt;/ul&gt;。我编辑了这个问题。你的代码有什么办法可以与html一起工作吗? 现在我得到了[u"Text for the first title... li #1"][u"Text for the second title... li #3"]。我知道这是因为选择器'h3 + ul' 查找标签h3,然后查找它的第一个ul 标签。有什么方法可以扩展它以使其查找h3 标签下方的所有ul 标签? @gglasses,我已经用你的第二个用例的解决方案编辑了我的答案

以上是关于带有 Xpath/BeautifulSoup 的 h3/h2 标签之间的 HTML的主要内容,如果未能解决你的问题,请参考以下文章

数据的查找和提取[2]——xpath解析库的使用

Xpathbs4和jsonpath

爬取豆瓣TOP250

一起学爬虫——PyQuery常用用法总结

python爬虫学习过程:

什么是网络爬虫技术?