แนวปฏิบัติที่ดีที่สุดในการเขียนเว็บแครปเปอร์ที่สามารถบำรุงรักษาได้คืออะไร?

ฉันจำเป็นต้องใช้เครื่องขูดสองสามตัวเพื่อรวบรวมข้อมูลหน้าเว็บบางหน้า (เนื่องจากไซต์ไม่มี API แบบเปิด) แยกข้อมูลและบันทึกลงในฐานข้อมูล ขณะนี้ฉันกำลังใช้ซุปที่สวยงามเพื่อเขียนโค้ดดังนี้:

discount_price_text = soup.select("#detail-main del.originPrice")[0].string;
discount_price = float(re.findall('[\d\.]+', discount_price_text)[0]);

ฉันเดาว่าโค้ดลักษณะนี้อาจใช้ไม่ได้ได้ง่ายมากเมื่อมีการเปลี่ยนแปลงหน้าเว็บ แม้จะเล็กน้อยก็ตาม ฉันจะเขียนสแครปเปอร์ที่ไม่ค่อยไวต่อการเปลี่ยนแปลงเหล่านี้ได้อย่างไร นอกเหนือจากการเขียนการทดสอบการถดถอยเพื่อให้ทำงานเป็นประจำเพื่อตรวจจับความล้มเหลว

โดยเฉพาะอย่างยิ่ง มี 'เครื่องขูดอัจฉริยะ' ที่มีอยู่ซึ่งสามารถ 'คาดเดาได้อย่างดีที่สุด' แม้ว่าตัวเลือก xpath/css ดั้งเดิมจะใช้ไม่ได้อีกต่อไปหรือไม่


person NeoWang    schedule 21.01.2014    source แหล่งที่มา
comment
ซีลีเนียม. pypi.python.org/pypi/selenium   -  person Priyank Patel    schedule 21.01.2014


คำตอบ (3)


หน้าต่างๆ มีศักยภาพที่จะเปลี่ยนแปลงอย่างมากจนการสร้างเครื่องมือขูดที่ "ชาญฉลาด" อาจเป็นเรื่องยากทีเดียว และถ้าเป็นไปได้ เครื่องขูดก็ค่อนข้างคาดเดาไม่ได้ แม้ว่าจะมีเทคนิคแปลกๆ เช่น การเรียนรู้ของเครื่อง ฯลฯ ก็ตาม เป็นการยากที่จะสร้างเครื่องขูดที่มีทั้งความน่าเชื่อถือและความยืดหยุ่นแบบอัตโนมัติ

การบำรุงรักษาค่อนข้างเป็นรูปแบบศิลปะที่เน้นไปที่วิธีการกำหนดและใช้งานตัวเลือก

ในอดีตฉันได้ใช้ตัวเลือก "สองขั้นตอน" ของตัวเอง:

  1. (ค้นหา) ขั้นตอนแรกมีความยืดหยุ่นสูงและตรวจสอบโครงสร้างของหน้าไปยังองค์ประกอบที่ต้องการ หากขั้นตอนแรกล้มเหลว จะเกิดข้อผิดพลาด "โครงสร้างเพจที่เปลี่ยนแปลง" บางชนิด

  2. (ดึงข้อมูล) ขั้นตอนที่สองนั้นค่อนข้างยืดหยุ่นและดึงข้อมูลจากองค์ประกอบที่ต้องการบนเพจ

วิธีนี้ช่วยให้สแครปเปอร์แยกตัวเองจากการเปลี่ยนแปลงหน้าที่รุนแรงด้วยการตรวจจับอัตโนมัติในระดับหนึ่ง ในขณะที่ยังคงรักษาระดับความยืดหยุ่นที่น่าเชื่อถือไว้ได้

ฉันมักจะใช้ตัวเลือก xpath บ่อยครั้ง และค่อนข้างน่าประหลาดใจจริงๆ กับการฝึกฝนเล็กๆ น้อยๆ ว่าคุณจะมีความยืดหยุ่นเพียงใดกับตัวเลือกที่ดีในขณะที่ยังคงแม่นยำมาก ฉันแน่ใจว่าตัวเลือก css คล้ายกัน การดำเนินการนี้จะง่ายขึ้นหากการออกแบบเพจมีความหมายและ "เรียบ" มากขึ้น

คำถามสำคัญบางประการที่ต้องตอบคือ:

  1. คุณคาดหวังที่จะเปลี่ยนแปลงอะไรบนเพจ?

  2. คุณคาดหวังอะไรที่จะคงเหมือนเดิมบนเพจ?

เมื่อตอบคำถามเหล่านี้ ยิ่งคุณแม่นยำมากเท่าใด ตัวเลือกของคุณก็จะยิ่งดีขึ้นเท่านั้น

