Разница между селекторами Scrapy a::text и ::text

Я создал парсер, чтобы получить некоторые названия продуктов с веб-страницы. Он работает гладко. Я использовал селекторы 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 содержат один и тот же селектор, за исключением пробела в последнем. Тем не менее, результаты всегда одинаковы.

Мой вопрос: есть ли существенная разница между ними, и когда я должен использовать первое и когда последнее?


person SIM    schedule 01.02.2018    source источник
comment
Каковы эти варианты использования? Вы просто спрашиваете о синтаксисе CSS?   -  person tripleee    schedule 01.02.2018
comment
Это ответ @tripleee?   -  person SIM    schedule 01.02.2018
comment
Нет, ответы — это то, что мы публикуем в большом поле внизу с помощью кнопки «Отправить ответ». То, что я написал, является комментарием. Он не содержит никаких попыток ответить, он просит вас уточнить свой вопрос - в идеале, редактировать его, чтобы лучший заголовок, лучшее описание проблемы и подходящие теги.   -  person tripleee    schedule 01.02.2018
comment
Какая часть моего описания неясна? Какие теги я выбрал, но не использовал в парсере? Хотя заголовок отредактирую.   -  person SIM    schedule 01.02.2018
comment
@novice-coder, можете ли вы поделиться ссылкой на псевдоэлемент ::text? ничего не могу найти о его существовании...   -  person Andersson    schedule 01.02.2018
comment
Спасибо, сэр Андерссон, за ваш комментарий. По сути, когда я анализирую любой текст из некоторых элементов с помощью селекторов CSS, мне не нужно это использование ::text, будь то библиотека BeautifulSoup или lxml. Однако, когда дело доходит до парсинга с помощью scrapy, это обязательно ::text для получения текста. Вы прекрасно это знаете. Дело в том, что я не нашел большой разницы в использовании пробела между ними для анализа любого текста из некоторых элементов, но должно быть какое-то do's and don'ts использование. Вот что я хочу знать.   -  person SIM    schedule 01.02.2018
comment
@novice-coder, я не использую Scrapy, поэтому на самом деле я не знаю, как получить текст из узла с помощью ::text :) Я просто никогда не слышал об этом псевдоэлементе. ИМХО, я действительно не думаю, что пространство может когда-либо иметь какое-либо значение в вашем случае ... Также я не думаю, что этот вопрос заслуживает отрицательных голосов :)   -  person Andersson    schedule 01.02.2018
comment
@tripleee: Заголовок в сторону, каким образом описание проблемы неясно (являются ли ::text и a::text функционально эквивалентными, если нет, то чем они отличаются и каковы, кхм, варианты использования для каждого) или теги не подходят (вопрос о селекторах, используемых библиотекой веб-скрейпинга Python под названием Scrapy)?   -  person BoltClock    schedule 01.02.2018
comment
С исправленным заголовком и разъяснениями здесь, в комментариях, я не думаю, что это больше неясно, и я отозвал свой голос, чтобы закрыть как неясный. Спасибо за пинг.   -  person tripleee    schedule 01.02.2018
comment
@tripleee: Не беспокойтесь. В итоге я потратил неожиданное количество времени и исследований, чтобы ответить на этот вопрос, поэтому я решил уточнить вопрос еще немного.   -  person BoltClock    schedule 01.02.2018


Ответы (1)


Интересное наблюдение! Я провел последние пару часов, расследуя это, и оказалось, что это гораздо больше, чем кажется на первый взгляд.

Если вы пришли из 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