Lesson 14: 網路爬蟲
Last Updated: 2022/12/27
簡介
web spider?
web crawler?
web scraper?
爬取網頁(連結) ➠ 製作網頁索引
Web crawling
應用:搜尋引擎
➊ Spider程式爬取網站內容
➋ 為爬取的網頁建立索引
➌使用者搜尋關鍵字
➍接收結果
爬取網頁內容 ➠ 轉成結構化資料
Web scraping
目的:Make smarter decisions
價格追蹤
商品評價
潛在客戶
2.1 遍訪連結
建立待訪堆疊
1. 從一個網頁開始
2.2 擷取內容
3. 輸出結果(至檔案)
圖片來源1
圖片來源2
取得HTML回應碼
解析超連結
解析其他HTML元素
資料輸出
資料清理
能力1: 取得HTML回應碼?了解HTTP協定
網址(http request)
瀏覽器
Web 伺服器
HTML回應碼(http response)
能力2: 解析超連結、其他HTML元素、網頁內容 ➠
HTML Parser(解析器)
符號化
建立DOM樹
以p元素為例
元素
內容
開始標籤
結束標籤
屬性名稱
屬性值
找出超連結元素A,屬性href的值 ➠ 待訪網址
【任務】建立待訪網址串列:
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>
內文:之後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有「絕對」、「相對」路徑之分
相對路徑, 不完整
補成絕對路徑
能力2: 解析超連結、其他HTML元素、網頁內容
Parser(解析器)
能力1: 取得HTML回應碼?了解HTTP協定
http連線功能
➠
圖片來源1
能力3: 資料清理、資料輸出 ➠
資料處理工具
2.1 遍訪連結
建立待訪堆疊
1. 從一個網頁開始
2.2 擷取內容
3. 輸出結果(至檔案)
圖片來源1
圖片來源2
取得HTML回應碼
解析超連結
解析其他HTML元素
資料輸出
資料清理
能力2: 解析網頁內容(Parser)
能力1: HTTP連線功能
pip install selenium
❶ 在主控台安裝selenium:
pip3 install selenium
或
❷ 下載瀏覽器驅動程式
確認你的Chrome版本
❸ 設定Path環境變數
輸入d:\drivers 或其他資料夾
❹ 將驅動程式解壓縮至d:\drivers
❺ 測試環境
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
get()函式
# 1. 引入webdriver功能
from selenium import webdriver
# 2. 設定驅動程式路徑
dirverPath = 'D:\drivers\chromedriver.exe'
# 3. 建立webdriver物件
browser = webdriver.Chrome(dirverPath)
# 4. 設定網址
url = 'http://www.au.edu.tw'
# 5. 取得網頁(HTTP連線功能)
browser.get(url) # 網頁下載至瀏覽器
find_element_by_id(id):傳回第一個相符id的元素。
find_elements_by_id(id):傳回所有相符的id的元素,傳回串列
find_element_by_class_name(name):傳回第一個相符Class的元素。
find_elements_by_class_name(name):傳回所有相符的Class的元素,傳回串列
find_element_by_name(name):傳回第一個相符name屬性的元素。
find_elements_by_name(name):傳回所有相符的name屬性的元素,傳回串列
find_element_by_css_selector(selector):傳回第一個相符CSS selector的元素。
find_elements_by_css_selector(selector):傳回所有相符的CSS selector的元素,傳回串列
find_element_by_partial_link_text(text):傳回第一個內含有text的<a>元素。
find_elements_by_ partial_link_text(text):傳回所有內含相符text的<a>元素,傳回串列
find_element_by_link_text(text):傳回第一個完全相同text的<a>元素。
find_elements_by_link_text(text):傳回所有完全相同text的<a>元素,以串列方式傳回。
find_element_by_tag_name(name):不區分大小寫,傳回第一個相符name的元素。
find_elements_by_tag_name(name):不區分大小寫,傳回所有相符的name的元素,傳回串列
找不到特定元素:NoSuchElementException
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
# 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找不到
# 1. 引入webdriver功能
from selenium import webdriver
# 2. 設定驅動程式路徑
dirverPath = 'D:\drivers\chromedriver.exe'
# 3. 建立webdriver物件
browser = webdriver.Chrome(dirverPath)
# 4. 設定網址
url = 'http://www.au.edu.tw'
# 5. 取得網頁
browser.get(url) # 網頁下載至瀏覽器
try:
tag = browser.find_element_by_id('main')
print(tag.tag_name)
except:
print("沒有找到相符的元素")
from selenium import webdriver # 1. 引入webdriver功能
from selenium.webdriver.common.by import By
dirverPath = 'D:\drivers\chromedriver.exe' # 2. 驅動程式路徑
browser = webdriver.Chrome(dirverPath) # 3. ebdriver物件
url = 'https://www.au.edu.tw'
browser.get(url) # 5. 取得網頁
try:
tag1 = browser.find_element(By.TAG_NAME, 'title')
print("標籤=%s, 內容= %s " % (tag1.tag_name, tag1.text))
tag3 = browser.find_elements(By.ID, 'content') # 傳回id為content的內容
for i in range(len(tag3)):
print("標籤=%s, 內容= %s " % (tag3[i].tag_name, tag3[i].text))
iframes = browser.find_elements(By.TAG_NAME, 'iframe')
print(f'有{len(iframes)}個iframe')
# 找第一個超連結
link = browser.find_element(By.TAG_NAME, "a")
print('文字部分:',link.text)
print('連結部分:', link.get_attribute('href'))
tag4 = browser.find_elements(By.TAG_NAME, 'p') # 傳回<p>
for i in range(len(tag4)):
print("標籤=%s, 內容= %s " % (tag4[i].tag_name, tag4[i].text))
tag5 = browser.find_elements(By.TAG_NAME, 'img') # 傳回<img>
for i in range(len(tag5)):
print("標籤=%s, 內容=%s " % (tag5[i].tag_name, tag5[i].get_attribute('src')))
except:
print("沒有找到相符的元素")
from selenium import webdriver # 1. 引入webdriver功能
dirverPath = 'D:\drivers\chromedriver.exe' # 2. 驅動程式路徑
browser = webdriver.Chrome(dirverPath) # 3. ebdriver物件
url = 'https://tw.yahoo.com/'
browser.get(url) # 5. 取得網頁
try:
images = browser.find_elements_by_tag_name('img')
for i in range(len(images)):
print(images[i].get_attribute('src'))
except:
print("沒有找到相符的元素")
finally:
browser.quit()
from selenium import webdriver # 引入webdriver
import time
driverPath = 'D:/drivers/chromedriver.exe'
browser = webdriver.Chrome(driverPath)
url = 'https://twbsball.dils.tku.edu.tw/wiki/index.php/%E5%88%86%E9%A1%9E:%E5%8F%B0%E7%81%A3%E6%97%85%E7%BE%8E%E7%90%83%E5%93%A1'
browser.get(url)
time.sleep(3)
try:
print(browser.title)
links = browser.find_elements_by_tag_name('a')
print('總共有幾個連結:', len(links))
for i in range(len(links)):
print(links[i].get_attribute('href'))
except:
print('沒有找到相符的元素')
finally:
browser.quit()
標題: title屬性
time.sleep(): 暫停一些秒數,以免被認為是「攻擊行為」
href屬性: 指定超連結網址
☛ 單一頁面版:使用 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欄位
以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(driverPath)
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'
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 (節點定位語法)
# 以上省略
# 版本: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
driverPath = 'C:/DRIVERS/chromedriver.exe'
# 連線到網頁url, 回傳解析結果
def get_browser(url):
browser = webdriver.Chrome(driverPath)
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
except:
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(driverPath)
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()
收集連結完整版
輸出csv至out_links.csv
☛ 單一頁面版:使用 Requests + BeautilfulSoup4
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) # 解析後有也有資訊可用
解析後的網頁才是重點!
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) # 解析後有也有資訊可用
實務上,會加上連線成功(代碼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) # 解析後有也有資訊可用
解析後的網頁才是重點!
以台灣棒球維基館為例
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結構
重點觀察
前往目標網址後,開啟「開發人員工具」(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: 取出段落中的超連結
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) # 印出擷取的資料
取得各段落內文