Интересное наблюдение! Я провел последние пару часов, расследуя это, и оказалось, что это гораздо больше, чем кажется на первый взгляд.
Если вы пришли из 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
(дочерний элемент также является потомком).
Вопиющим образом это происходит даже тогда, когда вы явно добавляете *
к селектору:
>>> 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>
Таким образом, хотя реализация ::text
в Scrapy является вопиющим нарушением грамматики селекторов, кажется, что это было сделано намеренно.
На самом деле другой псевдоэлемент 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()
появляется в (заброшенном в 2021 г.) Спецификация селекторов, не относящихся к элементам, где, как и следовало ожидать, она ведет себя последовательно с грамматикой селекторов, что делает ее поведение в Scrapy несовместимым со спецификацией. ::text
, с другой стороны, явно отсутствует в спецификации; основываясь на этом ответе, я думаю, вы можете сделать разумное предположение о том, почему.
person
BoltClock
schedule
01.02.2018
::text
? ничего не могу найти о его существовании... - person Andersson   schedule 01.02.2018::text
, будь то библиотекаBeautifulSoup
илиlxml
. Однако, когда дело доходит до парсинга с помощью scrapy, это обязательно::text
для получения текста. Вы прекрасно это знаете. Дело в том, что я не нашел большой разницы в использовании пробела между ними для анализа любого текста из некоторых элементов, но должно быть какое-тоdo's and don'ts
использование. Вот что я хочу знать. - person SIM   schedule 01.02.2018::text
:) Я просто никогда не слышал об этом псевдоэлементе. ИМХО, я действительно не думаю, что пространство может когда-либо иметь какое-либо значение в вашем случае ... Также я не думаю, что этот вопрос заслуживает отрицательных голосов :) - person Andersson   schedule 01.02.2018