2016年5月31日 星期二

Python_Note12

網頁抓取與解析

抓取:urllib

urllib is a package that collects several modules for working with URLs:

urllib是內建module,提供一般抓取網頁的工作,可以使用urlopen函數開啟某個網址,然後將傳回的物件呼叫它的read函數,取出所有網頁的內容,最後關閉。原本可能會很複雜的工作全部都已經被包好了。
  • 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)的函數
  • 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


Html Tag 類型
以下針對常見的 tag 類型做說明:
    • starttag
  1. 無屬性(attrs)的如: <head>
  2. 有包含屬性的: <span class="t18Red">
  3. 其中屬性會以 [ ("class", "t18Red" ) ] 形式存放內容
  • endtag
  1. </head>
  2. </xxx>為 endtag
  • startendtag
  1. <meta http-equiv="Content-Type" 
  2. content="text/html; charset=utf-8"/>
  •  data
  1. 被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 包含以下的方法
  • 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/>



#-*-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)

Out:
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'}

沒有留言:

張貼留言