Python程式設計
Lesson 14: 網路爬蟲
Last Updated: 2022/12/27
網路爬蟲
簡介
三步驟完成網頁大數據分析
網頁爬蟲
web spider?
web crawler?
web scraper?
關於網路爬蟲(1/3)
爬取網頁(連結) ➠ 製作網頁索引
Web crawling
應用:搜尋引擎
➊ Spider程式爬取網站內容
➋ 為爬取的網頁建立索引
➌使用者搜尋關鍵字
➍接收結果
關於網路爬蟲(2/3)
爬取網頁內容 ➠ 轉成結構化資料
Web scraping
目的:Make smarter decisions
價格追蹤
商品評價
潛在客戶
2.1 遍訪連結
建立待訪堆疊
1. 從一個網頁開始
2.2 擷取內容
3. 輸出結果(至檔案)
圖片來源1
圖片來源2
取得HTML回應碼
解析超連結
解析其他HTML元素
資料輸出
資料清理
關於網路爬蟲(3/3)
實作爬蟲程式
實作Web Scraper?(1/4)
能力1: 取得HTML回應碼?了解HTTP協定
網址(http request)
瀏覽器
Web 伺服器
HTML回應碼(http response)
實作Web Scraper?(2/4)
能力2: 解析超連結、其他HTML元素、網頁內容 ➠
HTML Parser(解析器)
符號化
建立DOM樹
關於HTML元素(1/3)
以p元素為例
元素
內容
開始標籤
結束標籤
屬性名稱
屬性值
找出超連結元素A,屬性href的值 ➠ 待訪網址
關於HTML元素(2/3)
【任務】建立待訪網址串列:
1.找到所有超連結元素A,讀取屬性href的值
2.讀取該A元素的內容(顯示於網頁的文字)
<div id="mw-pages">
<h2>類別”台灣球員“中的頁面</h2>
<p>這個分類中有以下的 200 個頁面,共有 15,436 個頁面。</p>(先前200) (
<a href="/wiki/index.php?title=%E5%88%86%E9%A1%9E:%E5%8F%B0%E7%81%A3%E7%90%83%E5%93%A1"
title="分類:台灣球員">之後200</a>)
<div lang="zh-tw" dir="ltr" class="mw-content-ltr">
<table style="width: 100%;">
<tr style="vertical-align: top;">
<td style="width: 33.3%;">
<h3>丁</h3>
<ul>
<li><a href="/wiki/index.php/%E4%B8%81%E4%B8%96%E5%81%89" title="丁世偉">丁世偉</a>
</li>
<li><a href="/wiki/index.php/%E4%B8%81%E4%B8%96%E6%9D%B0" title="丁世杰">丁世杰</a>
</li>
</ul>
<h3>丘</h3>
<ul>
<li><a href="/wiki/index.php/%E4%B8%98%E6%98%8C%E6%A6%AE" title="丘昌榮">丘昌榮</a>
</li>
</ul>
<h3>中</h3>
</td>
</tr>
</table>
</div>
</div>
關於HTML元素(3/3)
內文:之後200 網址:http://twbsball.dils.tku.edu.tw/wiki/index.php/%E5%88%86%E9%A1%9E:....
球員:丁世偉 網址:http://twbsball.dils.tku.edu.tw/wiki/index.php/%E4%B8%81%E4%B8%96%E5%81%89
球員:丁世杰 網址:http://twbsball.dils.tku.edu.tw/wiki/index.php/%E4%B8%81%E4%B8%96%E6%9D%B0
球員:丘昌榮 網址:http://twbsball.dils.tku.edu.tw/wiki/index.php/%E4%B8%98%E6%98%8C%E6%A6%AE
【任務】建立待訪網址串列:
1.找到所有超連結元素A,讀取屬性href的值
2.讀取該A元素的內容(顯示於網頁的文字)
...
<a href="/wiki/index.php/%E4%B8%98%E6%98%8C%E6%A6%AE" title="丘昌榮">丘昌榮</a>
...
球員:丘昌榮 網址:http://twbsball.dils.tku.edu.tw/wiki/index.php/%E4%B8%98%E6%98%8C%E6%A6%AE
【問題】
1. 超連結內文:語意不同
2. href有「絕對」、「相對」路徑之分
相對路徑, 不完整
補成絕對路徑
實作Web Scraper?(3/4)
能力2: 解析超連結、其他HTML元素、網頁內容
Parser(解析器)
能力1: 取得HTML回應碼?了解HTTP協定
http連線功能
➠
圖片來源1
實作Web Scraper?(4/4)
能力3: 資料清理、資料輸出 ➠
資料處理工具
2.1 遍訪連結
建立待訪堆疊
1. 從一個網頁開始
2.2 擷取內容
3. 輸出結果(至檔案)
圖片來源1
圖片來源2
取得HTML回應碼
解析超連結
解析其他HTML元素
資料輸出
資料清理
網路爬蟲套件: Selenium
能力2: 解析網頁內容(Parser)
能力1: HTTP連線功能
安裝Selenium
pip install -U selenium
❶ 在主控台安裝selenium:
pip3 install -U selenium
或
安裝Selenium
❷ 下載瀏覽器驅動程式
確認你的Chrome版本
新版的Selenium 4.6.0後會自動管理 WebDriver(不需下載驅動程式)
安裝Selenium
❸ 設定Path環境變數
輸入d:\drivers 或其他資料夾
安裝Selenium
❹ 將驅動程式解壓縮至d:\drivers
安裝Selenium
❺ 測試環境
from selenium import webdriver
從selenium模組引入webdriver功能
# 1. 引入webdriver功能
from selenium import webdriver
# 2. 設定驅動程式路徑
dirverPath = 'D:\drivers\chromedriver.exe'
# 3. 建立webdriver物件
browser = webdriver.Chrome(dirverPath)
print(type(browser))
建立webdriver物件, 變數名為browser
安裝Selenium
❺ 測試環境(4.6.0之後)
from selenium import webdriver
從selenium模組引入webdriver功能
# 1. 引入webdriver功能
from selenium import webdriver
# 不須設定驅動程式路徑
# 2. 建立webdriver物件
browser = webdriver.Chrome()
print(type(browser))
建立webdriver物件, 變數名為browser
selenium功能:擷取網頁
get()函式
from selenium import webdriver
import time
# 目標網站 URL
url = "https://www.au.edu.tw" # 替換為目標網站 URL
# 啟動瀏覽器
driver = webdriver.Chrome()
try:
# 開啟網站
driver.get(url)
time.sleep(2) # 等待頁面載入
except Exception as e:
print(e)
呼叫get()函式,可連上網頁
尋找HTML元素
獲取HTML元素內容的屬性與方法
HTML編碼
元素
開始標籤
結束標籤
內容
屬性名稱
屬性值
tag_name:元素名稱。
text:元素內容。
location:這是字典,內含有x和y鍵值,表示元素在頁面上的座標。
clear( ):可以刪除在文字(text)欄位或文字區域(textarea)欄位的文字。
get_attribute(name):可以獲得這個元素name屬性的值。
is_displayed( ):如果元素可以看到傳回True,否則傳回False。
is_enabled( ):如果元素是可以立即使用則傳回True,否則傳回False。
is_selected( ):如果元素的核取方塊有勾選則傳回True,否則傳回False。
# 1. 引入webdriver功能
from selenium import webdriver
from selenium.webdriver.common.by import By
# 2. 設定驅動程式路徑
dirverPath = 'D:\drivers\chromedriver.exe'
# 3. 建立webdriver物件
browser = webdriver.Chrome(dirverPath)
# 4. 設定網址
url = 'http://www.au.edu.tw'
# 5. 取得網頁
browser.get(url) # 網頁下載至瀏覽器
tag = browser.find_element(By.ID,'main')
print(tag.tag_name)
發生異常狀況(找不到指定的元素)...
main找不到
NoSuchElementException
# 1. 引入webdriver功能
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
# 3. 建立webdriver物件
browser = webdriver.Chrome()
# 4. 設定網址
url = 'http://www.au.edu.tw'
# 5. 取得網頁
try:
browser.get(url) # 網頁下載至瀏覽器
time.sleep(2)
tag = browser.find_element(By.ID,'main')
print(tag.tag_name)
except:
print('沒有找到相符的元素')
應以try...except處理異常狀況
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
# 目標網站 URL
url = 'https://tw.dictionary.search.yahoo.com/'
# 啟動瀏覽器
driver = webdriver.Chrome()
try:
# 開啟網站
driver.get(url)
time.sleep(2) # 等待頁面載入
# 抓取網頁標題
title = driver.title
print("網頁標題:", title)
# 抓取主要段落內容 (假設是第一個 <p>)
paragraph = driver.find_element(By.TAG_NAME, "p") # 替換為適合的選擇器
print("主要段落內容:", paragraph.text)
# 如果有多個段落,抓取所有段落內容
all_paragraphs = driver.find_elements(By.TAG_NAME, "p")
for i, p in enumerate(all_paragraphs, start=1):
print(f"段落 {i}: {p.text}")
except Exception as e:
print(f'爬取錯誤:{e}')
finally:
# 關閉瀏覽器
driver.quit()
1. 網頁標題:driver.title
2. 透過 By.TAG_NAME 找到第一個 <p> 標籤的內容
3. 利用 find_elements 取得所有 <p> 標籤
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
# 目標網站 URL
url = "https://news.google.com/home?hl=zh-TW&gl=TW&ceid=TW:zh-Hant"
# 啟動瀏覽器
driver = webdriver.Chrome()
try:
# 開啟網站
driver.get(url)
time.sleep(2)
# 抓取所有 <a> 標籤
links = driver.find_elements(By.TAG_NAME, "a")
print("抓取所有超連結:")
for i, link in enumerate(links, start=1):
href = link.get_attribute("href") # 獲取 href 屬性
if href: # 過濾掉空的連結
print(f"{i}. {href}")
except Exception as e:
print(f'擷取錯誤:{e}')
finally:
# 關閉瀏覽器
driver.quit()
範例: 抓取所有超連結
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
url = "https://news.google.com/home?hl=zh-TW&gl=TW&ceid=TW:zh-Hant"
try:
driver = webdriver.Chrome()
driver.get(url)
time.sleep(2)
#links = driver.find_elements(By.TAG_NAME, "a")
divs = driver.find_elements(By.CLASS_NAME, 'EctEBd')
for i,div in enumerate(divs, start=1):
print(f'名稱:{div.text}')
link = div.find_element(By.TAG_NAME, 'a')
href = link.get_attribute("href")
if href:
print(f'第{i}個連結: {href}')
print(f'超連結內文:{link.text}')
except:
print("Error")
範例:抓google新聞分類標題
使用css class name : 'EctEBd'
應用: 印出所有圖片網址
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
# 目標網站 URL
url = "https://news.google.com/home?hl=zh-TW&gl=TW&ceid=TW:zh-Hant"
# 啟動瀏覽器
driver = webdriver.Chrome()
try:
# 開啟網站
driver.get(url)
time.sleep(2)
# 抓取所有 <img> 標籤
images = driver.find_elements(By.TAG_NAME, "img")
print("抓取所有圖片網址:")
for i, img in enumerate(images, start=1):
src = img.get_attribute("src") # 獲取 src 屬性
if src: # 過濾掉空的圖片連結
print(f"{i}. {src}")
except Exception as e:
print(f'擷取錯誤:{e}')
finally:
# 關閉瀏覽器
driver.quit()
範例: ul, li清單項目
from selenium import webdriver
from selenium.webdriver.common.by import By
# 初始化 WebDriver(這裡以 Chrome 為例)
driver = webdriver.Chrome()
# 打開目標網頁
driver.get('你的目標網頁URL')
# 定位到 <ul> 標籤,使用 class 名稱
ul_element = driver.find_element(By.CLASS_NAME, '你的ul標籤class名稱')
# 如果有多個 class,可以使用 CSS 選擇器
# ul_element = driver.find_element(By.CSS_SELECTOR, 'ul.第1個class名稱.第2個class名稱')
# 獲取 <ul> 標籤下的所有 <li> 元素
li_elements = ul_element.find_elements(By.TAG_NAME, 'li')
# 遍歷並印出每個 <li> 元素的文本內容和屬性
for li in li_elements:
print(li.text)
print(li.get_attribute('data-value'))
# 關閉 WebDriver
driver.quit()
網路爬蟲實作
☛ 單一頁面版:使用 Selenium
☛ 多頁面版:先收集相同類型網頁的「連結」
單一網頁版
範例網頁
電影片名
年份、長度
爛蕃茄新鮮度
關於這部電影
價格
內容分級
評分、評論
其他(不一定有):
準備工作:找出解析的關鍵詞
第一類:HTML標籤
按F12 叫出「開發人員工具」
要擷取的部份
對應的HTML碼
h1
準備工作:找出解析的關鍵詞
第一類:HTML標籤
from selenium.webdriver.common.by import By
# 找出所有h1
h1tags = browser.find_elements(By.TAG_NAME, 'h1')
使用 find_elements() 找出所有;或 find_element() 找第一個
# 印出所有h1標籤的內文
for h1 in h1_tag:
print(h1.text)
By.TAG_NAME: HTML標籤類
.text欄位
關於HTML元素
以p元素為例
元素
內容
開始標籤
結束標籤
屬性名稱
屬性值
.text
準備工作:找出解析的關鍵詞
第一類:HTML標籤
from selenium import webdriver # 引入webdriver'
from selenium.webdriver.common.by import By
import time
# 版本:0.1
#driverPath = 'C:/DRIVERS/chromedriver.exe'
# 連線到網頁url, 回傳解析結果
def get_browser(url):
browser = webdriver.Chrome()
browser.get(url)
time.sleep(3)
return browser
# 主程式
電影資料={} # 所有欄位存放的字典
url = 'https://play.google.com/store/movies/details/Minions_The_Rise_of_Gru?id=xXdWrJF8KPQ.P&hl=zh_TW&gl=SI'
#
browser = get_browser(url) # 連結網頁, 解析網頁內容
# 電影片名
h1_tag = browser.find_element(By.TAG_NAME, 'h1') # 找第一個h1
電影片名 = h1_tag.text # 取出文字
電影資料['電影片名'] = 電影片名 # 存到字典
準備工作:找出解析的關鍵詞
第二類:CLASS (CSS樣式)
要擷取的部份
對應的HTML碼
div
tv4jIf
class需注意指定的名稱能否抓到要的資訊
準備工作:找出解析的關鍵詞
第二類:CLASS (CSS樣式)
from selenium.webdriver.common.by import By
# 找第一個class名稱叫做'tv4jIf'的標籤
div_year = browser.find_element(By.CLASS_NAME,'tv4jIf')
使用 find_elements() 找出所有;或 find_element() 找第一個
# 印出該標籤的內文
print(div_year.text)
By.CLASS_NAME: CLASS css樣式類
2022年 • 87 分鐘
年份
電影長度
2個欄位
使用字串split()功能切成兩段
str1 = 'bear, cat, dog, elephant'
arr = str1.split(',') #以逗點切割->串列['bear', ' cat', ' dog', ' elephant']
for item in arr:
print(item.strip()) # 去掉頭尾的空白字元'\t','\n','\r'
字串切割split()範例
div_year = browser.find_element(By.CLASS_NAME,'tv4jIf')
tokens = div_year.text.split('•')
for item in tokens:
print(item.strip())
年份/ 電影長度
準備工作:找出解析的關鍵詞
第二類:CLASS (CSS樣式)
# 以上省略
# 版本:0.3
# 主程式
電影資料={} # 所有欄位存放的字典
url = 'https://play.google.com/store/movies/details/Minions_The_Rise_of_Gru?id=xXdWrJF8KPQ.P&hl=zh_TW&gl=SI'
#
browser = get_browser(url) # 連結網頁, 解析網頁內容
# 電影片名
h1_tag = browser.find_element(By.TAG_NAME, 'h1') # 找第一個h1
電影片名 = h1_tag.text # 取出文字
電影資料['電影片名'] = 電影片名 # 存到字典
# 年份/電影長度
div_year = browser.find_element(By.CLASS_NAME,'tv4jIf')
for item in div_year.text.split('•'):
if '年' in item:
電影資料['年份'] = item
else:
電影資料['電影長度'] = item
print(電影資料)
準備工作:找出解析的關鍵詞
第二類:CLASS (CSS樣式)
# 以上省略
# 版本:0.5
# 主程式
電影資料={} # 所有欄位存放的字典
url = 'https://play.google.com/store/movies/details/Minions_The_Rise_of_Gru?id=xXdWrJF8KPQ.P&hl=zh_TW&gl=SI'
#
# ...略...
# 爛蕃茄新鮮度
fresh_tag = browser.find_element(By.CLASS_NAME, 'ClM7O')
電影資料['新鮮度'] = fresh_tag.text
# 價格
price_tag = browser.find_element(By.CLASS_NAME, 'u4ICaf')
電影資料['價格'] = price_tag.text
print(電影資料)
準備工作:找出解析的關鍵詞
第三類:XPATH (節點定位語法)
要擷取的部份
對應的HTML碼
div
data-g-id
標籤內特定的屬性名稱
準備工作:找出解析的關鍵詞
第三類:XPATH (節點定位語法)
from selenium.webdriver.common.by import By
# 找第一個class名稱叫做'tv4jIf'的標籤
about = browser.find_element(By.XPATH, '//div[@data-g-id="description"]')
使用 find_elements() 找出所有;或 find_element() 找第一個
//div[@data-g-id="description"]
By.XPATH: 節點定位
//div[...]
//找到某個div(相對路徑)
data-g-id屬性值是...的標籤
[@data-g-id="..."]
符號化
建立DOM樹
XPATH: 走訪DOM樹節點
準備工作:找出解析的關鍵詞
第三類:XPATH (節點定位語法)
# 以上省略
# 版本:0.5
# 主程式
電影資料={} # 所有欄位存放的字典
url = 'https://play.google.com/store/movies/details/Minions_The_Rise_of_Gru?id=xXdWrJF8KPQ.P&hl=zh_TW&gl=SI'
#
# ...略...
# # 關於這部電影
about = browser.find_element(By.XPATH, '//div[@data-g-id="description"]')
電影資料['關於'] = about.text
print(電影資料)
from selenium import webdriver # 引入webdriver'
from selenium.webdriver.common.by import By
import time
# 版本:1.0
# 連線到網頁url, 回傳解析結果
def get_browser(url):
browser = webdriver.Chrome()
browser.get(url)
time.sleep(3)
return browser
# 主程式
電影資料={} # 所有欄位存放的字典
url = 'https://play.google.com/store/movies/details/Minions_The_Rise_of_Gru?id=xXdWrJF8KPQ.P&hl=zh_TW&gl=SI'
#
browser = get_browser(url) # 連結網頁, 解析網頁內容
# 電影片名
h1_tag = browser.find_element(By.TAG_NAME, 'h1') # 找第一個h1
電影片名 = h1_tag.text # 取出文字
電影資料['電影片名'] = 電影片名 # 存到字典
# 年份/電影長度
div_year = browser.find_element(By.CLASS_NAME,'tv4jIf')
for item in div_year.text.split('•'):
if '年' in item:
電影資料['年份'] = item
else:
電影資料['電影長度'] = item
# 爛蕃茄新鮮度 itemprop="tomatoMeter"
# 分級 itemprop="contentRating"
# try:
ft = browser.find_element(By.XPATH,"//span[@itemprop='tomatoMeter']")
電影資料['新鮮度'] = ft.text
print('沒有爛番茄指數')
電影資料['新鮮度'] = ''
# 價格
price_tag = browser.find_element(By.CLASS_NAME, 'u4ICaf')
電影資料['價格'] = price_tag.text
# # 關於這部電影
about = browser.find_element(By.XPATH, '//div[@data-g-id="description"]')
電影資料['關於'] = about.text
# 內容分級
try:
grade_tag = browser.find_element(By.XPATH, '//div[@class="xg1aie"]')
電影資料['分級'] = grade_tag.text
except:
print('找不到內容分級')
print(電影資料)
單一網頁完整版
收集網頁連結
多網頁的準備工作
範例網頁
要擷取的部份
對應的HTML碼
class="zuJxTd"
擷取區段的關鍵詞
先用find_elements測試,同樣的class有兩個;第1個是要擷取的內容
擷取區段的關鍵詞
# 找到new movies 的 div class
list_tag= browser.find_element(By.CLASS_NAME, 'zuJxTd')
1. 先找到區段: 包含很多影片的標籤
2. 進一步觀察擷取每一部影片的關鍵詞
要擷取的部份
role="listitem"
步驟1結果
擷取區段的關鍵詞
# 找到new movies 的 div class
list_tag= browser.find_element(By.CLASS_NAME, 'zuJxTd')
# 在裡面找出所有role = listitem, 注意XPath最前面的. 代表從目前的層級,而非從root層級
item_tags = list_tag.find_elements(By.XPATH,'.//div[@role="listitem"]')
for item in item_tags:
print(item) # 但內容還包含很多HTML標籤
2. 進一步觀察擷取每一部影片的關鍵詞
item已經包含所需要的「連結」、「片名」、「價格」
3. 再觀察如何擷取所需欄位
擷取區段的關鍵詞
3. 再觀察如何擷取所需欄位
步驟1結果
步驟2結果
a標籤的href屬性
for item in item_tags:
a_tag = item.find_element(By.TAG_NAME, 'a')
href = a_tag.get_attribute('href') # 讀取href屬性
擷取區段的關鍵詞
3. 再觀察如何擷取所需欄位
電影名稱class="Epkrse"
for item in item_tags:
name = a_tag.find_element(By.CLASS_NAME,"Epkrse") # 找岀電影名
price = a_tag.find_element(By.CLASS_NAME, "LrNMN") # 找出價格
價格
class="LrNMN"
from selenium import webdriver # 引入webdriver'
from selenium.webdriver.common.by import By
import time
import csv
import os
driverPath = 'C:/DRIVERS/chromedriver.exe'
def get_browser(url):
browser = webdriver.Chrome()
browser.get(url)
time.sleep(3)
return browser
url = 'https://play.google.com/store/movies?hl=zh_TW&gl=SI'
browser = get_browser(url)
# 找到new movies 的 div class
list_tag= browser.find_element(By.CLASS_NAME, 'zuJxTd')
# 在裡面找出所有role = listitem, 注意XPath最前面的. 代表從目前的層級,而非從root層級
item_tags = list_tag.find_elements(By.XPATH,'.//div[@role="listitem"]')
# 將連結存起來
links = []
names = []
prices = []
for item in item_tags:
a_tag = item.find_element(By.TAG_NAME, 'a')
href = a_tag.get_attribute('href') # 讀取href屬性
name = a_tag.find_element(By.CLASS_NAME,"Epkrse") # 找岀電影名
price = a_tag.find_element(By.CLASS_NAME, "LrNMN") # 找出價格
links.append(href)
names.append(name.text)
prices.append(price.text)
print(f'{href},{name.text},{price.text}')
# 輸出CSV檔, 以'\t'間隔 三個欄位
fn = os.path.dirname(__file__)+'/out_links.csv'
with open(fn, mode='w+', encoding='utf-8', newline='') as cf:
cf_writer = csv.writer(cf, delimiter='\t')
cf_writer.writerow(['片名','網址','價格'])
for row in zip(names, links, prices): # zip是將三個串列'縫'在一起
cf_writer.writerow(row)
cf.close()
收集連結完整版(v1)
輸出csv至out_links.csv
# 1. 引入webdriver功能
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
import page_parse
import csv
def get_browser(url):
browser = webdriver.Chrome()
browser.get(url)
time.sleep(1)
return browser
# 主程式
url = 'https://play.google.com/store/movies/category/MOVIE?hl=zh_TW&gl=SI'
browser = get_browser(url)
# 找第一個class是ftgkle的段落
list_tag = browser.find_element(By.CLASS_NAME, 'ftgkle')
list_items = list_tag.find_elements(By.XPATH, './/div[@role="listitem"]')
print(f'找到幾部:{len(list_items)}')
movies=[] # 所有的電影
for item in list_items:
movie={} # 一部電影, 有四個欄位
# 找超連結a
link = item.find_element(By.TAG_NAME, 'a')
href = link.get_attribute('href')
movie['href'] = href # 超連結欄位
# 找圖片img
image = item.find_element(By.TAG_NAME, 'img')
src = image.get_attribute('src')
movie['src'] = src # 圖片網址欄位
# 電影片名
title = item.find_element(By.CLASS_NAME, 'Epkrse')
movie['title'] = title.text # 片名欄位
price = item.find_element(By.CLASS_NAME, 'vlGucd')
movie['price']= price.text # 價格欄位
movies.append(movie) # 一部電影放入串列中
print(f'{movies}') # 所有找到的電影
for m in movies:
url = m['href'] # 網址
data = page_parse.parse_page(url)
m.update(data)
print(m)
# print(f"{m['title']}: {data}")
收集連結完整版(v2)
課堂展示版
from selenium import webdriver # 引入webdriver'
from selenium.webdriver.common.by import By
import time
# 版本:1.0
# 連線到網頁url, 回傳解析結果
def get_browser(url):
browser = webdriver.Chrome()
browser.get(url)
time.sleep(2)
return browser
def parse_page(url):
電影資料={} # 所有欄位存放的字典
browser = get_browser(url) # 連結網頁, 解析網頁內容
# 年份/電影長度
div_year = browser.find_element(By.CLASS_NAME,'tv4jIf')
for item in div_year.text.split('•'):
if '年' in item:
電影資料['年份'] = item
else:
電影資料['電影長度'] = item
# 爛蕃茄新鮮度 itemprop="tomatoMeter"
# 分級 itemprop="contentRating"
try:
ft = browser.find_element(By.XPATH,"//span[@itemprop='tomatoMeter']")
電影資料['新鮮度'] = ft.text
except:
print('沒有爛番茄指數')
電影資料['新鮮度'] = ''
# # 關於這部電影
about = browser.find_element(By.XPATH, '//div[@data-g-id="description"]')
電影資料['關於'] = about.text
# 內容分級
try:
grade_tag = browser.find_element(By.XPATH, '//div[@class="xg1aie"]')
電影資料['分級'] = grade_tag.text
except:
print('找不到內容分級')
電影資料['分級'] = ''
print(電影資料)
return 電影資料
單一頁面爬取函式版
檔名: page_parse.py
多頁面程式需要
import page_parse
...
data = page_parse.parse_page(某網址)
網路爬蟲實作
☛ 單一頁面版:使用 Requests + BeautilfulSoup4
Web Scraping範例(單一頁面版)
pip install requests
pip install beautifulsoup4
pip install lxml
安裝Requests, BeautilfulSoup套件(使用pip 或 pip3)
parser部分使用lxml(速度快,也可解析XML檔)
若系統找不到pip,改用pip3
import requests, bs4
# (1) 連線
url = "https://www.au.edu.tw/" # url: 要擷取的網址
resp = requests.get(url) # 發出HTTP request
# (2) 網頁解析
soup = bs4.BeautifulSoup(resp.content, "lxml") # 分析網頁
# (3) 擷取內容
print(f'編碼:{resp.encoding}') # HTTP回應有部份訊息可供使用
print('網頁標題', soup.title) # 解析後有也有資訊可用
BeatuifulSoup範例網頁標題.title
解析後的網頁才是重點!
import requests, bs4
url = "https://www.au.edu.tw/" # url: 要擷取的網址
resp = requests.get(url) # 發出HTTP request
## 檢查是否連線成功
if resp.status_code == 200: # 代碼200表示連線成功
soup = bs4.BeautifulSoup(resp.content, "lxml") # 分析網頁
else: # 連線失敗或其他
exit(1) # 非200代碼,程式直接結束
print(f'編碼:{resp.encoding}') # HTTP回應有部份訊息可供使用
print('網頁標題', soup.title) # 解析後有也有資訊可用
BeatuifulSoup範例網頁標題.title
實務上,會加上連線成功(代碼200)的檢查
import requests, bs4
# (1) 連線
url = "https://www.au.edu.tw/" # url: 要擷取的網址
resp = requests.get(url) # 發出HTTP request
# (2) 網頁解析
soup = bs4.BeautifulSoup(resp.content, "lxml") # 分析網頁
# (3) 擷取內容
print(f'編碼:{resp.encoding}') # HTTP回應有部份訊息可供使用
print('網頁標題', soup.title) # 解析後有也有資訊可用
BeatuifulSoup範例找tag名稱
解析後的網頁才是重點!
完整範例
以台灣棒球維基館為例
Web Scraping範例單一頁面版
import requests
from bs4 import BeautifulSoup
# Step 1: 發出Http Request, 解析(parse)Http Response
## url: 要擷取的網址
base_url = "http://twbsball.dils.tku.edu.tw" # 網站基礎網址,補絕對網址用
url = "http://twbsball.dils.tku.edu.tw/wiki/index.php/%E5%88%86%E9%A1%9E:%E5%8F%B0%E7%81%A3%E7%90%83%E5%93%A1"
resp = requests.get(url) # 發出HTTP request
if resp.status_code == 200: # 代碼200表示連線正常
soup = BeautifulSoup(resp.content, "lxml") # parsing網頁內容
else:
exit(1) # 非200代碼,程式直接結束
### 維基百科頁面標題特徵: id="firstHeading"
title = soup.find(id="firstHeading") # 根據id值進行搜尋
print(f'頁面標題{title.text}')
### 維基百科頁面內容:假設只對包含在id="mw-pages"內的超連結感興趣
### 注意!!!每個網頁內容的id可能都不一樣!!!必須先詳細了解網頁格式
all_links = soup.find(id="mw-pages").find_all("a") # 找出所有<A>標籤
print(f"超連結數量:{len(all_links):10}")
# 走訪過濾後的<A>標籤:
#### 1. 只對此Wiki站內連結感興趣
#### 2. 只處理球員連結
num_of_link = 0
for link in all_links:
if link['href'].find("/wiki/") == -1: # 站外連結跳過不處理
continue
if link.text == '之後200': # 網頁特定內容,跳過不處理
print(f'type of link:{type(link)}')
continue
print(f"球員:{link.text} 網址:{base_url + link['href']}")
num_of_link += 1
print(f'球員總數{num_of_link:10}')
webscraping-link.py
重要:必須先了解該網頁的HTML結構
重點觀察
- 屬性值(id, class),
- 特定標籤<div>, <h>系列, <SPAN>
前往目標網址後,開啟「開發人員工具」(F12)
任務 1: 爬取「球員連結」
但不是「所有超連結」都是球員
移動游標尋找目標區域
div標籤, id值 "mw-pages"
# 以'lxml' parser解析 resp.content的內容
soup = BeautifulSoup(resp.content, "lxml")
...
# 找出mw-pages段落裡面所有<A>標籤
all_links = soup.find(id="mw-pages").find_all("a")
任務 2: 擷取「頁面內容」
有興趣的段落沒有 id, class等屬性值可用
dl, dd, ul, li等標籤無法區分是否為感興趣的段落
url = "擷取的網址"
resp = requests.get(url) # (1)連線
if resp.status_code == 200: # 代碼200表示連線正常
soup = BeautifulSoup(resp.content, "lxml") # (2)分析
else:
exit(1) # 非200代碼,程式直接結束
import requests
from bs4 import BeautifulSoup
import需要的模組
連線與分析(parsing)
### 維基百科頁面標題特徵: id="firstHeading"
title = soup.find(id="firstHeading") # 根據id值進行搜尋
print(f'頁面標題{title.text}')
擷取任務1: 取得標題
### 維基百科頁面內容:假設只對包含在id="mw-pages"內的超連結感興趣
### 注意!!!每個網頁內容的id可能都不一樣!!!必須先詳細了解網頁格式
all_links = soup.find(id="mw-pages").find_all("a") #找出所有<A>標籤
# 走訪過濾後的<A>標籤:
for link in all_links:
if link['href'].find("/wiki/") == -1: # 站外連結跳過不處理
continue
if link.text == '之後200': # 網頁特定內容,跳過不處理
continue
print(f"球員:{link.text} 網址:{link['href']}") # 印出網址
擷取任務2: 取出段落中的超連結
Web Scraping範例單一頁面版#2
Web Scraping範例單一頁面版#2
webscraping-player.py
from bs4 import BeautifulSoup
import requests
import pandas
sections = [ # data frame的column name,共9個
'簡介', # 同義詞段落'生平簡介'
'基本資料',
'經歷',
'個人年表',
'特殊事蹟',
'職棒生涯成績',
'備註',
'註釋或參考文獻',
'外部連結',
]
sid = { # 台灣棒球維基館的section id
'基本資料': '.E5.9F.BA.E6.9C.AC.E8.B3.87.E6.96.99',
'經歷': '.E7.B6.93.E6.AD.B7',
'個人年表': '.E5.80.8B.E4.BA.BA.E5.B9.B4.E8.A1.A8',
'特殊事蹟': '.E7.89.B9.E6.AE.8A.E4.BA.8B.E8.B9.9F',
'職棒生涯成績':'.E8.81.B7.E6.A3.92.E7.94.9F.E6.B6.AF.E6.88.90.E7.B8.BE',
'備註':'.E5.82.99.E8.A8.BB',
'註釋或參考文獻': '.E8.A8.BB.E9.87.8B.E6.88.96.E5.8F.83.E8.80.83.E6.96.87.E7.8D.BB',
'外部連結': '.E5.A4.96.E9.83.A8.E9.80.A3.E7.B5.90',
'結尾': 'stub'
}
intro = {
'簡介': '.E7.B0.A1.E4.BB.8B',
'生平簡介': '.E7.94.9F.E5.B9.B3.E7.B0.A1.E4.BB.8B'
}
def extract_introduction(content, key, value):
pos = str(content).find(f'id="{value}"') # 簡介 / 生平簡介
if pos == -1:
return None
else:
next_pos = str(content).find('<span class="mw-headline" id=".E5.9F.BA.E6.9C.AC.E8.B3.87.E6.96.99">基本資料</span>')
intro_str = str(content)[pos:next_pos] # 擷取 「簡介/生平簡介」 段落
# print('*****',key, len(key))
intro_str = intro_str[intro_str.find(key):] # 去掉最前面未切乾淨的html碼
# print(intro_str)
intro_soup = BeautifulSoup(intro_str, 'lxml') # parsing
texts = intro_soup.find_all(text=True) # 取出所有元素的文字,不含標籤屬性
# 資料格式為串列
return u"".join(t.strip() for t in texts) # 將串列元素接在一起形單一字串,u""為unicode
####使用字串.find()和取得子字串的功能,切出每個段落大致的內容
# 參數:
# content: http response回應的內容部份
# start: 開始字串(不含),要裁切內容的開始位置
# end: 結尾字串(不含), 裁切內容的結束位置
# 回傳:
# 裁切好的子字串
def get_sub_content(content, start, end):
start_pos = str(content).find(f'id="{start}"')
print(f'sp:{start_pos}')
if start_pos == -1: # 找不到start字串
return None
else: # 找到 start字串
end_pos = str(content).find(f'id="{end}"') # 尋找end字串
if end_pos == -1:
return None # 找不到end字串
else:
print(f'ep:{end_pos}')
return str(content)[start_pos: end_pos] # 回傳裁切子字串
#### 使用BeatuifulSoup的功能,取出以<dd></dd>包起來的段落內容
# 參數:
# html_str: 要分析的段落內容字串
# 回傳:
# 段落內容串列,每一個li為一個串列元素
def extract_dlist(html_str):
soup = BeautifulSoup(html_str, 'lxml') # parsing
li = soup.find_all('li') # 找到<li>
li_texts = [] # 空字串, 將存入所有li的文字
for item in li:
item_text = item.find_all(text=True)
if u"".join(item_text).strip() != '':
li_texts.append(u"".join(item_text).strip())
return li_texts
# Step 1: 發出Http Request, 解析(parse)Http Response
## url: 要擷取的網址
base_url = "http://twbsball.dils.tku.edu.tw"
url = "http://twbsball.dils.tku.edu.tw/wiki/index.php/%E4%BD%95%E9%9C%87%E7%83%8A"
url = "http://twbsball.dils.tku.edu.tw/wiki/index.php/%E7%8E%8B%E5%BB%BA%E6%B0%91(1980)"
resp = requests.get(url) # 發出HTTP request
if resp.status_code == 200:
soup = BeautifulSoup(resp.content, "lxml") # parsing網頁內容
else:
exit(1)
final_result = {} # 最後擷取的全文結果
all_content = soup.find(id="bodyContent") # 取得全部HTML內文
for key, value in intro.items():
pp = extract_introduction(all_content, key, value) # 取得 簡介
if pp != None:
print(f'找到{key}')
final_result[key] = pp # 簡介
break
else:
print(f'找不到{key}')
start_str = '' # 擷取段落用的開始字串
end_str = '' # 擷取段落用的結束字串
title = '' # 目前擷取的段落名稱:對應至sid的key
next_title = '' # 下一個擷取的段落名稱:對應至sid的key
for key, value in sid.items(): # 遍訪sid串列,取出定位字串
if start_str == '': # 1. 設定開始字串
start_str = value # 1.1開始字串
title = key # 1.2段落名稱
continue
else:
if end_str == '': # 2. 設定結束字串
end_str = value # 2.1 結束字串
next_title = key # 2.2 下一個段落名稱
print(title)
# 使用開始字串,結束字串取得中間的html內容
sub_content_str = get_sub_content(all_content, start_str, end_str)
if sub_content_str == None: # 如果擷取回來沒有內容
end_str = '' # 清除結束字串,繼續處理下一個sid項目
continue
# 處理擷取回來的段落內容
li_list = extract_dlist(sub_content_str) # 取出當中的li
final_result[title] = ''.join(li_list) # 紀錄起來
start_str = end_str # 設定下一段的開始字串
end_str = '' # 清除結束字串
title =next_title # 設定下一段的段落名稱
# print(final_result) # 印出擷取的資料
fr_array = [] # 準備空白表格資料
fr_array.append(final_result) # 加入一筆資料至表格
index=[i for i in range(len(fr_array))] # data frame的索引
### 建立dataframe
# 1. fr_array: 表格資料(目前只有一筆)
# 2. index=index: 前者是參數名稱,後者是第9行建立的索引串列
# 3. columns=sections: 前者是參數名稱, 後者是先前定義的欄位串列
df = pandas.DataFrame(fr_array,index=index, columns=sections)
df.to_csv('out_file.csv') # 寫出檔案(utf-8編碼)
裁切子字串的函式:指定開始、結束字串,切出中間的內容
####使用字串.find()和取得子字串的功能,切出每個段落大致的內容
# 參數:
# content: http response回應的內容部份
# start: 開始字串(不含),要裁切內容的開始位置
# end: 結尾字串(不含), 裁切內容的結束位置
# 回傳:
# 裁切好的子字串
def get_sub_content(content, start, end):
start_pos = str(content).find(f'id="{start}"')
print(f'sp:{start_pos}')
if start_pos == -1: # 找不到start字串
return None
else: # 找到 start字串
end_pos = str(content).find(f'id="{end}"') # 尋找end字串
if end_pos == -1:
return None # 找不到end字串
else:
print(f'ep:{end_pos}')
return str(content)[start_pos: end_pos] # 回傳裁切子字串
開始字串、結束字串包括:
sid = { # 台灣棒球維基館的section id
'基本資料': '.E5.9F.BA.E6.9C.AC.E8.B3.87.E6.96.99',
'經歷': '.E7.B6.93.E6.AD.B7',
'個人年表': '.E5.80.8B.E4.BA.BA.E5.B9.B4.E8.A1.A8',
'特殊事蹟': '.E7.89.B9.E6.AE.8A.E4.BA.8B.E8.B9.9F',
'職棒生涯成績':'.E8.81.B7.E6.A3.92.E7.94.9F.E6.B6.AF.E6.88.90.E7.B8.BE',
'備註':'.E5.82.99.E8.A8.BB',
'註釋或參考文獻': '.E8.A8.BB.E9.87.8B.E6.88.96.E5.8F.83.E8.80.83.E6.96.87.E7.8D.BB',
'外部連結': '.E5.A4.96.E9.83.A8.E9.80.A3.E7.B5.90',
'結尾': 'stub'
}
開始字串、結束字串段落內都是<dd></dd>, 找出當中的<li>
#### 使用BeatuifulSoup的功能,取出以<dd></dd>包起來的段落內容
# 參數:
# html_str: 要分析的段落內容字串
# 回傳:
# 段落內容串列,每一個li為一個串列元素
def extract_dlist(html_str):
soup = BeautifulSoup(html_str, 'lxml') # parsing
li = soup.find_all('li') # 找到<li>
li_texts = [] # 空字串, 將存入所有li的文字
for item in li:
item_text = item.find_all(text=True) # 只要內文
if u"".join(item_text).strip() != '':
li_texts.append(u"".join(item_text).strip())
return li_texts
li的內文才是要擷取的主體內容
球員簡介段落的格式與其他段落不同
def extract_introduction(content, value):
pos = str(content).find(f'id="{value}"') # 簡介 / 生平簡介
if pos == -1:
return None
else:
next_pos = str(content).find('<span class="mw-headline" id=".E5.9F.BA.E6.9C.AC.E8.B3.87.E6.96.99">基本資料</span>')
intro_str = str(content)[pos:next_pos] # 擷取 「簡介/生平簡介」 段落
intro_str = intro_str[intro_str.find(key):] # 去掉最前面未切乾淨的html碼
intro_soup = BeautifulSoup(intro_str, 'lxml') # parsing
texts = intro_soup.find_all(text=True) # 取出所有元素的文字,不含標籤屬性
# 資料格式為串列
return u"".join(t.strip() for t in texts) # 將串列元素接在一起形單一字串,u""為unicode
intro = {
'簡介': '.E7.B0.A1.E4.BB.8B',
'生平簡介': '.E7.94.9F.E5.B9.B3.E7.B0.A1.E4.BB.8B'
}
簡介段落有下列兩種網頁格式
soup = BeautifulSoup(resp.content, "lxml")
all_content = soup.find(id="bodyContent") # 取得全部HTML內文
start_str = '' # 擷取段落用的開始字串
end_str = '' # 擷取段落用的結束字串
title = '' # 目前擷取的段落名稱:對應至sid的key
next_title = '' # 下一個擷取的段落名稱:對應至sid的key
final_result = {}
for key, value in sid.items(): # 遍訪sid串列,取出定位字串
if start_str == '': # 1. 設定開始字串
start_str = value # 1.1開始字串
title = key # 1.2段落名稱
continue
else:
if end_str == '': # 2. 設定結束字串
end_str = value # 2.1 結束字串
next_title = key # 2.2 下一個段落名稱
print(title)
# 使用開始字串,結束字串取得中間的html內容
sub_content_str = get_sub_content(all_content, start_str, end_str)
if sub_content_str == None: # 如果擷取回來沒有內容
end_str = '' # 清除結束字串,繼續處理下一個sid項目
continue
# 處理擷取回來的段落內容
li_list = extract_dlist(sub_content_str) # 取出當中的li
final_result[title] = li_list # 紀錄起來
start_str = end_str # 設定下一段的開始字串
end_str = '' # 清除結束字串
title =next_title # 設定下一段的段落名稱
print(final_result) # 印出擷取的資料
取得各段落內文
Python程式設計
By Leuo-Hong Wang
Python程式設計
Lesson 14: 網路爬蟲
- 1,952