我已经创建了一个刮刀来从网页中获取一些产品名称。它运作顺利。我已经使用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))
如您所见,title
和title_ano
都包含相同的选择器,禁止后者的空格。然而,结果总是一样的。
我的问题:两者之间是否存在实质性差异,何时使用前者和后者?
答案 0 :(得分:8)
如果你来自CSS,你可能希望写a::text
的方式与编写a::first-line
,a::first-letter
,a::before
或a::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()'
因此,与cssselect一样,后代组合子后面的任何内容都会转换为descendant-or-self
轴,但由于文本节点是DOM中元素节点的正确子节点,因此::text
被视为独立节点并直接转换为text()
,descendant-or-self
轴与a
元素的后代匹配任何文本节点,就像a/text()
匹配任何文本节点< a
元素的em> 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 表现相似。当以下选择器没有任何后代元素时,它们都匹配属于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'
...但div ::attr(id)
和div *::attr(id)
会匹配id
个后代中的所有div
个属性节点及其自己的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
轴,确保[@href]
谓词永远不会在a
元素上进行测试。
如果您是XPath,Selectors甚至是Scrapy的新手,这可能看起来非常令人困惑和压倒一切。所以这里总结了何时使用一个选择器而不是另一个:
如果您的a::text
元素仅包含文字,或者您只对此a
元素的顶级文本节点感兴趣而不是其嵌套,请使用a
元件。
如果您的a ::text
元素包含嵌套元素,并且您想要提取此a
元素中的所有文本节点,请使用a
。
如果可以使用a ::text
,如果您的a
元素仅包含文字,则其语法会令人困惑,因此为了保持一致性,请使用a::text
代替
1 有趣的是,::attr()
出现在Non-element Selectors spec中,正如您所期望的那样,它与Selectors语法的行为一致,使其行为在Scrapy中与规范不一致。另一方面,::text
明显缺少规范;根据这个答案,我认为你可以合理地猜出原因。