python web scraping beautiful soup

หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup

ตอนที่แล้วเราพูดถึง 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 ของทั้งเพจนั้น ซึ่งมันมีข้อมูลส่วนที่เราไม่ได้อยากได้อยู่ด้วย ที่นี้เราต้องมาเรียนรู้วิธีเลือกส่วนที่เราต้องการเท่านั้น

Beautiful Soup

วิธีเลือกเอา 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 ของเพจ
หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 1

ดึงข้อมูลใน 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)
หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 2

วิธีเลือกเอา element ที่ต้องการทั้งหมดทุกตัว

s('element1')   #เอา element1 กลับมาทุกตัวที่เจอ

หรือ

s.find_all('element1')  #เอา element1 กลับมาทุกตัวที่เจอ

เช่น

s.body.find_all('h2')  # ได้ h2 ทุกตัว
หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 3

เราสามารถเข้าถึง item ย่อยลำดับที่ต้องการได้ ด้วยการใส่เลข index ในวงเล็บเหลี่ยม คล้ายกับ list ปกติเลย (แปลว่าเราวน loop ได้) เช่น

s.body.find_all('h2')[1]  # ได้ h2 index ที่ๅ ซึ่งคือตัวที่2
หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 4

แต่เราไม่สามารถ get_text() ตรงๆ จากการ find_all ได้ มันจะขึ้น error

หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 5

เราต้องแก้ไขด้วยการ เข้าไป get_text() ในแต่ละ item ของ h2 เลย ด้วยการวน loop เพื่อใช้คำสั่ง get_text() กับ item ข้างในทีละตัวเป็นต้น

for i in s.body.find_all('h2'):
  print(i.get_text())
หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 6

วิธีเลือกเอา element ที่มีลักษณะที่กำหนด

ก่อนหน้านี้ ผมได้ h2 กลับมาเยอะเกินที่ต้องการ เพราะดันได้ h2 ของ widget บทความล่าสุดด้านขวากลับมาด้วย แต่ที่ผมต้องการคือ h2 เฉพาะของหน้าหลักจริงๆ ดังนั้นผมจะต้องใช้วิธีค้นหาเฉพาะตัวที่มีลักษณะที่ผมต้องการเท่านั้น

ด้วยคำสั่ง attrs= dictionary ลักษณะที่ต้องการ

เช่น ผมต้องการ h2 ที่มี class เป็น post-title entry-title is-size-3 เท่านั้น (ซึ่งรู้จากการใช้ google chrome inspect เอา)

หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 7

ผมก็สามารถเขียนได้ดังนี้

for i in s.body.find_all('h2', attrs={'class':'post-title entry-title is-size-3'}): 
#เอาเฉพาะ h2 class ที่ต้องการ
  print(i.get_text())
หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 8

ลองค้นหาข้อมูลใน 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)
หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 9

จะเห็นว่าข้อมูลอยู่ใน 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()) 
หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 10

สมมติว่าผมอยากได้ hashtag ของแต่ละ Topic เราจะดึงข้อมูลยังไงดี? มาดูกัน

ซึ่งก่อนอื่นผมก็ต้องไปหาก่อนว่ามันอยู่ใน Tag อะไรยังไง?

หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 11

พอไปสำรวจดูพบว่า อยู่ใน 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()) 
หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 12

จะพบว่าแม้จะดึงข้อมูลมาได้ แต่มันสะเปะสะปะมากเลย และเกิดปัญหาตามมาคือ ผมไม่รู้ว่ารู้ว่า Tag ไหนเป็นของ Topic ไหน

ทีนี้จะทำไงดี?

การจัดกลุ่มข้อมูลที่ดึงมาได้

ผมคิดว่าโครงสร้างของข้อมูลที่น่าจะพอใช้เก็บข้อมูล List ที่มีจำนวนไม่แน่นอนได้ และอ้างอิงได้ค่อนข้างง่าย น่าจะเหมาะที่จะเก็บเป็น dictionary แบบซ้อนกัน ซึ่งเดี๋ยวผมจะเอาไปแปลงเป็น Data Frame ภายหลังก็ได้

ก่อนจะทำการดึงข้อมูลหลายๆ เรื่องพร้อมกัน เราจะต้องทำให้มั่นใจว่า topic และ tag ที่ดึงมามันสอดคล้องกัน โดยไม่ใช่ดึงรอบแรกได้ข้อมูล 10 อันมาชุดนึง แต่พอดึงอีกที ได้ข้อมูล 10 อันที่ไม่ตรงกัน (เช่น มีการอัปเดทข้อมูลชุดใหม่มา ลำดับอาจจะเลื่อนได้) ดังนั้นต้องไปเช็คดูก่อนว่าจริงๆ แล้วทั้งคู่อยู่ใน item แม่อะไรกันแน่ แล้วเข้าไปไล่ดึงสิ่งที่ต้องการใน item นั้นๆ มาให้ครบทีเดียว

หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 13

ปรากฏว่าจริงๆ แล้วทั้ง 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
หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 14

แปลงเป็น Data Frame และ Export เป็น CSV

ถ้าหากเรารู้สึกว่า Dictionary มันดูยาก เราก็สามารถ convert ให้กลายเป็น Data Frame ได้แบบง่ายๆ เลย ดังนี้

import pandas as pd
pantipDF=pd.DataFrame.from_dict(pantipDict, orient='index')
pantipDF
หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 15

และถ้าจะ Export Data Frame เป็น csv ก็สามารถทำได้ง่ายๆ เช่นกัน ดังนี้

from google.colab import files
pantipDF.to_csv('pantipHitz.csv') 
files.download('pantipHitz.csv')
หัด Python สำหรับคนเป็น Excel : ตอนที่ 7 – Web Scraping ด้วย Beautiful Soup 16

จบตอน

หวังว่าเพื่อนๆ จะพอเห็นภาพการทำ Web Scraping แบบเบื้องต้นด้วย Beautiful Soup นะครับ การเขียน code บางอันในบทความนี้ผมอาจไม่ได้ใช้วิธีที่เหมาะสมที่สุด ถ้าอย่างไรใครใช้ Python เป็นช่วยแนะนำผมได้เลยนะครับ

สารบัญ Series Python