如何选择和提取两个元素之间的文本?

Posted

技术标签:

【中文标题】如何选择和提取两个元素之间的文本?【英文标题】:how to select and extract texts between two elements? 【发布时间】:2018-02-07 23:17:04 【问题描述】:

我正在尝试使用 scrapy 抓取 this 网站。页面结构如下所示:

<div class="list">
  <a id="follows" name="follows"></a>
 <h4 class="li_group">Follows</h4>
 <div class="soda odd"><a href="...">Star Trek</a></div> 
 <div class="soda even"><a href="...</a></div>
 <div class="soda odd"><a href="..">Star Trek: The Motion Picture</a></div>
 <div class="soda even"><a href="..">Star Trek II: The Wrath of Khan</a></div>
 <div class="soda odd"><a href="..">Star Trek III: The Search for Spock</a></div>
 <div class="soda even"><a href="..">Star Trek IV: The Voyage Home</a></div>
  <a id="followed_by" name="followed_by"></a>
 <h4 class="li_group">Followed by</h4>
 <div class="soda odd"><a href="..">Star Trek V: The Final Frontier</a></div>
 <div class="soda even"><a href="..">Star Trek VI: The Undiscovered Country</a></div>
 <div class="soda odd"><a href="..">Star Trek: Deep Space Nine</a></div>
 <div class="soda even"><a href="..">Star Trek: Generations</a></div>
 <div class="soda odd"><a href="..">Star Trek: Voyager</a></div>
 <div class="soda even"><a href="..">First Contact</a></div>
   <a id="spin_off" name="spin_off"></a>
 <h4 class="li_group">Spin-off</h4>
 <div class="soda odd"><a href="..">Star Trek: The Next Generation - The Transinium Challenge</a></div>
 <div class="soda even"><a href="..">A Night with Troi</a></div>
 <div class="soda odd"><a href="..">Star Trek: Deep Space Nine</a></div
</div>

我想选择并提取&lt;h4 class="li_group"&gt;Follows&lt;/h4&gt;&lt;h4 class="li_group"&gt;Followed by&lt;/h4&gt;之间的文本然后&lt;h4 class="li_group"&gt;Followed by&lt;/h4&gt;&lt;h4 class="li_group"&gt;Spin-off&lt;/h4&gt;之间的文本 我试过这段代码:

def parse(self, response):
    for sel in response.css("div.list"):
        item = ImdbcoItem()
        item['Follows'] = sel.css("a#follows+h4.li_group ~ div a::text").extract(),
        item['Followed_by'] = sel.css("a#vfollowed_by+h4.li_group ~ div a::text").extract(),
        item['Spin_off'] = sel.css("a#spin_off+h4.li_group ~ div a::text").extract(),
    return item

但这第一项提取所有 div,而不仅仅是 &lt;h4 class="li_group"&gt;Follows&lt;/h4&gt;&lt;h4 class="li_group"&gt;Followed by&lt;/h4&gt; 之间的 div任何帮助都会非常有帮助!

【问题讨论】:

以防万一,imdb.com 有一个 (un)?official API,如果我没记错的话,您可以在其中清理所有这些数据。 【参考方案1】:

我喜欢在这些情况下使用的提取模式是:

遍历“边界”(此处为h4 元素) 从 1 开始枚举它们时 使用 XPath 的 following-sibling 轴,就像在 @Andersson 的回答中一样,获取下一个边界之前的元素, 并通过计算前面的“边界”元素的数量来过滤它们,因为我们从枚举中知道我们在哪里

这将是循环:

$ scrapy shell 'http://www.imdb.com/title/tt0092455/trivia?tab=mc&ref_=tt_trv_cnn'
(...)
>>> for cnt, h4 in enumerate(response.css('div.list > h4.li_group'), start=1):
...     print(cnt, h4.xpath('normalize-space()').get())
... 
1 Follows 
2 Followed by 
3 Edited into 
4 Spun-off from 
5 Spin-off 
6 Referenced in 
7 Featured in 
8 Spoofed in 

这是使用枚举获取边界之间元素的一个示例(请注意,这使用 XPath 变量,表达式中带有 $cnt 并在 .xpath() 中传递 cnt=cnt):

>>> for cnt, h4 in enumerate(response.css('div.list > h4.li_group'), start=1):
...     print(cnt, h4.xpath('normalize-space()').get())
...     print(h4.xpath('following-sibling::div[count(preceding-sibling::h4)=$cnt]',
                       cnt=cnt).xpath(
                          'string(.//a)').getall())