ท้ายที่สุดแล้ว คุณเป็นผู้เลือกว่าคุณต้องการรับความเสี่ยงมากน้อยเพียงใด ตัวเลือกของคุณจะน่าเชื่อถือเพียงใด เมื่อทั้งค้นหาและดึงข้อมูลบนเพจ วิธีที่คุณสร้างมันสร้างความแตกต่างอย่างมาก และวิธีที่ดีที่สุดคือรับข้อมูลจาก web-api ซึ่งหวังว่าจะเริ่มมีแหล่งข้อมูลมากขึ้น


แก้ไข: ตัวอย่างเล็ก ๆ

เมื่อใช้สถานการณ์ของคุณ โดยที่องค์ประกอบที่คุณต้องการอยู่ที่ .content > .deal > .tag > .price ตัวเลือก .content .price ทั่วไปจะ "ยืดหยุ่น" มากเกี่ยวกับการเปลี่ยนแปลงหน้า แต่ถ้าสมมุติว่ามีองค์ประกอบบวกลวงเกิดขึ้น เราอาจปรารถนาที่จะหลีกเลี่ยงการแยกออกจากองค์ประกอบใหม่นี้

การใช้ตัวเลือกแบบสองขั้นตอนทำให้เราสามารถระบุขั้นตอนแรกทั่วไปน้อยกว่าและไม่ยืดหยุ่นมากขึ้น เช่น .content > .deal และขั้นตอนที่สองที่กว้างกว่าเช่น .price เพื่อดึงข้อมูลองค์ประกอบสุดท้ายโดยใช้แบบสอบถาม relative ไปยังผลลัพธ์ของ อันดับแรก.

แล้วทำไมไม่ใช้ตัวเลือกอย่าง .content > .deal .price ล่ะ?

สำหรับการใช้งานของฉัน ฉันต้องการตรวจจับการเปลี่ยนแปลงหน้าเพจขนาดใหญ่โดยไม่ต้องรันการทดสอบการถดถอยเพิ่มเติมแยกต่างหาก ฉันตระหนักว่าแทนที่จะใช้ตัวเลือกขนาดใหญ่เพียงตัวเดียว ฉันสามารถเขียนขั้นตอนแรกเพื่อรวมองค์ประกอบโครงสร้างหน้าที่สำคัญได้ ขั้นตอนแรกนี้จะล้มเหลว (หรือรายงาน) หากไม่มีองค์ประกอบโครงสร้างอีกต่อไป จากนั้นฉันสามารถเขียนขั้นตอนที่สองเพื่อดึงข้อมูลได้อย่างสวยงามยิ่งขึ้นโดยสัมพันธ์กับผลลัพธ์ของขั้นตอนแรก

ฉันไม่ควรบอกว่ามันเป็นแนวทางปฏิบัติที่ "ดีที่สุด" แต่มันก็ได้ผลดี

person David    schedule 23.01.2014
comment
ขอบคุณ! ฉันเห็นด้วยอย่างยิ่งว่าการเลือกตัวเลือกที่แข็งแกร่งถือเป็นรูปแบบศิลปะ จริงๆ แล้วฉันกำลังคิดที่จะเขียนตัวเลือกหลายระดับตั้งแต่เฉพาะเจาะจงมาก (เช่น .content›.deal›.tag›.price) ไปจนถึงทั่วไปมากเช่น (.content .price) และถอยกลับไปสู่ระดับถัดไปหากระดับปัจจุบันล้มเหลว แต่ฉันไม่แน่ใจว่านั่นเป็นความคิดที่ดี เพราะอาจทำให้เกิดผลบวกลวงได้ บางครั้งความล้มเหลวก็ยังดีกว่าได้รับข้อมูลที่ไม่ถูกต้อง... และในแบบจำลอง 2 ขั้นตอนของคุณ คุณหมายถึงอะไรเมื่อคุณบอกว่าการดึงข้อมูลสามารถยืดหยุ่นได้บ้าง' พอเจอ element ก็ต้องดึงข้อมูลออกมาเลยใช่ไหม? - person NeoWang; 24.01.2014
comment
สิ่งที่ฉันหมายถึงโดยค่อนข้างยืดหยุ่นนั้นยืดหยุ่น สัมพันธ์ กับส่วนย่อยของหน้าที่ดึงข้อมูลโดยตัวเลือกขั้นแรก ฉันได้เพิ่มตัวอย่างเล็ก ๆ ข้างต้น - person David; 24.01.2014

ไม่เกี่ยวข้องกับ Python โดยสิ้นเชิงและไม่ยืดหยุ่นอัตโนมัติ แต่ฉันคิดว่าเทมเพลตของ เครื่องขูด Xidel ของฉันมี การบำรุงรักษาที่ดีที่สุด

