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

HTML in between h3/h2 tags with Xpath/BeautifulSoup

我正在为一个项目使用 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 中执行此操作吗?

使用 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')]

如果你想使用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 是正确的工具,因为有可用的方法。

您可以在 Scrapy 中使用 XPath 和 CSS selector。

这是一个示例解决方案(在 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 在评论中提出问题后:

Every <li> tag is enclosed in its own <ul> (...) Is there any way to extend that to make it look for all the ul tags below the h3 tag?

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

考虑这个输入 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 #3</li></ul>
<ul><li>Text for the second title... li #4</li></ul>

第一行 <ul><li> 有 1 个前面的 h3 兄弟,第三行 <ul><li> 有 2 个前面的 h3 兄弟。

因此,对于每个 h3,您需要跟随 ul 个兄弟姐妹,这些兄弟姐妹的数量恰好是您目前所见的 h3 个。

第一个:

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

那么,

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

等等。

这是在 enumerate()h3 selection 的帮助下实现的想法(记住 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']