DEV Community

CopyPasteEngineer
CopyPasteEngineer

Posted on • Edited on

Python Web Scraping part 5 - วิธีเลียนแบบการรับส่งข้อมูลของเว็บเป้าหมาย

บทความนี้คิดว่าจะเป็น part สุดท้าย สำหรับการสอน scraping เบื้องต้นแล้วนะครับ หลังจากนี้ ถ้ายังสนใจกันอยู่ คิดว่าจะพา "ทำโจทย์จริง" กันดู อาจจะเป็นบทความแบบนี้อีก หรืออาจจะมาเป็นคลิป เดี๋ยวจะประกาศให้ทราบนะครับ

ก่อนอื่น ขอทบทวนเนื้อหา parts ก่อน ๆ สักเล็กน้อยนะครับ

part 1: เราพูดถึงภาพหยาบ ๆ ของการ scrape ไว้ ว่าเราอาจจะมองได้เป็น 2 ขั้นตอน ก็คือ การ scrape คือดึงข้อมูลดิบ ๆ ลงมาให้ได้ และ การ extract คือการสกัดส่วนที่เราสนใจ ออกมาจากข้อมูลดิบนั้น

part 2: ก็จะพูดถึงเรื่องของการ extract ข้อมูลก่อน โดยจะแนะนำเครื่องมือที่ช่วยให้สามารถ ตรวจดูโครงสร้างของหน้า web ได้แบบไว ๆ เพื่อหาวิธีสกัดข้อมูลที่เราต้องการออกมา

part 3: จะสอนการสกัดข้อมูลด้วย XPath เบื้องต้น โดยใช้ประโยชน์จากโครงสร้างของหน้า web ที่เราได้เรียนรู้มา

part 4: จะเล่าเกี่ยวกับส่วน scrape บ้าง และได้แนะนำ 7 เทคนิค ที่ช่วยให้เราสามารถ ดึงข้อมูลจากหลาย ๆ เว็บ ที่มีระบบซับซ้อน หรือมีกลไกการป้องกันการ scrape เอาไว้

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

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

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

Alt Text
ตัวอย่าง: เว็บ Pantip จะค่อย ๆ โหลดข้อมูลมาเพิ่มเมื่อ scroll ลง

วิธีการหนึ่งที่ทำได้คือเราก็ต้องเข้าไปดูว่า ข้อมูลที่มันโหลดมาเพิ่มเนี่ย มันโหลดมาจากไหน โหลดมาอย่างไร จากนั้นเราก็แค่เลียนแบบมันก็จบ! ในบทความนี้จึงจะมาแนะนำ Network Inspector เครื่องมือชิ้นหนึ่งใน Google Chrome DevTools สำหรับการตรวจสอบการโหลดข้อมูลให้ได้รู้จักกันครับ

Get Started!

ถ้ายังจำกันได้ใน part ที่ 2 เราก็ได้พูดถึงเครื่องมือชิ้นหนึ่งของ Google Chrome DevTools กันไปแล้ว คือตัว Code Inspector ที่เอาไว้ใช้สำหรับตรวจสอบโครงสร้างของหน้าเว็บได้ เอาไว้ให้ developer ได้ตรวจสอบ ว่าโค้ดที่เขียนสามารถ render ออกมาเป็น HTML ได้ถูกต้องหรือเปล่า

ตัว Network Inspector ที่จะแนะนำให้รู้จักนี้ก็มีเอาไว้ด้วยเหตุผลคล้าย ๆ กันครับ ก็คือเพื่อให้ developer สามารถตรวจสอบได้ ว่าหน้าเว็บที่สร้างเนี่ย ได้ทำการรับส่งข้อมูลได้ถูกต้องตามที่ออกแบบเอาไว้หรือไม่ครับ เช่น เมื่อกดปุ่ม submit แล้ว ข้อมูลใน form ที่กรอกเอาไว้ ถูกส่งไปที่ server ได้ถูก URL ไหม และข้อมูลอยู่ใน format ที่ถูกต้องหรือเปล่านั่นเองครับ

และเช่นเดียวกับ Code Inspector คือเราสามารถใช้เครื่องมือตัวนี้ ให้เป็นประโยชน์ในการทำ scraping ได้ครับ!

เว็บข่าวไทยรัฐ

เว็บที่จะใช้เป็นตัวอย่างครั้งนี้คือเว็บข่าวของไทยรัฐ หน้านี้นะครับ thairath.co.th/news/politic ครับ โดยให้เราเลื่อนหน้าจอลงมาที่ส่วนที่เขียนว่า ข่าวอื่นๆ ตามในภาพด้านล่าง จะเห็นว่าหน้าเว็บนี้เลือกที่จะแสดงแค่ 12 ข่าวล่าสุดเท่านั้น โดยจะมีปุ่ม ดูเพิ่ม ที่ด้านล่างให้กด เพื่อให้เว็บทำการโหลดข่าวมาแสดงเพิ่มครับ

Alt Text

ในกรณีแบบนี้ ถ้าเรายิง requests ไปที่ URL https://www.thairath.co.th/news/politic โดยตรง เราจะไม่ได้ข้อมูลตรงนี้ีมาครับ เนื่องจากข้อมูลมันจะยังไม่ถูกโหลดเข้ามาในตอนแรก เพราะฉะนั้นเราจะต้อง ทำการเลียนแบบการ request ของปุ่ม "ดูเพิ่ม" (ขั้นตอนที่ 4 ใน diagram ตรงลูกศรสีแดง) ดูว่ามันโหลดข้อมูลใหม่มาเพิ่มได้อย่างไร แล้วเราก็ทำตามนั่นเองครับ

Alt Text
diagram แสดงการรับส่งข้อมูล ของเว็บ Thairath กับฝั่ง server

1. เปิด Network Inspector

ขั้นแรกให้เราไปที่ หน้าเว็บ ก่อนนะครับ แล้วกดปุ่ม F12 เพื่อเปิด Chrome DevTools ขึ้นมา จะมีหน้าต่างด้านขวาคล้าย ๆ ในภาพขึ้นมาครับ

Alt Text

จากนั้นให้เราเลือกเครื่อง Network Inspector โดยคลิกที่คำว่า Network จากแทบด้านบน (ของบางท่านอาจจะถูกย่อเอาไว้อยู่ ให้ลองเปิดหาดูจากตรงลูกศร >>) เมื่อเปิดมาแล้วจะเห็นเป็นหน้าว่าง ๆ แบบในรูปครับ นี่คือ Network Inspector พร้อมใช้งานแล้วครับ

Alt Text

2. ลองโหลดข้อมูล

ตอนนี้เมื่อเปิดหน้า Inspector ขึ้นมาแล้ว ถ้าหากมีการโหลดข้อมูลอะไรก็ตามเกิดขึ้น รายละเอียดของการโหลดข้อมูลจะแสดงขึ้นมาทันทีครับ

ให้ลองกดปุ่ม "ดูเพิ่ม" เพื่อโหลดข่าวมาแสดงเพิ่มเติม ก็จะเห็นข้อมูลหลายอย่างถูกโหลดขึ้นมาพร้อมกันดังภาพ

Alt Text

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

3. ดูข้อมูลของการ request

จากในรายการจะเห็นว่าตั้งแต่ข้อมูลบรรทัดที่ 2 เป็นต้นไป จะเป็นชื่อมั่ว ๆ ทั้งนั้น ซึ่งเป็นรูปภาพของแต่ละข่าวครับ สังเกตุได้จากที่คอลัมน์ Type จะเขียนว่าเป็นไฟล์ jpeg หรือลอง double คลิกเข้าไปก็จะสามารถดูภาพแต่ละภาพได้ครับ

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

ให้ลองคลิกเข้าไปที่ข้อมูลบรรทัดแรก จะเห็นว่ามันจะแสดงรายละเอียดที่สำคัญที่ใช้ request ไปทั้งหมดออกมา

Alt Text

ซึ่งข้อมูลที่แสดงออกมานี้ เพียงพอให้เราสามารถนำไป request เลียนแบบเองใน python ได้ครับ ตั้งแต่ URL คือปลายทางที่เราส่งข้อมูลไป, method คือวิธีการส่ง ซึ่งสำหรับกรณีนี้คือ GET request, headers ที่ใช้ request หรือถ้าบางที method ที่ใช้เป็น POST request ในส่วนท้ายสุดของหน้าก็จะแสดง Form Data พ่วงมาให้ด้วยครับ

Alt Text
ตัวอย่าง Form Data ที่จะถูกแสดงออกมาสำหรับ POST request (ไม่เกี่ยวกับเว็บนี้นะครับ เอามาให้ดูตัวอย่างเฉย ๆ)

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

Alt Text

ก็จะเห็นว่าข้อมูลที่ถูกส่งกลับมา เป็น JSON ครับ โดยมี items เป็น array ของรายละเอียดแต่ละข่าวที่ถูกโหลดขึ้นมาจริง ๆ เพราะฉะนั้น อันนี้ก็คือ URL ของ API ที่เราต้องการจริง ๆ นั่นเองครับ

4. เลียนแบบ request

ต่อไปเราก็เอารายละเอียดของ request ตัวนั้นมาลอกเลียนแบบใน Python นะครับ ก็จะได้เป็นตามโค้ดด้านล่าง

import requests
url = 'https://www.thairath.co.th/loadmore&section=/news/politic&ts=1597188900&limit=8'

resp = requests.get(url)
data = resp.json()  # เนื่องจากผลลัพธ์ที่ออกมาเป็น JSON เราสามารถใช้ .json() เพื่อแปลงให้เป็น dict ใน Python ได้เลย

จะได้ข้อมูล data ออกมาเป็น dict หน้าตาประมาณนี้นะครับ
Alt Text
...
Alt Text

5. Pagination?

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

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

ซึ่งวิธีการเปลี่ยนเนี่ย ก็ขึ้นอยู่กับเว็บเป้าหมายกำหนดเลยครับ บางเว็บใช้เลขหน้าแบบง่าย ๆ 1, 2, 3, 4, ... บางเว็บอาจใช้เลข ID ของ content ที่โหลดไปล่าสุดส่งต่อไปให้ เป็นต้น ลักษณะพวกนี้เรียกว่า Pagination ครับ

อย่างในกรณีนี้ ถ้าเราดู URL ที่เราใช้ดี ๆ ก็จะเห็นว่ามันส่งข้อมูลไป 2 อย่าง ได้แก่ ts=1597188900 และ limit=8 ตัว limit=8 เนี่ยเราเดาได้ใช่ไหมครับ ว่ามันหมายถึงจำนวนของข่าวที่ขอไป คือให้โหลดมาแค่ 8 ข่าวต่อครั้งก็พอ แล้ว ts ล่ะ??

คำใบ้อยู่ที่ข้อมูลที่ส่งกลับมาครับ จะเห็นว่าในแต่ละครั้งที่ request ข้อมูลที่ส่งกลับมาจะมีค่า minTs อยู่ด้วย ซึ่งจำนวนหลักและตัวเลขมันก็ดูคล้าย ๆ กับ ts ที่เราส่งไปนะครับ

Alt Text

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

url = 'https://www.thairath.co.th/loadmore&section=/news/politic&ts=1597149600&limit=8'
resp = requests.get(url)
data2 = resp.json()

note: จริง ๆ แล้ว ts ก็คือ timestamp นั่นเองครับ เห็นตัวย่อ และเห็นตัวแรกแล้ว หลาย ๆ ท่านน่าจะเดาได้ตั้งแต่แรกใช่ไหมครับ 55

เพราะฉะนั้นเราสามารถที่จะ automate การดึงข่าวมาหลาย ๆ หน้าดังโค้ดด้านล่าง

import requests

def get_url(ts=None):
    if ts is None:
        return 'https://www.thairath.co.th/loadmore&section=/news/politic&limit=8'

    return f'https://www.thairath.co.th/loadmore&section=/news/politic&ts={ts}&limit=8'

url = get_url()                   # ใช้ url เริ่มต้น คือไม่ต้องส่งค่า ts ไป
for i in range(10):               # วนไป 10 หน้า
    resp = requests.get(url)      # โหลดข้อมูล
    data = resp.json()            # แปลงเป็น dict

    for x in data['items']:       # ไล่ print title ของแต่ละข่าว
        print(x['title'])

    url = get_url(data['minTs'])  # สร้าง url ใหม่ โดยส่ง ts ให้มีค่าเท่ากับ minTs

Alt Text

Summary

จบไปแล้วนะครับ 5 parts สำหรับพื้นฐานทั้งหมดในการทำ Web Scraping ด้วย Python ถ้าเข้าใจทั้งหมดก็น่าจะสามารถดึงข้อมูลจากเว็บส่วนใหญ่ได้เยอะแล้วครับ แล้วก็อย่าลืมติดตามบทความต่อ ๆ ไปเกี่ยวกับการ scraping ด้วยนะครับ เดี๋ยวเรามาลองวิชากับเว็บจริง ๆ กัน

และก็ขอย้ำอีกครั้ง <เหมือนที่ย้ำอยู่ตลอดตั้งแต่ part แรก> ก็คือบทความนี้ทำขึ้นเพื่อการศึกษานะครับ ในการนำไปใช้จริง ควรคำนึงถึงความเหมาะสม และไม่ทำให้ผู้อื่นเดือดร้อนนะครับ

ถ้ามีเรื่องไหนที่สนใจเพิ่มเติมสามารถ comment เอาไว้ได้นะครับ

FB Page: Copy Paste Engineer

- ขอบคุณที่อ่านครับ -

part อื่น ๆ ใน series
- part 1: การดูดข้อมูลเบื้องต้น ด้วย Python
- part 2: Chrome's Code Inspector
- part 3: เทคนิคการ extract ข้อมูลด้วย XPath
- part 4: ทำไมถึง scrape บางเว็บไม่ได้??? 7 เทคนิคง่าย ๆ ให้ scrape เว็บส่วนใหญ่ได้ลื่นปรื๊ด
- part 5: วิธีเลียนแบบการรับส่งข้อมูลของเว็บเป้าหมาย

Top comments (0)