การขูดเว็บสมัยใหม่ด้วย Python และ Selenium

เผยแพร่แล้ว: 2022-03-11

มีการใช้การขูดเว็บเพื่อดึงข้อมูลจากเว็บไซต์เกือบตั้งแต่เกิดเวิลด์ไวด์เว็บ ในช่วงแรกๆ การสแครปนั้นทำบนหน้าสแตติกเป็นหลัก ซึ่งได้แก่ หน้าที่มีองค์ประกอบ แท็ก และข้อมูล

อย่างไรก็ตาม เมื่อไม่นานมานี้ เทคโนโลยีขั้นสูงในการพัฒนาเว็บทำให้งานยากขึ้นเล็กน้อย ในบทความนี้ เราจะสำรวจว่าเราจะขูดข้อมูลอย่างไรในกรณีที่เทคโนโลยีใหม่และปัจจัยอื่นๆ ป้องกันการขูดข้อมูลแบบมาตรฐาน

การขูดข้อมูลแบบดั้งเดิม

เนื่องจากเว็บไซต์ส่วนใหญ่สร้างหน้าที่อ่านง่ายของมนุษย์มากกว่าการอ่านอัตโนมัติ การคัดแยกเว็บส่วนใหญ่ประกอบด้วยการย่อยข้อมูลมาร์กอัปของหน้าเว็บโดยทางโปรแกรม (ลองนึกภาพคลิกขวา, ดูแหล่งที่มา) จากนั้นตรวจจับรูปแบบคงที่ในข้อมูลนั้นที่จะอนุญาตให้โปรแกรม เพื่อ "อ่าน" ข้อมูลต่างๆ และบันทึกลงในไฟล์หรือฐานข้อมูล

การขูดข้อมูล

หากพบข้อมูลรายงาน บ่อยครั้ง ข้อมูลจะสามารถเข้าถึงได้โดยการส่งผ่านตัวแปรแบบฟอร์มหรือพารามิเตอร์ด้วย URL ตัวอย่างเช่น:

 https://www.myreportdata.com?month=12&year=2004&clientid=24823

Python ได้กลายเป็นหนึ่งในภาษาการขูดเว็บที่ได้รับความนิยมมากที่สุดเนื่องจากส่วนหนึ่งมาจากไลบรารีเว็บต่างๆ ที่สร้างขึ้นสำหรับมัน ไลบรารียอดนิยมแห่งหนึ่งชื่อ Beautiful Soup ได้รับการออกแบบมาเพื่อดึงข้อมูลออกจากไฟล์ HTML และ XML โดยอนุญาตให้ค้นหา นำทาง และแก้ไขแท็ก (เช่น ต้นไม้แยกวิเคราะห์)

การขูดบนเบราว์เซอร์

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

ปัญหาหลักสามประการที่ขัดขวางไม่ให้ฉันใช้วิธีขูดแบบมาตรฐาน:

  1. ใบรับรอง. มีใบรับรองที่ต้องติดตั้งเพื่อเข้าถึงส่วนของเว็บไซต์ที่มีข้อมูลอยู่ เมื่อเข้าถึงหน้าแรก จะมีข้อความแจ้งให้ฉันเลือกใบรับรองที่ถูกต้องของใบรับรองที่ติดตั้งในคอมพิวเตอร์ของฉัน แล้วคลิกตกลง
  2. ไอเฟรม ไซต์ใช้ iframes ซึ่งทำให้การขูดตามปกติของฉันยุ่งเหยิง ใช่ ฉันสามารถลองค้นหา URL ของ iframe ทั้งหมด จากนั้นจึงสร้างแผนผังเว็บไซต์ แต่ดูเหมือนว่าจะไม่สะดวก
  3. จาวาสคริปต์ ข้อมูลเข้าถึงได้หลังจากกรอกแบบฟอร์มพร้อมพารามิเตอร์ (เช่น รหัสลูกค้า ช่วงวันที่ ฯลฯ) โดยปกติ ฉันจะข้ามแบบฟอร์มและส่งตัวแปรแบบฟอร์ม (ผ่าน URL หรือเป็นตัวแปรแบบฟอร์มที่ซ่อนอยู่) ไปยังหน้าผลลัพธ์และดูผลลัพธ์ แต่ในกรณีนี้ แบบฟอร์มมี JavaScript ซึ่งไม่อนุญาตให้ฉันเข้าถึงตัวแปรของแบบฟอร์มในลักษณะปกติ

ดังนั้นฉันจึงตัดสินใจละทิ้งวิธีการแบบเดิมและดูเครื่องมือที่เป็นไปได้สำหรับการขูดบนเบราว์เซอร์ สิ่งนี้จะทำงานต่างจากปกติ – แทนที่จะไปที่หน้าโดยตรง ดาวน์โหลด parse tree และดึงองค์ประกอบข้อมูลออกมา ฉันจะ "ทำตัวเหมือนมนุษย์" และใช้เบราว์เซอร์เพื่อไปยังหน้าที่ฉันต้องการ จากนั้นจึงขูด ข้อมูล - ดังนั้นจึงไม่ต้องจัดการกับอุปสรรคดังกล่าว

ซีลีเนียม

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

ภาษาที่ฉันชอบสำหรับการขูดเว็บคือ Python เนื่องจากมีไลบรารีที่ผสานรวมอย่างดีซึ่งโดยทั่วไปแล้วสามารถจัดการฟังก์ชันทั้งหมดที่จำเป็นได้ และแน่นอนว่ามีไลบรารี Selenium สำหรับ Python สิ่งนี้จะทำให้ฉันสามารถยกตัวอย่างเช่น “เบราว์เซอร์” – Chrome, Firefox, IE, ฯลฯ – แล้วแสร้งทำเป็นว่าฉันใช้เบราว์เซอร์เองเพื่อเข้าถึงข้อมูลที่ฉันต้องการ และหากฉันไม่ต้องการให้เบราว์เซอร์ปรากฏขึ้นจริงๆ ฉันสามารถสร้างเบราว์เซอร์ในโหมด "หัวขาด" ได้ ซึ่งทำให้ผู้ใช้ทุกคนมองไม่เห็น

การติดตั้งโครงการ

ในการเริ่มทดลอง ฉันต้องสร้างโปรเจ็กต์และได้ทุกอย่างที่ต้องการ ฉันใช้เครื่อง Windows 10 และทำให้แน่ใจว่าฉันมี Python เวอร์ชันที่อัปเดตแล้ว (เป็นเวอร์ชัน 3.7.3) ฉันสร้างสคริปต์ Python เปล่า จากนั้นโหลดไลบรารี่ที่ฉันคิดว่าอาจจำเป็น โดยใช้ PIP (ตัวติดตั้งแพ็คเกจสำหรับ Python) หากฉันยังไม่ได้โหลดไลบรารี่ไว้ นี่คือห้องสมุดหลักที่ฉันเริ่มด้วย:

  1. คำขอ (สำหรับการส่งคำขอ HTTP)
  2. URLLib3 (การจัดการ URL)
  3. ซุปสวย (เผื่อว่าซีลีเนียมรับมือไม่ไหว)
  4. ซีลีเนียม (สำหรับการนำทางบนเบราว์เซอร์)

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

ปัญหาที่ 1 – ใบรับรอง

ตัวเลือกแรกที่ฉันต้องทำคือเบราว์เซอร์ใดที่ฉันจะบอกให้ซีลีเนียมใช้ เนื่องจากโดยทั่วไปฉันใช้ Chrome และสร้างขึ้นจากโปรเจ็กต์ Chromium โอเพ่นซอร์ส (ยังใช้โดยเบราว์เซอร์ Edge, Opera และ Amazon Silk) ฉันคิดว่าฉันจะลองทำดูก่อน

ฉันสามารถเริ่มต้น Chrome ในสคริปต์ได้โดยเพิ่มส่วนประกอบไลบรารีที่ฉันต้องการ จากนั้นออกคำสั่งง่ายๆ สองสามคำสั่ง:

 # Load selenium components from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait, Select from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException # Establish chrome driver and go to report site URL url = "https://reportdata.mytestsite.com/transactionSearch.jsp" driver = webdriver.Chrome() driver.get(url)

เนื่องจากฉันไม่ได้เปิดเบราว์เซอร์ในโหมดหัวขาด เบราว์เซอร์จึงปรากฏขึ้นจริง ๆ และฉันก็เห็นว่ามันกำลังทำอะไรอยู่ มันขอให้ฉันเลือกใบรับรองทันที (ซึ่งฉันติดตั้งไว้ก่อนหน้านี้)

ปัญหาแรกที่ต้องแก้ไขคือใบรับรอง วิธีการเลือกและยอมรับเพื่อเข้าสู่เว็บไซต์? ในการทดสอบสคริปต์ครั้งแรกของฉัน ฉันได้รับข้อความแจ้งนี้:

การขูดข้อมูล

นี้ไม่ดี ฉันไม่ต้องการคลิกปุ่มตกลงด้วยตนเองทุกครั้งที่ฉันเรียกใช้สคริปต์

ปรากฏว่าฉันสามารถหาวิธีแก้ไขปัญหานี้ได้โดยไม่ต้องเขียนโปรแกรม แม้ว่าฉันหวังว่า Chrome จะมีความสามารถในการส่งชื่อใบรับรองเมื่อเริ่มต้นระบบ แต่ฟีเจอร์นั้นไม่มีอยู่จริง อย่างไรก็ตาม Chrome มีความสามารถในการเลือกใบรับรองโดยอัตโนมัติหากมีรายการใดอยู่ในรีจิสทรี Windows ของคุณ คุณสามารถตั้งค่าให้เลือกใบรับรองแรกที่เห็น หรือให้เฉพาะเจาะจงมากกว่านี้ก็ได้ เนื่องจากฉันโหลดใบรับรองเพียงใบเดียว ฉันจึงใช้รูปแบบทั่วไป

การขูดข้อมูล

ดังนั้น ด้วยชุดดังกล่าว เมื่อฉันบอก Selenium ให้เปิด Chrome และข้อความแจ้งใบรับรองปรากฏขึ้น Chrome จะ "เลือกอัตโนมัติ" ใบรับรองและดำเนินการต่อ

ปัญหาที่ 2 – Iframes

ตกลง ตอนนี้ฉันอยู่ในไซต์และแบบฟอร์มปรากฏขึ้น แจ้งให้ฉันพิมพ์รหัสลูกค้าและช่วงวันที่ของรายงาน

การขูดข้อมูล

จากการตรวจสอบแบบฟอร์มในเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ (F12) ฉันสังเกตเห็นว่ามีการนำเสนอแบบฟอร์มใน iframe ดังนั้น ก่อนที่ฉันจะเริ่มกรอกแบบฟอร์ม ฉันต้อง "เปลี่ยน" เป็น iframe ที่เหมาะสมซึ่งมีแบบฟอร์มอยู่ ในการดำเนินการนี้ ฉันได้เรียกใช้ฟีเจอร์สลับไปใช้ของ Selenium ดังนี้:

 # Switch to iframe where form is frame_ref = driver.find_elements_by_tag_name("iframe")[0] iframe = driver.switch_to.frame(frame_ref)

ดี ตอนนี้อยู่ในกรอบที่ถูกต้อง ฉันสามารถกำหนดส่วนประกอบ เติมข้อมูลในฟิลด์รหัสลูกค้า และเลือกรายการแบบเลื่อนลงวันที่:

 # Find the Customer ID field and populate it element = driver.find_element_by_name("custId") element.send_keys(custId) # send a test id # Find and select the date drop-downs select = Select(driver.find_element_by_name("fromMonth")) select.select_by_visible_text(from_month) select = Select(driver.find_element_by_name("fromYear")) select.select_by_visible_text(from_year) select = Select(driver.find_element_by_name("toMonth")) select.select_by_visible_text(to_month) select = Select(driver.find_element_by_name("toYear")) select.select_by_visible_text(to_year)

ปัญหาที่ 3 – JavaScript

สิ่งเดียวที่เหลืออยู่ในแบบฟอร์มคือ "คลิก" ที่ปุ่ม "ค้นหา" ซึ่งจะทำให้การค้นหาเริ่มต้นขึ้น สิ่งนี้ค่อนข้างยุ่งยากเล็กน้อยเนื่องจากดูเหมือนว่าปุ่มค้นหาจะถูกควบคุมโดย JavaScript และไม่ใช่ปุ่มประเภท "ส่ง" ปกติ เมื่อตรวจสอบในเครื่องมือสำหรับนักพัฒนาแล้ว ฉันพบภาพปุ่มและสามารถรับ XPath ได้ด้วยการคลิกขวา

การขูดข้อมูล

เมื่อมีข้อมูลนี้ ฉันจึงพบองค์ประกอบในหน้านั้นแล้วจึงคลิกเข้าไป

 # Find the 'Find' button, then click it driver.find_element_by_xpath("/html/body/table/tbody/tr[2]/td[1]/table[3]/tbody/tr[2]/td[2]/input").click()

และ voila แบบฟอร์มถูกส่งและข้อมูลปรากฏขึ้น! ตอนนี้ ฉันสามารถขูดข้อมูลทั้งหมดบนหน้าผลลัพธ์และบันทึกได้ตามต้องการ หรือฉันทำได้?

การรับข้อมูล

อันดับแรก ฉันต้องจัดการกับกรณีที่การค้นหาไม่พบอะไรเลย นั่นค่อนข้างตรงไปตรงมา โดยจะแสดงข้อความในแบบฟอร์มการค้นหาโดยไม่ปล่อยไว้ เช่น "ไม่พบระเบียน" ฉันเพียงแค่ค้นหาสตริงนั้นและหยุดอยู่ที่นั่นหากฉันพบ

แต่ถ้าผลลัพธ์ออกมา ข้อมูลจะถูกนำเสนอใน div พร้อมเครื่องหมายบวก (+) เพื่อเปิดธุรกรรมและแสดงรายละเอียดทั้งหมด ธุรกรรมที่เปิดแสดงเครื่องหมายลบ (-) ซึ่งเมื่อคลิกจะเป็นการปิด div การคลิกเครื่องหมายบวกจะเรียก URL เพื่อเปิด div และปิดอันที่เปิดอยู่

การขูดข้อมูล

ดังนั้นจึงจำเป็นต้องค้นหาเครื่องหมายบวกบนหน้า รวบรวม URL ถัดจากแต่ละรายการ จากนั้นวนซ้ำเพื่อรับข้อมูลทั้งหมดสำหรับทุกธุรกรรม

 # Loop through transactions and count links = driver.find_elements_by_tag_name('a') link_urls = [link.get_attribute('href') for link in links] thisCount = 0 isFirst = 1 for url in link_urls: if (url.find("GetXas.do?processId") >= 0): # URL to link to transactions if isFirst == 1: # already expanded + isFirst = 0 else: driver.get(url) # collapsed +, so expand # Find closest element to URL element with correct class to get tran type tran_type=driver.find_element_by_xpath("//*[contains(@href,'/retail/transaction/results/GetXas.do?processId=-1')]/following::td[@class='txt_75b_lmnw_T1R10B1']").text # Get transaction status status = driver.find_element_by_class_name('txt_70b_lmnw_t1r10b1').text # Add to count if transaction found if (tran_type in ['Move In','Move Out','Switch']) and (status == "Complete"): thisCount += 1

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

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

อุปสรรคและแนวทางแก้ไขเพิ่มเติมที่เป็นไปได้

