Python 爬蟲實戰
楊証琨, 楊鎮銘
中央研究院資訊科學研究所資料洞察實驗室
Outline
2
圖片爬蟲 - 圖片的 tag?
3
圖片爬蟲 - 與文字爬蟲的差異
4
爬文字的過程 - 取得頁面資訊
5
request
request
Client
Server
.html
.html
https://gushi.tw/hu-shih-memorial-hall/
爬文字的過程 - 解析頁面並取得文字
Beautifulsoup( )
尋找第 4 個 <p> tag
透過 .text 取得文字資訊
6
Client
.html
爬圖片的過程 - 取得頁面資訊
7
request
request
Client
Server
.html
.html
https://gushi.tw/hu-shih-memorial-hall/
爬圖片的過程 - 取得位置資訊
Beautifulsoup( )
尋找第 1 個 <img> tag
透過 src 取得圖片位置資訊
8
Client
.html
爬圖片的過程 - 取得圖片
9
request
request
Client
Server
img
img
http://gushi.tw/wp-content/uploads/2016/08/logo.png
爬圖片的過程 - 下載圖片
from urllib.request import urlretrieve
# 透過 urlretrieve 下載圖片
# url: 你要下載的圖片位置
# file: 你要儲存的文件名稱
urlretrieve(url, file)
10
爬圖片的過程 - 偽裝成瀏覽器發送請求
11
request
request
Client
Server
身份識別 User-Agent
12
身份識別 User-Agent
13
身份識別 User-Agent
14
身份識別 User-Agent
15
爬圖片的過程 - 偽裝成瀏覽器發送請求
from urllib.request import build_opener
from urllib.request import install_opener
opener = build_opener()
opener.addheaders = [('User-Agent', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36')]
install_opener(opener)
16
練習 00: 下載圖片 (5 ~ 8 mins)
目標程式:00_download_image.py
目標網站:https://gushi.tw/hu-shih-memorial-hall/
目標:下載該頁面的第一個圖片
17
Bonus: 檢查圖片下載百分比
from urllib.request import urlretrieve
# 設計 callback
def check_percentage(chunk, size, remote):
percentage = 100.0 * chunk * size / remote
if percentage > 100.0:
percentage = 100.0
print('Download...{:.2f}%'.format(percentage))
urlretrieve(url, 'logo.png', check_percentage)
18
圖片存檔 - 副檔名的重要性
19
test.docx
test.xlsx
用 Office Word 開啟的文件
用 Office Excel 開啟的文件
無法開啟文件
rename
站外資源
20
眼見不為憑
21
眼見不為憑
22
眼見不為憑
23
獲取真實的圖片格式
import requests
from bs4 import BeautifulSoup
from PIL import Image
url = 'http://i.imgur.com/q6fMyz9.jpg'
response = requests.get(url, stream=True)
# 讓 PIL.Image 讀進圖片幫我們了解圖片格式
image = Image.open(response.raw)
print(image.format) # JPEG
24
圖片存檔 - 原始檔名與正確的副檔名
# url = 'http://i.imgur.com/q6fMyz9.jpg'
filename = url.split('/')[-1] # q6fMyz9.jpg
filename = filename.split('.')[0] # q6fMyz9
ext = image.format.lower() # JPEG -> jpeg
download_filename = '{}.{}'.format(filename, ext) # q6fMyz9.jpeg
25
練習 01: 判斷格式並下載圖片 (5 ~ 8 mins)
目標程式:01_download_image_and_check_format.py
目標:下載圖片並以正確格式儲存
url = 'http://imgur.com/rqCqA.png'
url = 'http://imgur.com/rqCqA.jpg'
url = 'http://imgur.com/rqCqA.gif'
26
練習 01: 延伸思考
27
Outline
28
檔案爬蟲 - 超連結檔案
29
檔案爬蟲 - 超連結的本體
30
<a href=”...”>
</a>
<img src=”...”>
檔案爬蟲 - 定位節點
31
src = 'http://140.112.115.12/exam/sites/all/modules/filefield/icons/application-pdf.png'
<a href=”...”>
</a>
<img src=”...”>
檔案爬蟲 - 定位節點
# 透過 regular expression 找到相同圖片的 img tag
images = soup.find_all('img',
{'src': re.compile('application-pdf\.png')})
for image in images:
# 透過 parent 函數尋訪 img tag 的上一層 tag
print(image.parent['href'])
32
href 的絕對路徑與相對路徑
33
檔案爬蟲 - 相對位置
34
請問可以外送十杯咖啡嗎?
可以阿,請問要送到哪裡?
門口進來左轉的樓梯到三樓右手邊的櫃台
… 是奧客嗎
檔案爬蟲 - 相對位置
url = '/exam/sites/all/modules/pubdlcnt/pubdlcnt.php?file=http://140.112.115.12/exam/sites/default/files/exam/graduate/106g/106_graduate_4.pdf&nid=5814'
response = requests.get(image_url)
35
檔案爬蟲 - 將相對路徑轉為絕對路徑
36
http://exam.lib.ntu.edu.tw/graduate
/exam/sites/all/modules/pubdlcnt/pubdlcnt.php
絕對路徑
相對路徑
http://exam.lib.ntu.edu.tw/exam/sites/all/modules/pubdlcnt/pubdlcnt.php
組合
檔案爬蟲 - 取得絕對位置
from urllib.parse import urljoin
print(urljoin(絕對路徑, 相對路徑))
37
檔案爬蟲 - 解析 URL
38
絕對路徑
相對路徑
練習 2-1: 檔案下載與 URL 轉換 (5 ~ 8 mins)
目標程式:02_1_observe_urljoin.py
目標:觀察不同的情況透過 urljoin 的結果
print(urljoin(response.url, '105g/'))
print(urljoin(response.url, '/105g/'))
print(urljoin(response.url, '//facebook.com'))
print(urljoin(response.url, '../'))
print(urljoin(response.url, '../../'))
39
練習 2-2: 檔案下載與 URL 轉換 (5 ~ 8 mins)
目標程式:02_2_download_history_exam.py
目標網站:http://140.112.115.12/exam/graduate
目標:下載頁面上的所有考古題 (25 份)
40
Outline
41
網站結構
42
root
articles
imgs
js
網站瀏覽行為
43
網站瀏覽行為
44
root
articles
imgs
js
Home
網站瀏覽行為
45
root
articles
imgs
js
主題特輯
遍歷網站 - 從網頁連結到其他網頁
46
Home
主題特輯
檢查 href 路徑之後送 request�(urljoin, requests)
取得主題特輯頁面
透過迴圈尋訪網站
47
root
index1
index2
index3
透過迴圈尋訪網站
48
root
index1
index2
index3
hidden
遍歷網站
[ index2, index3 ]
[ index3 ]
[ hidden ]
[ ]
49
root
index1
index2
index3
hidden
遍歷網站 - 直到看過所有連結
# 儲存即將要送 request 的網址
wait_list = ['https://afuntw.github.io/demo-crawling/demo-page/ex1/index1.html']
# 當 wait list 裡還有網址的時候...
while wait_list != []:
# 送 request 的流程
50
遍歷網站 - 更新 wait_list 清單
# 從 wait_list 中取出第一個網址並更新
url = wait_list.pop(0)
# 從 wait_list 中放入新的網址
wait_list.append(new_url)
51
練習 03: 取得真正的所有標題 (5~8 mins)
目標程式:03_crawling_demo1_hidden.py
目標:透過超連結不斷爬取多個網頁的 h1 tag
52
現實中網站的超連結設計
53
遍歷網站的迴圈問題
wait list
wait list
wait list
54
解決遍歷網站的迴圈問題
viewed_list = []
# 將送過 request 的網址存入 viewed_list
viewed_list.append(url)
# 檢查新網址沒有出現在 wait_list 與 viewed_list
if new_url not in wait_list and new_url not in viewed_list:
wait_list.append(new_url)
55
練習 04: 避免迴圈問題 (5~8 mins)
目標程式:04_crawling_demo2_no_infinite.py
目標:避免無窮迴圈的爬取網站的 h1 tag
56
root
index1
index2
index3
hidden
Summary
57
Outline
58
href 不全然是你想要的網址
59
你對 href 夠了解嗎?
60
href 可能的值 | 敘述 | 範例 |
absolute URL | 絕對路徑 | https://gushi.tw/ |
relative URL | 相對路徑 | /ex1/html1.html |
anchor | 同一頁面的其他 tag | #top |
other protocols | 其他協定 | mailto://example@gmail.com |
JavaScript | 程式碼 | javascript:console.log(“Hello”) |
過濾 href
import re
# 過濾錨點, e.g. #top
check_url_1 = re.match('#.*', url) # True/False
# 過濾其他協定, 只接受 http/https
from urllib.parse import urlparse
check_url_2 = urlparse(url).scheme not in ['https', 'http']
# 過濾程式碼, e.g. javascript:alert();
check_url_3 = re.match('^javascript.*', url)
61
練習 05: 過濾 href (5~10 mins)
目標程式:05_crawling_demo3_filter_href.py
目標:過濾不必要的超連結並取得網站的所有 h2 tag
# 觀察下列的值並嘗試送出 request
anchor = urljoin(url, '#top')
protocol = urljoin(url, 'mailto:example@gmail.com')
code = urljoin(url, 'javascript:alert("Hi");')
62
符合絕對路徑的 url 一定沒問題?
63
使用 urlparse 的極限
64
子網域, 網域與後綴
65
子網域
網域
後綴
台大首頁 http://www.ntu.edu.tw
台大中文 http://www.cl.ntu.edu.tw
台大法律 http://www.law.ntu.edu.tw
台大化工 http://www.che.ntu.edu.tw
...
透過 tldextract 分析網域
66
練習 06: 檢查域名 (5~8 mins)
目標程式:06_crawling_demo4_extract_domain.py
目標:分析網域,只對 www 或是跟原本網址一樣的 sub domain 送出 request
# 延伸思考:短網址或是路徑為 ip 拆解的結果?
from tldextract import extract
print(extract('https://goo.gl/z321G7'))
print(extract('https://127.0.0.1:80'))
67
Bonus: Google 短網址服務
from tldextract import extract
extract_url = extract(url)
# 判斷是否為 google 短網址
if extract_url.domain == 'goo' or extract_url.suffix == 'gl':
response = requests.get(extract_url)
print(response.url)
68
爬網站的重點回顧
69
Outline
70
甚麼是動態網頁?
71
靜態網頁 vs 動態網頁
72
靜態網頁
動態網頁
檢查網頁是靜態還是動態
73
關掉 JavaScript
打開 JavaScript
取得程式更新後的網頁
74
模擬使用者操作瀏覽器的行為
from selenium import webdriver
# 透過指定的瀏覽器 driver 打開 Chrome
driver = webdriver.Chrome('../webdriver/chromedriver')
# 透過瀏覽器取得網頁
driver.get('https://afuntw.github.io/demo-crawling/demo-page/ex4/index1.html')
75
瀏覽器的大小也會影響網頁結構
76
瀏覽器視窗最大化
from selenium import webdriver
# 透過指定的瀏覽器 driver 打開 Chrome
driver = webdriver.Chrome('../webdriver/chromedriver')
# 將瀏覽器視窗最大化
driver.maximize_window()
77
Selenium 定位 tag
# e.g. 取得 id='first' 的 tag
id_tag = drver.find_element_by_id('first')
print(id_tag)
78
練習 07: 靜態與動態爬蟲的差異 (5 mins)
目標程式:07_crawling_demo4_selenium.py
目標:分別透過 requests 與 Selenium 爬網站上�id = 'first' 的 tag
>>> requests: First featurette heading. It'll blow your mind.
>>> selenium: Rendered by Javascript
79
了解 tag 之間的關係
<bookstore>
<book>
<title>Harry Potter</title>
<author>K. Rowling</author>
</book>
</bookstore>
80
了解 tag 之間的關係
<bookstore>
<book>
<title>Harry Potter</title>
<author>K. Rowling</author>
</book>
</bookstore>
81
Selenium 定位 tag - XPath
82
語法 | 意義 |
/ | 從 root 開始選擇 |
// | 從任何地方開始選擇 |
. | 選擇當下這個 node |
.. | 選擇當下這個 node 的 parent node |
@ | 選擇 attribute |
* | 選擇任何 node |
| | OR |
XPath 範例
html
body
第三個 div
第一個 div
83
XPath 範例
from selenium import webdriver
# 打開瀏覽器, 視窗最大化, 對目標網址送 request...
# 尋找一個 html > body > div[2] > div[0]
h2 = driver.find_element_by_xpath(� '/html/body/div[2]/div[0]')
84
XPath 範例
from selenium import webdriver
from selenium.webdriver.common.by import By
# 打開瀏覽器, 視窗最大化, 對目標網址送 request...
# 尋找網頁中所有的 p tag
p = driver.find_elements(By.XPATH, '//p')
85
XPath 範例
from selenium import webdriver
from selenium.webdriver.common.by import By
# 尋找任何一個 id = 'first' 的 tag
h2 = driver.find_element(By.XPATH, '//*[@id="first"]')
# 尋找網頁中 id = 'second' 或 'third' 的 h2 tag
p = driver.find_elements(By.XPATH,
'//h2[@id="second"] | //h2[@id="third"]')
86
XPath helper
87
透過開發者工具取得 XPath
88
練習 08: 透過 XPath 做動態爬蟲 (10 mins)
目標程式:08_crawling_pchome_selenium.py
目標網站:http://24h.pchome.com.tw/region/DHBE
目標:取得頁面上調列商品的名稱與價格 (30 項)
89
Summary
90
爬蟲不一定要爬網頁
91
Reference
92