兩種方式提取網頁信息——爬蟲初步


問題:對網頁Python會議,用瀏覽器查看源碼;嘗試解析HTML,輸出Python官網發布的會議時間、名稱和地點

 

准備工作:

①打開網頁后,需要提取的信息

②按F12進入開發者模式,找到這部分的源代碼

<li>
                        <h3 class="event-title"><a href="/events/python-events/883/">PyConDE &amp; 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('&ndash;','-')) #把'-'的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、補充屬性__parsertaginfo

__parsertag用來標記當前語句塊對應的信息名字,如'name'、'time'……

infolist對象,用來存放所有我們提取信息

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_starttaghandle_data、handle_endtag

從這部分塊中提取到的tag被我們標記為"name",對應的dataPyConDE & 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參數傳入tagattrs。更廣泛的來看,<X Y=Z>類型的starttag都會被解碼為tag='X' , attrs=('Y','Z')的形式。

這樣我們就可以很方便地對解碼后的傳入參數tagattrs進行分析,而不用考慮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)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM