Scrapy选择器"a :: text"和"a :: text"之间的区别

SIM*_*SIM 5 python css-selectors pseudo-element scrapy python-3.x

我创建了一个刮刀来从网页上获取一些产品名称.它运作顺利.我已经使用CSS选择器来完成这项工作.然而,我唯一无法理解的是选择器之间的区别(a::text并且a ::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))
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,title并且title_ano包含相同的选择,扎在后者的空间.然而,结果总是一样的.

我的问题:两者之间是否存在实质性差异,何时使用前者和后者?

Bol*_*ock 9

有趣的观察!过去几个小时我一直在研究这个问题,事实证明,除此之外还有更多的东西.

如果你从CSS来了,你可能会想到写a::text的多,你会写同样的方式a::first-line,a::first-letter,a::beforea::after.没有惊喜.

在另一方面,标准的选择语法会建议a ::text的匹配::text一个伪元素后裔的的a元素,使之等同于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()'
Run Code Online (Sandbox Code Playgroud)

因此,像cssselect,任何如下后代组合子被翻译成descendant-or-self轴,但由于文本节点是在DOM元素节点的适当的儿童,::text被视为一个独立的节点,并直接转换为text(),其与descendant-or-self轴线,匹配任何这是一种的后代文本节点a元素,就像a/text()任何文本节点匹配的的a元素(一个孩子也是一个后代).

很明显,即使您*向选择器添加显式,也会发生这种情况:

>>> css2xpath('a *::text')
'descendant-or-self::a/descendant-or-self::text()'
Run Code Online (Sandbox Code Playgroud)

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

<a href="https://example.com">Link <span>text</span></a>
Run Code Online (Sandbox Code Playgroud)

因此,虽然Scrapy的实现::text是对Selectors语法的严重违反,但它似乎是故意这样做的.

实际上,Scrapy的其他伪元素::attr()1表现相似.当以下选择器没有任何后代元素时,它们都匹配id属于该div元素的属性节点:

>>> 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'
Run Code Online (Sandbox Code Playgroud)

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

<div id="parent"><p id="child"></p></div>
Run Code Online (Sandbox Code Playgroud)

当然,这是一个不那么合理的用例,因此我们不得不怀疑这是否是实施的无意识的副作用::text.

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

>>> css2xpath('a [href]')
'descendant-or-self::a/descendant-or-self::*/*[@href]'
Run Code Online (Sandbox Code Playgroud)

这正确地将后代组合子转换descendant-or-self::*/*为另外的隐含child轴,确保[@href]从不在a元素上测试谓词.

如果您是XPath,Selectors甚至是Scrapy的新手,这可能看起来非常令人困惑和压倒一切.所以这里总结了何时使用一个选择器而不是另一个:

  • 使用a::text,如果你的a元素只包含文本,或者如果你只在此的顶级文本节点感兴趣的a元素,而不是它的嵌套元素.

  • 使用a ::text,如果你的a元素包含嵌套的元素,并要提取此范围内的所有文本节点a元素.

    虽然您可以使用a ::text如果您的a元素仅包含文本,但其语法令人困惑,因此为了保持一致性,请使用a::text.


1 有趣的是,::attr()出现在非元素选择器规范中,正如您所期望的那样,它与Selectors语法的行为一致,使其在Scrapy中的行为与规范不一致.::text另一方面,规格明显缺失; 基于这个答案,我认为你可以对其进行合理的猜测.