ความแตกต่างระหว่างตัวเลือก 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 pseudo-element ได้หรือไม่? ฉันไม่พบสิ่งใดเกี่ยวกับการมีอยู่ของมันเลย...   -  person Andersson    schedule 01.02.2018
comment
ขอบคุณเซอร์ Andersson สำหรับความคิดเห็นของคุณ โดยพื้นฐานแล้ว เมื่อฉันแยกวิเคราะห์ข้อความจากองค์ประกอบบางอย่างโดยใช้ตัวเลือก css ฉันไม่จำเป็นต้องใช้ ::text ไม่ว่าจะเป็นไลบรารี BeautifulSoup หรือ lxml อย่างไรก็ตาม เมื่อพูดถึงการแยกวิเคราะห์แบบเดียวกันโดยใช้ scrapy นี่ถือเป็นข้อบังคับ ::text เพื่อรับข้อความ คุณรู้เรื่องนี้เป็นอย่างดี ประเด็นคือ: ฉันไม่พบความแตกต่างมากนักในการใช้ช่องว่างระหว่างนั้นเพื่อแยกวิเคราะห์ข้อความจากองค์ประกอบบางอย่าง แต่ควรมี do's and don'ts เกี่ยวกับการใช้งาน นั่นคือสิ่งที่ผมอยากจะรู้   -  person SIM    schedule 01.02.2018
comment
@ novice-coder ฉันไม่ได้ใช้ Scrapy ดังนั้นจริงๆ แล้วฉันไม่รู้วิธีรับข้อความจากโหนดด้วย ::text :) ฉันไม่เคยได้ยินเกี่ยวกับองค์ประกอบหลอกนี้เลย IMHO ฉันไม่คิดว่าพื้นที่จะสร้างความแตกต่างในกรณีของคุณได้เลย ... นอกจากนี้ฉันไม่คิดว่าคำถามนี้สมควรได้รับการลงคะแนน :)   -  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 axis จับคู่โหนดข้อความใดๆ ที่สืบทอดมาจากองค์ประกอบ 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 จะเป็นการละเมิดไวยากรณ์ 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() ปรากฏใน (ละทิ้ง ณ ปี 2021) ข้อมูลจำเพาะของตัวเลือกที่ไม่ใช่องค์ประกอบ โดยที่คุณคาดว่าตัวเลือกจะทำงานสอดคล้องกับไวยากรณ์ของตัวเลือก ซึ่งทำให้พฤติกรรมของตัวเลือกใน Scrapy ไม่สอดคล้องกับข้อมูลจำเพาะ ::text ในทางกลับกันหายไปจากข้อมูลจำเพาะอย่างเห็นได้ชัด จากคำตอบนี้ ฉันคิดว่าคุณสามารถเดาได้อย่างสมเหตุสมผลว่าทำไม

person BoltClock    schedule 01.02.2018