ตอนที่แล้วเราพูดถึง Library เจ๋งๆ ไปหลายตัว ในตอนนี้ผมจะขอพูดถึงการใช้ Python ดึงข้อมูลจาก website กันครับ ซึ่งเครื่องมือที่จะใช้ชื่อว่า Beautiful Soup นั่นเอง ซึ่งบทความนี้อาจไม่ค่อยเกี่ยวกับการทำงานใน Excel เท่าไหร่ แต่เรียกว่ามาอุดจุดอ่อนของการใช้ Excel จะดีกว่า เพราะใน Excel จะดึงข้อมูลจากเว็บได้ไม่ค่อยดี แม้จะมีฟังก์ชัน WEBSERVICE, FILTERXML หรือแม้จะใช้ Power Query ก็ตาม ก็ยังไม่ค่อย Work เท่าการใช้ Python
สารบัญ
การเรียกใช้งาน Beautiful Soup
เวลาใช้งาน Beautiful Soup เรามักจะใช้กับ module อีกตัวที่เรียกว่า requests เพื่อจะขอข้อมูลจาก website กลับมา โดยจะถูกใช้ร่วมกันประมาณนี้
import requests
from bs4 import BeautifulSoup
myURL="https://www.thepexcel.com/category/highlights/" #ใส่เว็บที่เราต้องการ
r = requests.get(myURL) #ขอข้อมูลจาก url ที่กำหนดเก็บไว้ใน object r (request)
s = BeautifulSoup(r.content, "html.parser") #แปลงโครงสร้าง html ใน content เข้า object s (soup)
ถ้าลองดูว่าใน s เป็นอะไร มันก็คือโครงสร้าง html ของทั้งเพจนั้น ซึ่งมันมีข้อมูลส่วนที่เราไม่ได้อยากได้อยู่ด้วย ที่นี้เราต้องมาเรียนรู้วิธีเลือกส่วนที่เราต้องการเท่านั้น
วิธีเลือกเอา element ที่ต้องการ
ทีนี้เจ้า Beautiful Soup เนี่ย สามารถดึงข้อมูลเฉพาะส่วนที่เราต้องการจาก html ได้ โดยเราจะต้องเข้าใจก่อนว่าเว็บไซต์ต่างๆ ที่เราเห็นนั้นจะถูกเก็บข้อมูลในลักษณะของภาษา HTML ซึ่งมีลักษณะที่เป็น Tag ลงไปเป็นชั้นๆ โดยจะมีการเปิด Tag และ Tag ทุกครั้ง เช่น
<Tagแม่>
<Tagลูก> Content1 </Tagลูก>
<Tagลูก> Content2 </Tagลูก>
<Tagลูก> Content3 </Tagลูก>
</Tagแม่>
<Tagแม่>
<Tagลูก> Content4 </Tagลูก>
<Tagลูก> Content5 </Tagลูก>
<Tagลูก> Content6 </Tagลูก>
</Tagแม่>
เราสามารถใช้ Beautiful Soup เลือกข้อมูลได้ด้วยวิธีเขียนแบบนี้
s.element1 #เข้าไปใน element1 อันแรก
หรือ
s.find('element1') #เข้าไปใน element1 อันแรก
เช่น
s.Tagแม่ #เข้าไปใน Tagแม่ อันแรก
#จะได้แบบนี้
#<Tagแม่>
# <Tagลูก> Content1 </Tagลูก>
# <Tagลูก> Content2 </Tagลูก>
# <Tagลูก> Content3 </Tagลูก>
#</Tagแม่>
ถ้าจะเข้าไปชั้นที่ลึกขึ้น
s.element1.element2 #เข้าไปใน element1 อันแรก -> element2 อันแรก
หรือ
s.find('element1').find('element2') #เข้าไปใน element1 อันแรก -> element2 อันแรก
เช่น
s.Tagแม่.Tagลูก #เข้าไปใน Tagแม่ อันแรก -> เช้าไป Tag ลูกอันแรกในนั้น
#จะได้แบบนี้
# <Tagลูก> Content1 </Tagลูก>
เช่น ถ้าลองกับเว็บของผม จะเป็นดังนี้
s.body.h2 #ได้ element h2 อันแรกสุด ใน body ของเพจ
s.body.p #ได้ element p อันแรกสุด ใน body ของเพจ
ดึงข้อมูลใน element ออกมา
ถ้าใน element นั้นมีข้อมูลหลายส่วน เช่น ใน h2 ของผมมีข้อมูลเยอะแยะ เราสามารถดึงบางส่วนมากได้ เช่น
myText=s.body.h2.get_text() #ได้ข้อความข้างใน
myLink=s.body.h2.a.get('href') #ได้ link ข้างใน ของ a ตัวแรกสุด ใน h2 แรกสุด
myImageURL=s.body.img.get('src') #ได้ url ของรูปอันแรก ข้างใน body
print(myText)
print(myLink)
print(myImageURL)
วิธีเลือกเอา element ที่ต้องการทั้งหมดทุกตัว
s('element1') #เอา element1 กลับมาทุกตัวที่เจอ
หรือ
s.find_all('element1') #เอา element1 กลับมาทุกตัวที่เจอ
เช่น
s.body.find_all('h2') # ได้ h2 ทุกตัว
เราสามารถเข้าถึง item ย่อยลำดับที่ต้องการได้ ด้วยการใส่เลข index ในวงเล็บเหลี่ยม คล้ายกับ list ปกติเลย (แปลว่าเราวน loop ได้) เช่น
s.body.find_all('h2')[1] # ได้ h2 index ที่ๅ ซึ่งคือตัวที่2
แต่เราไม่สามารถ get_text() ตรงๆ จากการ find_all ได้ มันจะขึ้น error
เราต้องแก้ไขด้วยการ เข้าไป get_text() ในแต่ละ item ของ h2 เลย ด้วยการวน loop เพื่อใช้คำสั่ง get_text() กับ item ข้างในทีละตัวเป็นต้น
for i in s.body.find_all('h2'):
print(i.get_text())
วิธีเลือกเอา element ที่มีลักษณะที่กำหนด
ก่อนหน้านี้ ผมได้ h2 กลับมาเยอะเกินที่ต้องการ เพราะดันได้ h2 ของ widget บทความล่าสุดด้านขวากลับมาด้วย แต่ที่ผมต้องการคือ h2 เฉพาะของหน้าหลักจริงๆ ดังนั้นผมจะต้องใช้วิธีค้นหาเฉพาะตัวที่มีลักษณะที่ผมต้องการเท่านั้น
ด้วยคำสั่ง attrs= dictionary ลักษณะที่ต้องการ
เช่น ผมต้องการ h2 ที่มี class เป็น post-title entry-title is-size-3 เท่านั้น (ซึ่งรู้จากการใช้ google chrome inspect เอา)
ผมก็สามารถเขียนได้ดังนี้
for i in s.body.find_all('h2', attrs={'class':'post-title entry-title is-size-3'}):
#เอาเฉพาะ h2 class ที่ต้องการ
print(i.get_text())
ลองค้นหาข้อมูลใน Pantip Hitz
เดี๋ยวลองดึงข้อมูล topic ในหน้าเว็บของ Pantip Hitz ครับ
import requests
from bs4 import BeautifulSoup
myURL="https://pantip.com/home/hitz" #ใส่เว็บที่เราต้องการ
r = requests.get(myURL) #ขอข้อมูลจาก url ที่กำหนดเก็บไว้ใน object r (request)
s = BeautifulSoup(r.content, "html.parser") #แปลงโครงสร้าง html ใน content เข้า object s (soup)
จะเห็นว่าข้อมูลอยู่ใน tag a ซึ่งอยู่ใน h2 อีกที ดังนั้นเราจะดึงข้อมูลได้โดยการใส่คำสั่งว่า
for i in s.find_all('h2'): #เอาเฉพาะ h2 class ที่ต้องการ
#เอา text ใน a ที่มี class ตามที่ต้องการ
print(i.find('a',attrs={'class':'gtm-hitz-page1'}).get_text())
สมมติว่าผมอยากได้ hashtag ของแต่ละ Topic เราจะดึงข้อมูลยังไงดี? มาดูกัน
ซึ่งก่อนอื่นผมก็ต้องไปหาก่อนว่ามันอยู่ใน Tag อะไรยังไง?
พอไปสำรวจดูพบว่า อยู่ใน a ที่อยู่ใน div class ชื่อว่า pt-list-item__tag อีกที
ดังนั้นถ้าลองดึงข้อมูลมาจะเป็นดังนี้
for i in s.find_all('div',attrs={'class':'pt-list-item__tag'}): #เอาเฉพาะ h2 class ที่ต้องการ
for tag in i.find_all('a'):
print(tag.get_text())
จะพบว่าแม้จะดึงข้อมูลมาได้ แต่มันสะเปะสะปะมากเลย และเกิดปัญหาตามมาคือ ผมไม่รู้ว่ารู้ว่า Tag ไหนเป็นของ Topic ไหน
ทีนี้จะทำไงดี?
การจัดกลุ่มข้อมูลที่ดึงมาได้
ผมคิดว่าโครงสร้างของข้อมูลที่น่าจะพอใช้เก็บข้อมูล List ที่มีจำนวนไม่แน่นอนได้ และอ้างอิงได้ค่อนข้างง่าย น่าจะเหมาะที่จะเก็บเป็น dictionary แบบซ้อนกัน ซึ่งเดี๋ยวผมจะเอาไปแปลงเป็น Data Frame ภายหลังก็ได้
ก่อนจะทำการดึงข้อมูลหลายๆ เรื่องพร้อมกัน เราจะต้องทำให้มั่นใจว่า topic และ tag ที่ดึงมามันสอดคล้องกัน โดยไม่ใช่ดึงรอบแรกได้ข้อมูล 10 อันมาชุดนึง แต่พอดึงอีกที ได้ข้อมูล 10 อันที่ไม่ตรงกัน (เช่น มีการอัปเดทข้อมูลชุดใหม่มา ลำดับอาจจะเลื่อนได้) ดังนั้นต้องไปเช็คดูก่อนว่าจริงๆ แล้วทั้งคู่อยู่ใน item แม่อะไรกันแน่ แล้วเข้าไปไล่ดึงสิ่งที่ต้องการใน item นั้นๆ มาให้ครบทีเดียว
ปรากฏว่าจริงๆ แล้วทั้ง Topic และ Tag อยู่ใน li ที่มีชื่อ class ว่า pt-list-item ทั้งคู่ ดังนั้น ผมจะต้องวน loop ตามแต่ละ item ของ li แต่ละตัว แล้วภายในนั้นผมค่อยเอา Topic(H2) ในนั้น (มีอันเดียว) และ Tag (a) ที่ต้องการมาให้หมด(มีหลายอัน)
โดยผมจะดึงข้อมูล มาใส่ใน Dictionary เปล่าที่สร้างไว้ จนครบทุก item
pantipDict={} #สร้าง Dict เปล่าๆ
Myli=s.find_all('li',attrs={'class':'pt-list-item'})
for num in range(len(Myli)):
tempDict={} #สร้าง dict ชั่วคราว
topicText=Myli[num].find('h2').get_text()
tempDict['Topic']=topicText
tagList=Myli[num].find('div',attrs={'class':'pt-list-item__tag'})
tempList=[]
for tag in tagList.find_all('a'): #หา tag แต่ละตัว
tempList.append(tag.get_text())
tempDict['Tag']=tempList #ใส้ tempList เข้าไปใน dict ตัวจริง
pantipDict[num]=tempDict #ใส้ tempdict เข้าไปใน dict ตัวจริง
pantipDict
แปลงเป็น Data Frame และ Export เป็น CSV
ถ้าหากเรารู้สึกว่า Dictionary มันดูยาก เราก็สามารถ convert ให้กลายเป็น Data Frame ได้แบบง่ายๆ เลย ดังนี้
import pandas as pd
pantipDF=pd.DataFrame.from_dict(pantipDict, orient='index')
pantipDF
และถ้าจะ Export Data Frame เป็น csv ก็สามารถทำได้ง่ายๆ เช่นกัน ดังนี้
from google.colab import files
pantipDF.to_csv('pantipHitz.csv')
files.download('pantipHitz.csv')
จบตอน
หวังว่าเพื่อนๆ จะพอเห็นภาพการทำ Web Scraping แบบเบื้องต้นด้วย Beautiful Soup นะครับ การเขียน code บางอันในบทความนี้ผมอาจไม่ได้ใช้วิธีที่เหมาะสมที่สุด ถ้าอย่างไรใครใช้ Python เป็นช่วยแนะนำผมได้เลยนะครับ
สารบัญ Series Python
- หัดเขียนโปรแกรม Python สำหรับคนเป็น Excel มาก่อน : ตอนที่ 1
- หัด Python สำหรับคนเป็น Excel : ตอนที่ 2 – ประเภทข้อมูล (Data Types)
- หัด Python สำหรับคนเป็น Excel : ตอนที่ 3 – การวน Loop และ เงื่อนไข if
- หัด Python สำหรับคนเป็น Excel : ตอนที่ 4 – การทำงานกับ String และ List
- หัด Python สำหรับคนเป็น Excel : ตอนที่ 5 – การสร้างฟังก์ชันขึ้นมาใช้เอง (Function)
- หัด Python สำหรับคนเป็น Excel : ตอนที่ 6 – การเรียกใช้ Module / Packages เจ๋งๆ ที่มีคนสร้างไว้แล้ว
- หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup
- แนวทางการใช้ Python ใน Power BI
- หัด Python สำหรับคนเป็น Excel : ตอนที่ 8 – การสร้างกราฟด้วย Matplotlib
- รวม Link สอน Python / Programming / AI/ Machine Learning แบบฟรีๆ
- สอนใช้ Python ใน Excel ตอนที่ 1 : ลองใช้ครั้งแรก
- สอนใช้ Python ใน Excel ตอนที่ 2 : List, Loop, Condition
- สอนใช้ Python ใน Excel ตอนที่ 3 : Regular Expression (RegEx)
- สอนใช้ Python ใน Excel ตอนที่ 4 : สร้างฟังก์ชันใช้เอง
- สอนใช้ Python ใน Excel ตอนที่ 5 : สร้างกราฟ Visualization เบื้องต้นด้วย Matplotlib
- การวิเคราะห์ข้อมูลเบื้องต้นด้วย Python: เริ่มต้นด้วย Pandas และ Matplotlib
- เพิ่มยอดขายด้วย Market Basket Analysis : วิเคราะห์คู่สินค้าขายดีด้วย Python