คุณจะเขียนมันเหมือน:

<div id="detail-main"> 
   <del class="originPrice">
     {extract(., "[0-9.]+")} 
   </del>
</div>

แต่ละองค์ประกอบของเทมเพลตจะจับคู่กับองค์ประกอบบนเว็บเพจ และหากเหมือนกัน นิพจน์ภายใน {} จะถูกประเมิน

องค์ประกอบเพิ่มเติมบนหน้าจะถูกละเว้น ดังนั้นหากคุณพบความสมดุลที่เหมาะสมขององค์ประกอบที่รวมและองค์ประกอบที่ถูกลบ เทมเพลตจะไม่ได้รับผลกระทบจากการเปลี่ยนแปลงเล็กๆ น้อยๆ ทั้งหมด การเปลี่ยนแปลงที่สำคัญจะทำให้เกิดความล้มเหลวในการจับคู่ ซึ่งดีกว่า xpath/css มากซึ่งจะส่งคืนชุดว่าง จากนั้น คุณสามารถเปลี่ยนเฉพาะองค์ประกอบที่เปลี่ยนแปลงในเทมเพลตได้ ในกรณีที่เหมาะ คุณจะสามารถนำส่วนต่างระหว่างหน้าเก่า/หน้าที่เปลี่ยนแปลงไปใช้กับเทมเพลตได้โดยตรง ไม่ว่าในกรณีใด คุณไม่จำเป็นต้องค้นหาว่าตัวเลือกใดได้รับผลกระทบ หรืออัปเดตตัวเลือกหลายตัวสำหรับการเปลี่ยนแปลงครั้งเดียว เนื่องจากเทมเพลตสามารถรวมข้อความค้นหาทั้งหมดสำหรับหน้าเดียวไว้ด้วยกัน

person BeniBela    schedule 23.01.2014

แก้ไข: อ๊ะ ตอนนี้ฉันเห็นว่าคุณใช้เครื่องมือเลือก CSS อยู่แล้ว ฉันคิดว่าพวกเขาให้คำตอบที่ดีที่สุดสำหรับคำถามของคุณ ไม่หรอก ฉันไม่คิดว่าจะมีวิธีที่ดีกว่านี้

อย่างไรก็ตาม บางครั้งคุณอาจพบว่าการระบุข้อมูลโดยไม่มีโครงสร้างทำได้ง่ายกว่า ตัวอย่างเช่น หากคุณต้องการขูดราคา คุณสามารถทำการค้นหาด้วยนิพจน์ทั่วไปที่ตรงกับราคา (\$\s+[0-9.]+) แทนที่จะอาศัยโครงสร้าง


โดยส่วนตัวแล้ว ไลบรารี่ webscraping นอกกรอบที่ฉันได้ลองมาทุกประเภทแล้วทิ้งสิ่งที่ต้องการไว้ (กลไก, Scrapy และอื่นๆ)

ฉันมักจะม้วนของตัวเองโดยใช้:

  • urllib2 (ไลบรารีมาตรฐาน)
  • lxml และ
  • cssselect

cssselect อนุญาตให้คุณใช้ตัวเลือก CSS (เช่นเดียวกับ jQuery) เพื่อค้นหา div ตาราง ฯลฯ ที่เฉพาะเจาะจง สิ่งนี้พิสูจน์ได้ว่าเป็นสิ่งล้ำค่าจริงๆ

โค้ดตัวอย่างเพื่อดึงคำถามแรกจากหน้าแรกของ SO:

import urllib2
import urlparse
import cookielib

from lxml import etree
from lxml.cssselect import CSSSelector

post_data = None
url = 'http://www.stackoverflow.com'
cookie_jar = cookielib.CookieJar()
http_opener = urllib2.build_opener(
    urllib2.HTTPCookieProcessor(cookie_jar),
    urllib2.HTTPSHandler(debuglevel=0),
)
http_opener.addheaders = [
    ('User-Agent', 'Mozilla/5.0 (X11; Linux i686; rv:25.0) Gecko/20100101 Firefox/25.0'),
    ('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
]
fp = http_opener.open(url, post_data)
parser = etree.HTMLParser()
doc = etree.parse(fp, parser)

elem = CSSSelector('#question-mini-list > div:first-child > div.summary h3 a')(doc)
print elem[0].text

แน่นอนว่าคุณไม่จำเป็นต้องใช้ cookiejar หรือ user-agent เพื่อจำลอง FireFox แต่ฉันพบว่าฉันต้องการสิ่งนี้เป็นประจำเมื่อทำการขูดไซต์

person Community    schedule 22.01.2014