อาจมีอุปสรรคอื่น ๆ มากมายปรากฏขึ้นในขณะที่ขูดเว็บไซต์สมัยใหม่ด้วยเบราว์เซอร์ของคุณเอง แต่ส่วนใหญ่สามารถแก้ไขได้ นี่คือบางส่วน:

  • พยายามหาอะไรก่อนปรากฏ

    ขณะเรียกดูตัวเอง คุณพบว่ากำลังรอให้หน้าปรากฏขึ้นบ่อยเพียงใด บางครั้งเป็นเวลาหลายวินาที สิ่งเดียวกันนี้สามารถเกิดขึ้นได้ในขณะที่นำทางโดยทางโปรแกรม คุณมองหาคลาสหรือองค์ประกอบอื่น - และไม่มี!

    โชคดีที่ซีลีเนียมมีความสามารถในการรอจนกว่าจะเห็นองค์ประกอบบางอย่าง และสามารถหมดเวลาได้หากองค์ประกอบไม่ปรากฏขึ้น เช่น:

 element = WebDriverWait(driver, 10). until(EC.presence_of_element_located((By.ID, "theFirstLabel")))


  • ผ่าน Captcha

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

สำหรับข้อความแจ้งง่ายๆ (เช่น "2 + 3 คืออะไร") โดยทั่วไปสามารถอ่านและทำความเข้าใจได้ง่าย อย่างไรก็ตาม สำหรับอุปสรรคขั้นสูง มีไลบรารีที่สามารถช่วยพยายามถอดรหัสได้ ตัวอย่างบางส่วน ได้แก่ 2Captcha, Death by Captcha และ Bypass Captcha

  • การเปลี่ยนแปลงโครงสร้างเว็บไซต์

    เว็บไซต์มีไว้เพื่อเปลี่ยนแปลง – และมักจะทำ นั่นเป็นเหตุผลที่เมื่อเขียนสคริปต์การขูด คุณควรจำสิ่งนี้ไว้เสมอ คุณจะต้องคิดว่าจะใช้วิธีใดเพื่อค้นหาข้อมูลและวิธีใดที่ไม่ควรใช้ พิจารณาเทคนิคการจับคู่บางส่วน แทนที่จะพยายามจับคู่ทั้งวลี ตัวอย่างเช่น เว็บไซต์อาจเปลี่ยนข้อความจาก "ไม่พบระเบียน" เป็น "ไม่พบระเบียน" แต่ถ้าการจับคู่ของคุณอยู่ใน "ไม่มีระเบียน" คุณก็ไม่เป็นไร นอกจากนี้ ให้พิจารณาว่าจะจับคู่กับ XPATH, ID, ชื่อ, ข้อความลิงก์, แท็กหรือชื่อคลาส หรือตัวเลือก CSS – และตัวเลือกใดที่มีแนวโน้มน้อยที่สุดที่จะเปลี่ยนแปลง

สรุป: Python และ Selenium

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

ข้อสังเกต ไม่ได้หมายความว่าทุกเว็บไซต์ ควร ถูกคัดลอก บางแห่งมีข้อจำกัดที่ชอบด้วยกฎหมาย และมีหลายคดีในศาลที่ตัดสินความถูกกฎหมายในการคัดลอกไซต์บางแห่ง ในทางกลับกัน บางไซต์ยินดีต้อนรับและสนับสนุนให้ดึงข้อมูลจากเว็บไซต์ของพวกเขา และในบางกรณีก็มี API เพื่อทำให้สิ่งต่างๆ ง่ายขึ้น

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

แหล่งข้อมูลที่แนะนำสำหรับการขูดเว็บที่ซับซ้อน:

  • Advanced Python Web Scraping: แนวทางปฏิบัติที่ดีที่สุด & วิธีแก้ปัญหา
  • การขูดที่ทำได้ด้วยตัวเองที่ปรับขนาดได้: วิธีสร้างและใช้งานเครื่องขูดในขนาดใหญ่