問題:對網頁Python會議,用瀏覽器查看源碼;嘗試解析HTML,輸出Python官網發布的會議時間、名稱和地點
准備工作:
①打開網頁后,需要提取的信息
②按F12進入開發者模式,找到這部分的源代碼
<li> <h3 class="event-title"><a href="/events/python-events/883/">PyConDE & PyData Berlin 2020 (cancelled)</a></h3> <p> <time datetime="2020-10-14T00:00:00+00:00">14 Oct. – 16 Oct. <span class="say-no-more"> 2020</span></time> <span class="event-location">Berlin, Germany</span> </p> </li>
方法1、request請求+正則表達式+re函數
step1、通過GET請求讀取網頁信息,並轉化為str類型
step2、利用正則表達式和re函數進行信息查找
完整代碼:
# 從https://www.python.org/events/python-events/,解析HTML # 輸出Python官網發布的會議時間、名稱和地點 # 正則表達式、匹配函數所在模塊re # 請求網頁內容:urllib模塊request類 import re from urllib import request, error def GetInfo_URL(URL): # 提取網頁內容,返回為str,請求方式為GET req = request.Request(URL) try: with request.urlopen(req) as res: print('Status:',res.status,res.reason) data = res.read().decode() # 轉碼后data為str類型 except error.URLError as e: print(e) # 對data使用正則表達式和findall函數進行信息提取 #行與行間,沒有空格及換行符\n re_datetime = re.compile(r'datetime="(.+)T') re_duration = re.compile(r'<time datetime=".*">(.*)<span class="say-no-more">') #r'\"(\d{2} \w{3}\..*)\"' re_name = re.compile(r'<h3 class="event-title"><.+>(.+)</a') re_location = re.compile(r'event-location\">(.+)</span') ret_datetime = re_datetime.findall(data) ret_duration = re_duration.findall(data) ret_name = re_name.findall(data) ret_location = re_location.findall(data) for i in range(len(ret_datetime)): print('Event %d:'%i) print('Datetime:%s'%ret_datetime[i]) print('Duration:%s' % ret_duration[i].replace('–','-')) #把'-'的html符號實體轉化為str'-' print('Name:%s'%ret_name[i]) print('Location:%s'%ret_location[i]) print('\n') if __name__ == '__main__': URL='https://www.python.org/events/python-events/' GetInfo_URL(URL)
需要注意的幾點:
1、html內容經過decode解碼為str類型后,html中不同行內容以拼接的形式放在同一個str中了,行與行的內容間不存在換行符,空格等分隔符號(這點很重要!!!)
2、接1,因此,構建正則表達式時,可以直接看該模塊前后模塊的內容,將匹配限制在一個很精確的范圍
比如代碼中正則表達式re_duration的構建:
re_duration = re.compile(r'<time datetime=".*">(.*)<span class="say-no-more">')
方法2、Python的HTMLParser類
導入:
from html.parser import HTMLParser
這個類是專門用來解析HTML的,用起來很方便。
只需要簡單的分析,要提取的網頁中的各項信息儲存格式就可以。
回到這張圖片,圖片中框起來的是我們要提取出來的信息。
step1、導入模塊和類
from html.parser import HTMLParser from urllib import request,error import re
step2、構建此程序專用的MyHTMLParser類(繼承自HTMLParser)
class MyHTMLParser(HTMLParser): def __init__(self): super().__init__() self.__parsertag = '' self.info = [] def handle_starttag(self, tag, attrs): if ('class', 'event-title') in attrs: self.__parsertag = 'name' if tag == 'time': self.__parsertag = 'time' if ('class', 'say-no-more') in attrs: self.__parsertag = 'year' if ('class', 'event-location') in attrs: self.__parsertag = 'location' def handle_endtag(self,tag): self.__parsertag = '' def handle_data(self, data): if self.__parsertag == 'name': self.info.append(f'會議名稱:{data}') if self.__parsertag == 'time': self.info.append(f'會議時間:{data}') if self.__parsertag == 'year': data=data.strip()#消去前后多余的空格 # 由於會檢出幾個匹配但錯誤的str,所以用正則表達式加以檢驗 if re.match('^\d{4}$',data): self.info.append(f'會議年份:{data}') if self.__parsertag == 'location': self.info.append(f'會議地點:{data}\n')
編寫MyHTMLParser有很多細節:
下邊我用紅色標記關鍵字,用藍色標記方法
1、補充屬性__parsertag與info
__parsertag用來標記當前語句塊對應的信息名字,如'name'、'time'……
info為list對象,用來存放所有我們提取的信息
2、重寫三個方法handle_starttag、handle_endtag、handle_data
handle_starttag(self,tag,attrs):當遇到starttag(<tag attrs>)時,運行該方法,參數中的tag為源碼中<后的標識符,attrs為補充參數
handle_endtag(self):遇到endtag(</tag>,與starttag相對應)時,運行該方法
handle_data(self,data):遇到中間的data(沒有被<>包括的部分都是data)時,運行該方法
我們需要的內容,都在handle_data方法中利用參數data提取。而handle_starttag則用來標識這個data是屬於哪部分的,用屬性__parsertag記錄下這部分data所屬的tag。handle_endtag是在讀到代碼塊的末尾時,用來復原__parsertag的,以便下個模塊可以重復上述操作。
下邊我們以源代碼中的一個html塊來解讀handle_starttag、handle_data、handle_endtag
從這部分塊中提取到的tag被我們標記為"name",對應的data為PyConDE & PyData Berlin 2020 (cancelled)
務必要記住,塊的starttag是<tag attrs>格式,endtag是</tag>,data是沒有被<>包括的部分。
①HTML解析器在運行到<h3 ...>時,識別出這是starttag,於是調用handle_starttag方法進行處理。
<h3 class='event-title'>經過解碼后變為,tag='h3',attrs=('class' , 'event-title')。這里的tag與attrs就是handle_starttag參數中傳入的tag和attrs。更廣泛的來看,<X Y=Z>類型的starttag都會被解碼為tag='X' , attrs=('Y','Z')的形式。
這樣我們就可以很方便地對解碼后的傳入參數tag與attrs進行分析,而不用考慮html代碼了。
②注意到想要提取的'name'內容都是以<h3 class='event-title'>為starttag的,我們就可以在handle_starttag中對這部分的內容標記為'name';由於tag不具有代表性(比如此處的tag為h3,但是其他代碼段也有h3,所以就不能用h3作為判斷標志),我們用attrs作為判斷標志:
if ('class','event-tile') in attrs: self.__parsertag='name'
之所以這樣寫,可以再回頭看一下我在①中第二段所寫的內容。
③HTML解析器運行到PyConDE & PyData Berlin 2020 (cancelled)時,由於該部分內容沒有被<>包括,所以識別出這部分為data,於是調用handle_data方法進行處理。
if self.__parsertag=='name': self.info.append(f'會議名稱:{data}')
由於在handle_starttag中我們已經將此部分的內容用內部變量__parsertag標注為'name',所以進行處理時,只需要用if語句將__parsertag與'name'進行匹配,匹配成功就可以繼續我們想對'name'塊的data所作的處理了。由於我們的最終目的是輸出,所以我們把這部分的data格式化后加入list中。
④HTML解析器當運行到</h3>時,識別出這是endtag,就調用方法handle_endtag進行處理。由於接下來還有html代碼塊要進行類似上述流程的處理,所以我們把參數初始化,本例中只需要初始化__parsertag就可以了
self.__parsertag=''
step3、抓取該網頁的所有內容——GET請求
如果不清楚如何用GET請求抓取某個網頁的所有內容,可以參考訪問網頁的兩種方式:GET與POST
我們在step2中說的那么多,前提是已經把網頁內容獲取了,而獲取網頁內容的函數如下:
def GetInfo_URL(URL): headers={'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36'} #為了安全起見,最好用try...except語句塊進行網頁請求 try: req=request.Request(URL,headers=headers) with request.urlopen(req) as res: #輸出請求狀態 print(f'Status:{res.status},{res.reason}') data=res.read() return data.decode() except error.URLError as e: print(e)
step4、在main函數中運行
if __name__=='__main__': URL='https://www.python.org/events/python-events/' data=GetInfo_URL(URL) #創建HTML解析器 parser=MyHTMLParser() #對抓取的數據進行解析,feed方法 #如果數據太長,一次裝不下,可以多次feed parser.feed(data) for s in parser.info: print(s)
這部分也有幾點需要注意的地方:
①創建HTML解析器,用我們自寫的MyHTMLParser創建一個實例
parser=MyHTMLParser()
②裝載數據+解析——feed()方法
parser.feed(data)
如果data太長一次放不下,可以多次feed,結果和一次feed全部相同
③輸出結果
for s in parse.info: print(s)
完整代碼:
from html.parser import HTMLParser from urllib import request, error import re class MyHTMLParser(HTMLParser): def __init__(self): super().__init__() self.__parsertag = '' self.info = [] def handle_starttag(self, tag, attrs): if ('class', 'event-title') in attrs: self.__parsertag = 'name' if tag == 'time': self.__parsertag = 'time' if ('class', 'say-no-more') in attrs: self.__parsertag = 'year' if ('class', 'event-location') in attrs: self.__parsertag = 'location' def handle_endtag(self,tag): self.__parsertag = '' def handle_data(self, data): if self.__parsertag == 'name': self.info.append(f'會議名稱:{data}') if self.__parsertag == 'time': self.info.append(f'會議時間:{data}') if self.__parsertag == 'year': data=data.strip()#消去前后多余的空格 # 由於會檢出幾個匹配但錯誤的str,所以用正則表達式加以檢驗 if re.match('^\d{4}$',data): self.info.append(f'會議年份:{data}') if self.__parsertag == 'location': self.info.append(f'會議地點:{data}\n') def GetInfo_URL(URL): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36'} # 為了安全起見,最好用try...except語句塊進行網頁請求 try: req = request.Request(URL, headers=headers) with request.urlopen(req) as res: # 輸出請求狀態 print(f'Status:{res.status},{res.reason}') data = res.read() return data.decode() except error.URLError as e: print(e) if __name__ == '__main__': URL = 'https://www.python.org/events/python-events/' data = GetInfo_URL(URL) # 創建HTML解析器 parser = MyHTMLParser() # 對抓取的數據進行解析,feed方法 # 如果數據太長,一次裝不下,可以多次feed parser.feed(data) for s in parser.info: print(s)