【问题标题】:Difference between Scrapy selectors "a::text" and "a ::text"Scrapy 选择器“a::text”和“a ::text”之间的区别
【发布时间】:2018-07-11 10:24:55
【问题描述】:

我创建了一个抓取工具来从网页中抓取一些产品名称。它工作顺利。我已经使用 CSS 选择器来完成这项工作。但是,我唯一不明白的是选择器a::texta ::text 之间的区别(不要忽略后者中a::text 之间的空格)。当我运行我的脚本时,无论我选择哪个选择器,我都会得到完全相同的结果。

import requests
from scrapy import Selector

res = requests.get("https://www.kipling.com/uk-en/sale/type/all-sale/?limit=all#")
sel = Selector(res)
for item in sel.css(".product-list-product-wrapper"):
    title = item.css(".product-name a::text").extract_first().strip()
    title_ano = item.css(".product-name a ::text").extract_first().strip()
    print("Name: {}\nName_ano: {}\n".format(title,title_ano))

如您所见,titletitle_ano 都包含相同的选择器,后者中的空格除外。然而,结果总是一样的。

我的问题:两者之间有什么实质性区别,我应该什么时候使用前者,什么时候使用后者?

【问题讨论】:

  • 这些“用例”怎么样?你只是在问 CSS 语法吗?
  • 这是@tripleee的答案吗?
  • 不,答案是我们通过“发布您的答案”按钮在下面的大框中发布的内容。我发布的是评论。它不包含任何回答的尝试,它要求您澄清您的问题——理想情况下,edit 它有一个更好的标题、更好的问题描述和合适的标签。
  • 我的描述中哪一部分不清楚?我选择的哪些标签没有在我的刮刀中使用?不过要编辑标题。
  • @tripleee: 撇开标题不谈,问题描述在什么方面不清楚( ::text 和 a::text 在功能上是否等效,如果不是,它们有何不同以及是什么,咳咳, 用例)或标签不合适(问题是关于名为 Scrapy 的 Python Web 抓取库使用的选择器)?

标签: python python-3.x css-selectors scrapy pseudo-element


【解决方案1】:

有趣的观察!在过去的几个小时里,我对此进行了调查,结果发现,它比表面上看到的要多得多。

如果您来自 CSS,您可能希望编写 a::text 的方式与编写 a::first-linea::first-lettera::beforea::after 的方式大致相同。没有惊喜。

另一方面,标准选择器语法建议a ::text 匹配a 元素的后代::text 伪元素,使其等同于a *::text。但是,.product-list-product-wrapper .product-name a 没有任何子元素,所以按权利,a ::text 应该不匹配任何内容。它确实匹配的事实表明 Scrapy 没有遵循语法。

Scrapy 使用 Parsel(它本身基于 cssselect)将选择器转换为 XPath,这就是 ::text 的来源。考虑到这一点,让我们看看 Parsel 如何实现::text

>>> from parsel import css2xpath
>>> css2xpath('a::text')
'descendant-or-self::a/text()'
>>> css2xpath('a ::text')
'descendant-or-self::a/descendant-or-self::text()'

因此,像 cssselect 一样,任何跟随后代组合器的东西都被转换为 descendant-or-self 轴,但是因为文本节点是 DOM 中元素节点的正确子节点,所以 ::text 被视为独立节点并直接转换为text(),它与 descendant-or-self 轴匹配任何作为 a 元素后代的文本节点,就像 a/text() 匹配 a 的任何文本节点 child元素(一个孩子也是一个后代)。

令人震惊的是,即使您向选择器添加显式 * 也会发生这种情况:

>>> css2xpath('a *::text')
'descendant-or-self::a/descendant-or-self::text()'

但是,使用descendant-or-self 轴意味着a ::text 可以匹配a 元素中的所有文本节点,包括嵌套在a 中的其他元素中的文本节点。在以下示例中,a ::text 将匹配两个文本节点:'Link ' 后跟 'text'

<a href="https://example.com">Link <span>text</span></a>

因此,尽管 Scrapy 对 ::text 的实现严重违反了 Selectors 语法,但它似乎是有意这样做的。

事实上,Scrapy 的另一个伪元素::attr()1 的行为类似。以下选择器在没有任何后代元素时都匹配属于div 元素的id 属性节点:

>>> css2xpath('div::attr(id)')
'descendant-or-self::div/@id'
>>> css2xpath('div ::attr(id)')
'descendant-or-self::div/descendant-or-self::*/@id'
>>> css2xpath('div *::attr(id)')
'descendant-or-self::div/descendant-or-self::*/@id'

...但是div ::attr(id)div *::attr(id) 将匹配div 的后代中的所有id 属性节点以及它自己的id 属性,例如在以下示例中:

<div id="parent"><p id="child"></p></div>

当然,这是一个不太合理的用例,所以人们不得不怀疑这是否是 ::text 实现的无意副作用。

将伪元素选择器与用任何简单选择器替换伪元素的选择器进行比较:

>>> css2xpath('a [href]')
'descendant-or-self::a/descendant-or-self::*/*[@href]'

这会正确地将后代组合器转换为 descendant-or-self::*/*,并带有一个额外的隐式 child 轴,确保永远不会在 a 元素上测试 [@href] 谓词。

如果您是 XPath、选择器甚至 Scrapy 的新手,那么这一切似乎都非常令人困惑和不知所措。因此,这里总结了何时使用一个选择器而不是另一个选择器:

  • 如果您的 a 元素仅包含文本,或者您只对此 a 元素的顶级文本节点而不是其嵌套元素感兴趣,请使用 a::text

  • 如果您的a 元素包含嵌套元素并且您想要提取此a 元素中的所有文本节点,请使用a ::text

    虽然你可以使用a ::text,如果你的a元素只包含文本,它的语法会让人困惑,所以为了保持一致性,请改用a::text


1有趣的是,::attr() 出现在(自 2021 年起废弃)Non-element Selectors spec 中,正如您所期望的那样,它的行为与选择器语法一致,使得它在 Scrapy 中的行为与规范不一致。另一方面,::text 在规范中明显缺失;基于这个答案,我想你可以对原因做出合理的猜测。

【讨论】:

    猜你喜欢
    • 2015-03-07
    • 1970-01-01
    • 1970-01-01
    • 2016-02-17
    • 1970-01-01
    • 2016-05-31
    • 1970-01-01
    • 2021-10-22
    • 1970-01-01
    相关资源
    最近更新 更多