... 
1 Follows 
['Star Trek', 'Star Trek: The Animated Series', 'Star Trek: The Motion Picture', 'Star Trek II: The Wrath of Khan', 'Star Trek III: The Search for Spock', 'Star Trek IV: The Voyage Home']
2 Followed by 
['Star Trek V: The Final Frontier', 'Star Trek VI: The Undiscovered Country', 'Star Trek: Deep Space Nine', 'Star Trek: Generations', 'Star Trek: Voyager', 'First Contact', 'Star Trek: Insurrection', 'Star Trek: Enterprise', 'Star Trek: Nemesis', 'Star Trek', 'Star Trek Into Darkness', 'Star Trek Beyond', 'Star Trek: Discovery', 'Untitled Star Trek Sequel']
3 Edited into 
['Reading Rainbow: The Bionic Bunny Show', 'The Unauthorized Hagiography of Vincent Price']
4 Spun-off from 
['Star Trek']
5 Spin-off 
['Star Trek: The Next Generation - The Transinium Challenge', 'A Night with Troi', 'Star Trek: Deep Space Nine', "Star Trek: The Next Generation - Future's Past", 'Star Trek: The Next Generation - A Final Unity', 'Star Trek: The Next Generation: Interactive VCR Board Game - A Klingon Challenge', 'Star Trek: Borg', 'Star Trek: Klingon', 'Star Trek: The Experience - The Klingon Encounter']
6 Referenced in 
(...)

这是你如何使用它来填充和项目(这里,我使用一个简单的 dict 只是为了说明):

>>> item = 
>>> for cnt, h4 in enumerate(response.css('div.list > h4.li_group'), start=1):
...     key = h4.xpath('normalize-space()').get().strip() # there are some non-breaking spaces
...     if key in ['Follows', 'Followed by', 'Spin-off']:
...         values = h4.xpath('following-sibling::div[count(preceding-sibling::h4)=$cnt]',
...                        cnt=cnt).xpath(
...                           'string(.//a)').getall()
...         item[key] = values
... 

>>> from pprint import pprint
>>> pprint(item)
'Followed by': ['Star Trek V: The Final Frontier',
                 'Star Trek VI: The Undiscovered Country',
                 'Star Trek: Deep Space Nine',
                 'Star Trek: Generations',
                 'Star Trek: Voyager',
                 'First Contact',
                 'Star Trek: Insurrection',
                 'Star Trek: Enterprise',
                 'Star Trek: Nemesis',
                 'Star Trek',
                 'Star Trek Into Darkness',
                 'Star Trek Beyond',
                 'Star Trek: Discovery',
                 'Untitled Star Trek Sequel'],
 'Follows': ['Star Trek',
             'Star Trek: The Animated Series',
             'Star Trek: The Motion Picture',
             'Star Trek II: The Wrath of Khan',
             'Star Trek III: The Search for Spock',
             'Star Trek IV: The Voyage Home'],
 'Spin-off': ['Star Trek: The Next Generation - The Transinium Challenge',
              'A Night with Troi',
              'Star Trek: Deep Space Nine',
              "Star Trek: The Next Generation - Future's Past",
              'Star Trek: The Next Generation - A Final Unity',
              'Star Trek: The Next Generation: Interactive VCR Board Game - A '
              'Klingon Challenge',
              'Star Trek: Borg',
              'Star Trek: Klingon',
              'Star Trek: The Experience - The Klingon Encounter']
>>> 

【讨论】:

谢谢,就像一个魅力。但我不知道如何在我的代码中使用它。你能给我一个提示或提供一个代码来使用吗?【参考方案2】:

你可以尝试使用下面的 XPath 表达式来获取

“关注”块的所有文本节点:

//div[./preceding-sibling::h4[1]="Follows"]//text()

“Followed by”块的所有文本节点:

//div[./preceding-sibling::h4[1]="Followed by"]//text()

“分拆”块的所有文本节点:

//div[./preceding-sibling::h4[1]="Spin-off"]//text()

【讨论】:

您甚至可以将[./preceding-sibling::h4[1][.="Follows"]] 简化为[./preceding-sibling::h4[1]="Follows"] 安德森先生,您真是太棒了。什么表情!!!是否可以创建具有相同元素的 css 选择器来定位相同的东西?一个例子就足够了。谢谢。 我不确定是否有办法将提供的 XPath 表达式转换为相同的 CSS 选择器。我知道前面和前面的元素有 !? 符号,但似乎它们还不支持...... 为了学习,最后一件事要了解安德森爵士关于这个主题的知识。为什么是“h4[1]”,我的意思是为什么不在上面创建的 xpath 中索引 [0] 或 [2]?对不起我的无知。 h[1] 表示前面的第一个标头。请注意,与索引以0 开头的编程语言不同,XPath 索引以1 开头

以上是关于如何选择和提取两个元素之间的文本?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 sed/grep 提取两个单词之间的文本?

文本摘要:如何选择合适的 n-gram 大小

如何从 MS Word 文档中选择性地提取以黄色突出显示的文本?

如何在 innerText 或 nodeValue 之间进行选择?

如何使用 css 选择器提取属性值?

如何防止在Javascript中双击选择文本