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?
如果 h3
和 ul
都是兄弟姐妹,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']
我正在为一个项目使用 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 theul
tags below theh3
tag?
如果 h3
和 ul
都是兄弟姐妹,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']