網頁抓取與解析
抓取:urllib
urllib
is a package that collects several modules for working with URLs:
urllib.request
for opening and reading URLs
urllib.error
containing the exceptions raised by urllib.request
urllib.parse
for parsing URLs
urllib.robotparser
for parsing robots.txt
files
urllib是內建module,提供一般抓取網頁的工作,可以使用urlopen函數開啟某個網址,然後將傳回的物件呼叫它的read函數,取出所有網頁的內容,最後關閉。原本可能會很複雜的工作全部都已經被包好了。urllib
is a package that collects several modules for working with URLs:urllib.request
for opening and reading URLsurllib.error
containing the exceptions raised by urllib.request
urllib.parse
for parsing URLsurllib.robotparser
for parsing robots.txt
files- urlopen(),是基於python的open()方法
- urllib.request.urlopen('網址')
- 傳入參數要遵循http、ftp、等網路協議
urllib.request.urlopen('http://www.yahoo.com.tw')
特別注意,協定方式一定要加 ( ex : http://)
- 也可以是本機端的檔案
urllib.request.urlopen('file:c:\\user\\檔名.副檔名')網頁讀取
- 使用read()方法會將所有內容以bytes型態讀取出來
- bytes型態可透過呼叫decode()方法來設定編碼, 並轉成字串型態回傳
response = urllib.request.urlopen(‘http://invoice.etax.nat.gov.tw/’)
response.read().decode(‘utf_8’)
- 其中 read() 中可以傳入參數,例如read(10)則會回傳長度10的字串
#-*-coding:UTF-8 -*-
# 使用 urllib 讀取網頁內容範例
import
urllib.request
response
= urllib.request.urlopen('http://invoice.etax.nat.gov.tw/')
html =
response.read().decode('utf_8')
print(html)
HTMLparser
https://docs.python.org/3/library/html.parser.html#module-html.parser
This module defines a class
HTMLParser
which serves as the basis for parsing text files formatted in HTML (HyperText Mark-up Language) and XHTML.- 是HTML的解析器,不是嚴謹地去解析網頁,它可以處理像不對稱的HTML語法等等,對於網路上各種千奇百怪出錯的網頁來說,當然是選擇可以容錯的 Parser比較好
- 其運作方式是這樣,使用者覆載(override)一系列的handle_xxx函數,例如handle_data就是負責處理非HTML標籤,也就是不在<>的那些字用的方法,當它分析到這樣的資料就會呼叫handle_data,所以覆載了這個函數就可以處理這些資料,如果你希望可以處理 HTML標籤,也可以覆載handle_startag等等方法。其中xxx表示html tag的類型
- from html.parserimport HTMLParser
- 透過繼承的機制繼承 HTMLParser 類別
- 定義我們自己的網頁原始碼的解析器類別
- 依需求覆載(override)一系列的 handle_xxx函數, 並實作函式的內容
- 使用自行定義的類別產生出解析器物件實體
- 透過呼叫feed()方法將傳入的參數進行語法分析
可附載 (override)的函數
抓取網頁前,需先分析一下Html- HTMLParser.handle_starttag(tag, attrs)
- HTMLParser.handle_endtag(tag)
- HTMLParser.handle_startendtag(tag, attrs)
- HTMLParser.handle_data(data)
- HTMLParser.handle_entityref(name)
- HTMLParser.handle_charref(name)
- HTMLParser.handle_comment(data)
- HTMLParser.handle_decl(decl)
- HTMLParser.handle_pi(data)
- HTMLParser.unknown_decl(data)
Html Tag 類型
以下針對常見的 tag 類型做說明:
Out:
HTMLParser 包含以下的方法
到網頁原始碼找關鍵字位置
從發票號碼找出關鍵字為<span class="t18Red">跟</span>
以下針對常見的 tag 類型做說明:
- starttag
- 無屬性(attrs)的如: <head>
- 有包含屬性的: <span class="t18Red">
- 其中屬性會以 [ ("class", "t18Red" ) ] 形式存放內容
- endtag
- </head>
- 有</xxx>為 endtag
- startendtag
- <meta http-equiv="Content-Type"
- content="text/html; charset=utf-8"/>
- data
範例:以統一發票號碼網頁為例
- 被tag夾住的內容,非任何tag形式稱之為data
#-*-coding:UTF-8
-*-
# HTMLParser 範例
from
html.parser import HTMLParser
class
MyHTMLParser(HTMLParser):
def handle_starttag(self, tag, attrs):
print("Encountered a start
tag:", tag)
def handle_endtag(self, tag):
print("Encountered an end tag
:", tag)
def handle_data(self, data):
print("Encountered some data
:", data)
myparser
= MyHTMLParser()
myparser.feed('<html><head><title>Test</title></head><body><h1>Parse
me!</h1></body></html>')
Out:
Encountered
a start tag: html
Encountered
a start tag: head
Encountered
a start tag: title
Encountered
some data : Test
Encountered
an end tag : title
Encountered
an end tag : head
Encountered
a start tag: body
Encountered
a start tag: h1
Encountered
some data : Parse me!
Encountered
an end tag : h1
Encountered
an end tag : body
Encountered
an end tag : html
- HTMLParser.feed(data)
- HTMLParser.close()
- HTMLParser.reset()
- HTMLParser.getpos() print(data,'pos:',self.getpos())
- HTMLParser.get_starttag_text()
到網頁原始碼找關鍵字位置
從發票號碼找出關鍵字為<span class="t18Red">跟</span>
<span class="t18Red">18498950</span><br/>
Out:
上述情況僅限簡單的網頁抓取,但網頁有各種功能及語法,以下以動態網頁中的下拉式選單擷取及傳送指令為範例。
高鐵網站
http://www.thsrc.com.tw/tw/TimeTable/SearchResult
首先,要如何知道是POST還是GET
記得先搜尋一次,才會有資料傳輸。
使用POST方法就不用擔心資料大小的限制,可以防止使用者操作瀏覽器網址,表單的資料被隱藏在message-body中,因此,在大多數的情況下,使用POST方法將表單資料傳到Web Server端是更有效的方法。
一個簡單的GET就是在網址尾部加上一個問號 ? 之後進行宣告傳送的變數。而傳送的格式是:”變數名稱=變數的值” 。
若有多個變數需要傳遞則用 & 符號隔開。
範例:
此範例會將資料輸出為data.html,打開看後可以看到下拉式選單中的資訊被擷取出來了,不過因為還沒有經過語法分析,所以是將網頁的全部資料 (包括下拉式選單) 都擷取下來。
Header
#-*-coding:UTF-8
-*-
# 統一發票號碼抓取範例
import
urllib.request
from
html.parser import HTMLParser
data =
urllib.request.urlopen('http://invoice.etax.nat.gov.tw')
content
= data.read().decode('utf_8')
data.close()
class
myparser(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.isNumber = 0
self.numbers = []
def handle_data(self, data):
if self.isNumber == 1:
self.numbers.append(data) #self.numbers.extend(data.split('、'))
self.isNumber = 0
print(data, 'pos:', self.getpos()) #印出 data所在網頁中的行、位置
def handle_starttag(self, tag, attrs):
if tag == 'span' and attrs ==
[('class','t18Red')]: #關鍵字
self.isNumber = 1
def handle_endtag(self,tag):
pass
Parser
= myparser()
Parser.feed(content)
print(Parser.numbers)
18498950
pos: (2, 1388)
08513139
pos: (2, 1505)
21881534、53050416、85174778
pos: (2, 1619)
086
pos: (2, 2176)
51730762
pos: (2, 2682)
67442563
pos: (2, 2799)
11036956、55794786、62610317
pos: (2, 2913)
079
pos: (2, 3470)
['18498950',
'08513139', '21881534、53050416、85174778', '086', '51730762', '67442563',
'11036956、55794786、62610317', '079']
pos:(行,位置)
改為#self.numbers.extend(data.split('、'))
則輸出為
['18498950',
'08513139', '21881534', '53050416', '85174778', '086', '51730762', '67442563',
'11036956', '55794786', '62610317', '079']
然後再取前6項,即可取出當月的統一發票號碼(這邊自行嘗試)
URL的解碼與編碼
- import urllib.parse
- urllib.parse.quote(str)
此方法可將str中的字串轉為url編碼範例:
In[7]:
urllib.parse.quote('美國隊長3')
Out[7]:
'%E7%BE%8E%E5%9C%8B%E9%9A%8A%E9%95%B73'- urllib.parse.unquote(str)
將url碼解碼
範例:
In[8]:
urllib.parse.unquote('%E7%BE%8E%E5%9C%8B%E9%9A%8A%E9%95%B73')
Out[8]:
'美國隊長3'
上述情況僅限簡單的網頁抓取,但網頁有各種功能及語法,以下以動態網頁中的下拉式選單擷取及傳送指令為範例。
高鐵網站
http://www.thsrc.com.tw/tw/TimeTable/SearchResult
首先,要如何知道是POST還是GET
從網頁>>右鍵>>檢查>>Network>>Headers>>Form
Data
記得先搜尋一次,才會有資料傳輸。
POST
POST方法是將要傳送的資訊放在message-body中使用POST方法就不用擔心資料大小的限制,可以防止使用者操作瀏覽器網址,表單的資料被隱藏在message-body中,因此,在大多數的情況下,使用POST方法將表單資料傳到Web Server端是更有效的方法。
GET
GET就是指在網址上指定變數的名稱及變數的值給網頁伺服器,使用GET是有一個上限的所以較不適合用來傳送大量的資料或訊號。一個簡單的GET就是在網址尾部加上一個問號 ? 之後進行宣告傳送的變數。而傳送的格式是:”變數名稱=變數的值” 。
若有多個變數需要傳遞則用 & 符號隔開。
範例:
#-*-coding:UTF-8
-*-
# 高鐵站查詢範例
import
urllib.request
import
urllib.parse
from
html.parser import HTMLParser
data =
urllib.parse.urlencode({'StartStation': '977abb69-413a-4ccf-a109-0272c24fd490',
'EndStation':'f2519629-5973-4d08-913b-479cce78a356','SearchDate':'2016/05/31','SearchTime':'14:00','SearchWay':'DepartureInMandarin','RestTime':'','EarlyOrLater':''}) #這邊請輸入From Data的當下資訊,否則會顯示網頁不存在,譬如輸入日期是舊的,因為高鐵網站已沒有過去日期的選單
data =
data.encode('utf-8')
request
=
urllib.request.Request('http://www.thsrc.com.tw/tw/TimeTable/SearchResult',data
= data,headers = {'User-Agent' :'Mozilla/5.0 (Windows NT 6.1; WOW64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36'})
response
= urllib.request.urlopen(request, data)
html =
response.read().decode('utf_8')
print(html,file
= open('data.html','w',encoding= 'utf_8') )
此範例會將資料輸出為data.html,打開看後可以看到下拉式選單中的資訊被擷取出來了,不過因為還沒有經過語法分析,所以是將網頁的全部資料 (包括下拉式選單) 都擷取下來。
Header
需讓Python知道網頁瀏覽器是哪一種,所以 Headerˋ中的User-Agent也需要修改,
headers
= {'User-Agent' :'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36'}