---
description: May 14, 2020
---

# Web Scraping Part 3

## Python Web Scraping part 3 - เทคนิคการ extract ข้อมูลด้วย XPath

มาต่อกันกับ series Python Web Scraping part 3 นะครับ หลังจากที่ใน [part ที่แล้ว](https://dev.to/copypasteengineer/python-web-scraping-part-2-chrome-s-code-inspector-3ok6) เราได้เรียนรู้วิธีการแกะ XPath ของแต่ละ element ออกมาจาก web browser แบบง่าย ๆ กันแล้ว แต่ทีนี้ในกรณีที่ข้อมูลที่เราสนใจมีหลายตำแหน่งมาก **เราคงไม่อยากเสียเวลานั่ง extract แต่ละ element ด้วยมือทั้งหมดใช่ไหมครับ** (เพราะถ้าจะทำด้วยมือทั้งหมด ก็ก๊อปตัวข้อมูลมาตรง ๆ เลยสะดีกว่า 555)

ท่านที่พอจะมีประสบการณ์การทำเว็บมาบ้าง พอเห็น XPath สัก 1 หรือ 2 อัน ก็อาจจะเดาได้แล้ว ว่า XPath ที่เหมาะสม ที่น่าจะครอบคลุมทุก ๆ element นั้น ควรจะเป็นอะไร แต่ถึงจะเดาไม่ออกก็ไม่เป็นไรครับ เพราะอย่างบาง website ที่ซับซ้อนขึ้นหน่อย ต่อให้มีประสบการณ์ก็คงเดาไม่ออกในทีแรกเช่นกัน

สำหรับในบทความนี้ก็จะสอนวิธีหนึ่งที่สามารถทำได้ เพื่อหา XPath ที่ **"น่าจะ"** ครอบคลุมข้อมูลทั้งหมดที่เราต้องการมานะครับ ซึ่งเป็นวิธีที่ผู้เขียนเองก็ใช้งานอยู่จริงในการ scrape website ในหลาย ๆ ครั้งครับ (ที่บอกว่า **"น่าจะ"** เนี่ย เพราะว่าในความเป็นจริงไม่มีใครรู้หรอกครับ ว่า website เป้าหมายนี้ ถูกเขียน หรือถูกสร้างมาอย่างไร แต่ด้วยวิธีดังกล่าว+หลักของสถิติ เราก็จะมั่นใจได้ในระดับหนึ่งว่า XPath ของเรามัน **"น่าจะ"** ดีเพียงพอครับ)

วิธีหนึ่งที่ผมทำก็คือ sample XPath ออกมาดู เอามา **สำรวจ** เพิ่มอีกเยอะ ๆ ก็จะช่วยให้เห็นภาพมากขึ้นครับ จากนั้นเราก็จะใช้ syntax ของ XPath ในการ select และระบุ condition เพื่อให้ครอบคลุมกรณีที่เรา sample ออกมา ให้มากที่สุดครับ

## **Get Started!**

ทบทวนสักเล็กน้อย ใน part นี้เราก็ยังอยู่กับเว็บ Wikipedia [https://th.wikipedia.org/wiki/รายชื่อเทศบาลตำบลในประเทศไทย](https://th.wikipedia.org/wiki/%E0%B8%A3%E0%B8%B2%E0%B8%A2%E0%B8%8A%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B9%80%E0%B8%97%E0%B8%A8%E0%B8%9A%E0%B8%B2%E0%B8%A5%E0%B8%95%E0%B8%B3%E0%B8%9A%E0%B8%A5%E0%B9%83%E0%B8%99%E0%B8%9B%E0%B8%A3%E0%B8%B0%E0%B9%80%E0%B8%97%E0%B8%A8%E0%B9%84%E0%B8%97%E0%B8%A2) กันเหมือนเดิมนะครับ โดยเป้าหมายคือ **หา XPath สำหรับ extract รายชื่อเทศบาลตำบลทั้งหมด** ซึ่งใน [part ที่แล้ว](https://dev.to/copypasteengineer/python-web-scraping-part-2-chrome-s-code-inspector-3ok6) เราใช้ Chrome Inspector ในการแกะ XPath ของ **"Element" หนึ่ง ๆ** มาได้แบบง่าย ๆ แล้วนะครับ ในตอนนี้เรากำลังจะหา XPath ที่สามารถ extract ข้อมูลทั้งหมดได้ **(คือครอบคลุมทุก element ไม่ใช่แค่ 1 element)**

#### **1. Sample XPath มาเพิ่ม**

อย่างในเว็บเป้าหมาย ถ้าลอง scroll เลื่อนขึ้นลงดู จะเห็นว่ามันมีหลายตาราง เพราะฉะนั้น เพื่อให้เราได้เห็นรูปแบบที่หลากหลาย แล้วก็ครอบคลุมข้อมูลทั้งหมดมากที่สุด ก็ควรจะหยิบ sample ออกมาจากหลาย ๆ ที่ กระจาย ๆ กันหน่อยนั่นเองครับ เช่น เอามาจากตาราง 1 บ้าง จากตาราง 8 บ้าง จากตารางสุดท้ายบ้าง หรือจากหัวตารางบ้าง จากท้ายตารางบ้าง ปน ๆ กันไป **โดยหลักสถิติแล้วยิ่งเราสุ่มมามากเท่าไหร่ เราก็จะยิ่งมั่นใจได้ว่าเราได้เห็น pattern ที่เป็นไปได้ทั้งหมดแล้วมากขึ้นนั่นเองครับ** (จะเรียกว่าหลักสถิติหรือดวงก็ได้นะครับ 555)

สำหรับท่านที่ยังไม่เข้าใจว่าจะเอา sample XPath ตรงนี้มาได้อย่างไร **กรุณาอ่าน** [**part 2**](https://dev.to/copypasteengineer/python-web-scraping-part-2-chrome-s-code-inspector-3ok6) **ด่วน ๆ เลยครับ!** หรือถ้ามีข้อสงสัย สามารถคอมเม้นถามด้านล่าง หรือในเพจ [Copy Paste Engineer](https://www.facebook.com/CopyPasteEng/) ได้เลยนะครับ

ด้านล่างนี้เป็นตัวอย่างที่ผม sample ออกมา\


```
 (1) //*[@id="mw-content-text"]/div/table[3]/tbody/tr[1]/td[3]/b/a    (ตาราง 1 บรรทัด 1)
 (2) //*[@id="mw-content-text"]/div/table[3]/tbody/tr[2]/td[3]/b/a    (ตาราง 1 บรรทัด 2)
 (3) //*[@id="mw-content-text"]/div/table[3]/tbody/tr[10]/td[3]/b/a   (ตาราง 1 บรรทัด 10)
 (4) //*[@id="mw-content-text"]/div/table[4]/tbody/tr[3]/td[3]/b/a    (ตาราง 2 บรรทัด 1)
 (5) //*[@id="mw-content-text"]/div/table[4]/tbody/tr[30]/td[2]/b/a   (ตาราง 2 บรรทัด 28)
 (6) //*[@id="mw-content-text"]/div/table[7]/tbody/tr[25]/td[2]/b/a/b (ตาราง 5 บรรทัด 23)
 (7) //*[@id="mw-content-text"]/div/table[10]/tbody/tr[41]/td[2]/b/a  (ตาราง 8 บรรทัดสุดท้าย)
 (8) //*[@id="mw-content-text"]/div/table[24]/tbody/tr[3]/td[3]/a/b   (ตาราง 22 บรรทัด 1)
 (9) //*[@id="mw-content-text"]/div/table[76]/tbody/tr[3]/td[3]/b/a   (ตารางสุดท้าย บรรทัด 1)
(10) //*[@id="mw-content-text"]/div/table[76]/tbody/tr[37]/td[2]/b/a  (ตารางสุดท้าย บรรทัดสุดท้าย)
```

จะเห็นว่า XPath จริง ๆ ของข้อมูลเรา มันไม่ได้ตรงไปตรงมาเสมอไปนะครับ อย่างเช่นตัวเลขของ table และ tr ใน XPath ของแต่ละ sample นั้น ก็อาจจะไม่ได้สัมพันธ์กับเลขตารางหรือเลขบรรทัดของข้อมูลเสมอไป หรือบางทีก็มี element b บ้าง บางทีก็ไม่มี **ซึ่งในเหตุการณ์เช่นนี้สามารถพบเจอได้จริง ในเว็บทั่ว ๆ ไปนะครับ** โดยอาจเกิดจาก developer ที่เขียนโค้ดไม่สัมพันธ์กันเอง หรืออย่างในกรณีของ Wikipedia นี้ คืออาจเกิดจาก user ซึ่งเป็นผู้เขียนบทความได้ใช้โครงสร้างของข้อมูลที่แตกต่างกันนั่นเองครับ

#### **2. สำรวจ XPath**

ในขั้นตอนนี้ เราจะทำการวิเคราะห์ XPath ที่เรา sample ออกมา แล้วก็ตั้งข้อสังเกตุถึง **จุดที่เหมือน** และ **จุดที่แตกต่าง** สำหรับแต่ละตัวอย่างนะครับ ซึ่งอาจจะต้องใช้ความรู้ HTML เล็กน้อยนะครับ แต่จะพยายามอธิบายให้เข้าใจง่ายที่สุดนะครับ

เมื่อเรา sample ออกมาดูเยอะ ๆ แล้วก็จะเห็นครับว่ามันมีส่วนที่เหมือนกันอยู่ คือ ขึ้นต้นด้วย element `<table>`\


```
//*[@id="mw-content-text"]/div/table
```

ซึ่ง `<table>` ก็หมายถึงตัวตารางแต่ละตารางนั่นเองครับ

และจะสังเกตุได้จาก XPath ที่ได้มา ว่าข้อมูลที่เราต้องการ\
**1.** จะเริ่มต้นที่ **ตารางที่ 3** (เพราะในตารางที่ 1 บรรทัดที่ 1 ใช้ `table[3]`)\
**2.** แล้วตามด้วย `<tbody>`\
**3.** ตามด้วย `<tr>` คือแต่ละ row ของ table นั้น ๆ\
**4.** จากนั้นก็ `<td>` คือแต่ละ column ของ row นั้น ๆ ซึ่งอาจจะเป็น **column ที่ 2 หรือ column ที่ 3 ก็ได้** (เช่น sample `(4)` และ `(5)`)\


```
(4) //*[@id="mw-content-text"]/div/table[4]/tbody/tr[3]/td[3]/b/a    (ตาราง 2 บรรทัด 1)
(5) //*[@id="mw-content-text"]/div/table[4]/tbody/tr[30]/td[2]/b/a   (ตาราง 2 บรรทัด 28)
```

**5.** แล้วก็อาจจะมี หรือไม่มี `<b>` ก็ได้ (กรณีที่ไม่มี `<b>` ดู sample ที่ `(8)`)\


```
(8) //*[@id="mw-content-text"]/div/table[24]/tbody/tr[3]/td[3]/a/b   (ตาราง 22 บรรทัด 1)
```

**6.** ตามด้วย `<a>` คือข้อความที่เป็น link\
**7.** แล้วก็อาจจะมี หรือไม่มี `<b>` ก็ได้

นี่ก็เป็นข้อสังเกตุทั้งหมดที่เราได้จาก sample ของเรานะครับ

#### **3. สร้าง XPath (จริง ๆ สักที 555)**

ต่อไป จากข้อสังเกตุทั้ง 7 ข้อ ด้านบน เราก็ได้ทราบแล้วว่า elements ที่เราต้องการ จะมี XPath ลักษณะอย่างไร **ทีนี้เรานำข้อสังเกตุทั้งหมดมารวมกัน เพื่อสร้าง XPath ขึ้นมา 1 อัน ที่สามารถครอบคลุม cases ที่กล่าวมาได้ทั้งหมด!** โดยใช้การเขียน **condition** มาเพื่อช่วยเลือก elements ที่ต้องการนะครับ อันดับแรกก็เริ่มจาก XPath ที่ทุก sample มีร่วมกันก่อน ก็คือ\


```
//*[@id="mw-content-text"]/div/table
```

ทีนี้ ก็จะเริ่มใส่ condition ตามข้อสังเกตุของเราข้างต้นแล้วนะครับ\
**1.** เราต้องการเลือกตารางตั้งแต่ตารางที่ 3 ขึ้นไปเท่านั้น วิธีการคือให้ใส่ condition เข้าไป ว่า **position ของ `<table>` เนี่ย ต้องมากกว่าหรือเท่ากับ 3 ครับ** ซึ่งเขียนเป็น condition ได้ว่า `position() >= 3` ก็จะได้ XPath เป็น\


```
//*[@id="mw-content-text"]/div/table[position() >= 3]
```

**2.** ตามด้วย `<tbody>` และ `<tr>` ก็เติมเข้าไปตรง ๆ ได้เลยครับ\


```
//*[@id="mw-content-text"]/div/table[position() >= 3]/tbody/tr
```

**3.** ตามด้วย `<td>` แต่เราจะเอาเฉพาะ column ที่ 2 หรือ 3 เท่านั้น วิธีหนึ่งที่ทำได้ก็คือ ใส่ condition ว่า **position ของ `<td>` เนี่ย ต้อง >= 2 และ <= 3** ซึ่งเขียน condition ออกมาได้ว่า `2 <= position() and position() <= 3` จึงได้ XPath เป็น\


```
//*[@id="mw-c.../tbody/tr/td[2 <= position() and position() <= 3]
```

**4.** อย่างที่สังเกตุว่าบาง sample ก็มี `<b>` ขึ้นก่อนแล้วตามด้วย `<a>` (`/b/a/`)\
หรือบาง sample ก็เป็น `<a>` ขึ้นก่อน แล้วตามด้วย `<b>` (`/a/b/`)\
แล้วยังมี `<b>` แล้ว `<a>` แล้ว `<b>` อีก (`/b/a/b/`)\
โชคดีที่ใน XPath เราสามารถละได้ โดยการเขียนเป็น `//a//` จะหมายถึงว่าจะมี element อะไรขึ้นก่อน `<a>` ก็ได้ (กี่ elements ก็ได้ หรือจะไม่มีก็ได้) แล้วตามด้วย elements อะไรก็ได้ (กี่ elements ก็ได้ หรือจะไม่มีก็ได้) แล้วตัว engine ก็จะหาทุก ๆ elements ที่เป็นไปได้มาให้เองครับ\


```
//*[@id="mw-c.../tbody/tr/td[2 <= position() and position() <= 3]//a//
```

**note: ซึ่งการละใน XPath นี้ ต้องระวังในการใช้สักเล็กน้อยนะครับ อย่าง `//a//` อาจหมายถึง `/b/a` ก็ได้ หรือ `/b/a/b` ก็ได้ หรืออาจหมายถึง `/b/div/a/div/b/a/b/div` ก็ได้ คือในส่วนที่ละ จะแทนเป็นอะไรก็ได้นั่นเองครับ**

**5.** สุดท้ายตามด้วย `text()` คือต้องการเอาเฉพาะข้อความที่อยู่ใน element นั้นออกมา\


```
//*[@id="mw-c.../tbody/tr/td[2 <= position() and position() <= 3]//a//text()
```

ได้ XPath สุดท้ายเป็น\


```
//*[@id="mw-content-text"]/div/table[position() >= 3]/tbody/tr/td[2 <= position() and position() <= 3]//a//text()
```

ซึ่งเป็น XPath ที่เรามั่นใจว่าและครอบคลุมทุกกรณีที่เรา sample ออกมา และ(หวังว่า)จะครอบคลุมข้อมูลที่เราต้องการจริง ๆ ทั้งหมด

**และเป็น XPath ที่เราก็ได้นำไปใช้จริงแล้วในการ extract&#x20;**_**รายชื่อเทศบาลตำบล**_**&#x20;ใน** [**บทความ part ที่ 1**](https://dev.to/copypasteengineer/python-web-scraping-part-1-python-49ce) **นั่นเองครับ**

## **Summary**

สำหรับการสร้าง XPath นั้น วิธีการที่นำเสนอไปก็เป็นหนึ่งในวิธีที่สามารถทำได้ง่าย ๆ ซึ่งเราจะสามารถมั่นใจได้ในระดับหนึ่งว่า XPath ที่ได้มานั้น **ครอบคลุมข้อมูลส่วนใหญ่ โดยทำการ sample ออกมาให้มากขึ้น และ sample&#x20;**_**ในหลาย ๆ จุดที่แตกต่างกัน กระจาย ๆ กันไป**_ แต่อาจจะต้องอาศัยความรู้ในเรื่อง syntax และคำสั่งของ XPath สักเล็กน้อยสำหรับการเขียน condition เพื่อ select element ต่าง ๆ นะครับ

ซึ่งจริง ๆ แล้ว **ผู้เขียนเองก็ไม่ได้จำคำสั่งของ XPath ได้ทั้งหมดจริง ๆ หรอกครับ** เมื่อจำเป็นต้องใช้ที ก็ search หาจาก internet เอา โดยผู้เขียนพบว่า **เว็บ&#x20;**_**w3school**_**&#x20;ได้ทำรายการของคำสั่งต่าง ๆ พร้อมคำอธิบายเอาไว้ได้ดีและครบถ้วนแล้ว ทุก ๆ ท่านสามารถนำไปใช้เพื่ออ้างอิงขณะที่เขียนได้เลยครับ** [**link นี้เลยครับ**](https://www.w3schools.com/xml/xpath_intro.asp)

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

FB Page: [Copy Paste Engineer](https://www.facebook.com/CopyPasteEng)

Reference : [https://dev.to/copypasteengineer/python-web-scraping-part-3-extract-xpath-18h](https://dev.to/copypasteengineer/python-web-scraping-part-3-extract-xpath-18h)

