1. 程式人生 > >Python爬蟲之解析網頁

Python爬蟲之解析網頁

常用的類庫為lxml, BeautifulSoup, re(正則)

以獲取豆瓣電影正在熱映的電影名為例,url='https://movie.douban.com/cinema/nowplaying/beijing/'

網頁分析

部分網頁原始碼

<ul class="lists">
                    <li
                        id="3878007"
                        class="list-item"
                        data-title="海王"
                        data-score="8.2"
                        data-star="40"
                        data-release="2018"
                        data-duration="143分鐘"
                        data-region="美國 澳大利亞"
                        data-director="溫子仁"
                        data-actors="傑森·莫瑪 / 艾梅柏·希爾德 / 威廉·達福"
                        data-category="nowplaying"
                        data-enough="True"
                        data-showed="True"
                        data-votecount="105013"
                        data-subject="3878007"
                    >
                        

分析可知我們要的電影名稱資訊在li標籤的data-title屬性裡

下面開始寫程式碼

爬蟲原始碼展示

import requests
from lxml import etree              # 匯入庫
from bs4 import BeautifulSoup
import re

import time

# 定義爬蟲類
class Spider():
    def __init__(self):
        self.url = 'https://movie.douban.com/cinema/nowplaying/beijing/'

        self.headers = {
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
        }
        r = requests.get(self.url,headers=self.headers)
        r.encoding = r.apparent_encoding
        self.html = r.text

    def lxml_find(self):
        '''用lxml解析'''
        start = time.time()                     # 三種方式速度對比
        selector = etree.HTML(self.html)        # 轉換為lxml解析的物件
        titles = selector.xpath('//li[@class="list-item"]/@data-title')    # 這裡返回的是一個列表
        for each in titles:
            title = each.strip()        # 去掉字元左右的空格
            print(title)
        end = time.time()
        print('lxml耗時', end-start)

    def BeautifulSoup_find(self):
        '''用BeautifulSoup解析'''
        start = time.time()
        soup = BeautifulSoup(self.html, 'lxml')   # 轉換為BeautifulSoup的解析物件()裡第二個引數為解析方式
        titles = soup.find_all('li', class_='list-item')
        for each in titles:
            title = each['data-title']
            print(title)
        end = time.time()
        print('BeautifulSoup耗時', end-start)

    def re_find(self):
        '''用re解析'''
        start = time.time()
        titles = re.findall('data-title="(.+)"',self.html)
        for each in titles:
            print(each)
        end = time.time()
        print('re耗時', end-start)

if __name__ == '__main__':
    spider = Spider()
    spider.lxml_find()
    spider.BeautifulSoup_find()
    spider.re_find()

輸出結果

海王
無名之輩
無敵破壞王2:大鬧網際網路
狗十三
驚濤颶浪
毒液:致命守護者
憨豆特工3
神奇動物:格林德沃之罪
恐龍王
老爸102歲
生活萬歲
進擊的男孩
摘金奇緣
亡命救贖
一百年很長嗎
雲上日出
誰是壞孩子
照相師
緣·夢
網路謎蹤
龍貓
印度合夥人
綠毛怪格林奇
最萌警探
春天的馬拉松
lxml耗時 0.007623910903930664
海王
無名之輩
無敵破壞王2:大鬧網際網路
狗十三
驚濤颶浪
毒液:致命守護者
憨豆特工3
神奇動物:格林德沃之罪
恐龍王
老爸102歲
生活萬歲
進擊的男孩
摘金奇緣
亡命救贖
一百年很長嗎
超時空大冒險
天渠
愛不可及
二十歲
你好,之華
冒牌搭檔
鐵甲戰神
克隆人
恐怖快遞
中國藍盔
阿凡提之奇緣歷險
名偵探柯南:零的執行人
為邁克爾·傑克遜鑄造雕像
再見仍是朋友
心迷宮
淡藍琥珀
阿拉姜色
兩個俏公主
雲上日出
誰是壞孩子
照相師
緣·夢
網路謎蹤
龍貓
印度合夥人
綠毛怪格林奇
最萌警探
春天的馬拉松
BeautifulSoup耗時 0.061043500900268555
海王
無名之輩
無敵破壞王2:大鬧網際網路
狗十三
驚濤颶浪
毒液:致命守護者
憨豆特工3
神奇動物:格林德沃之罪
恐龍王
老爸102歲
生活萬歲
進擊的男孩
摘金奇緣
亡命救贖
一百年很長嗎
超時空大冒險
天渠
愛不可及
二十歲
你好,之華
冒牌搭檔
鐵甲戰神
克隆人
恐怖快遞
中國藍盔
阿凡提之奇緣歷險
名偵探柯南:零的執行人
為邁克爾·傑克遜鑄造雕像
再見仍是朋友
心迷宮
淡藍琥珀
阿拉姜色
兩個俏公主
雲上日出
誰是壞孩子
照相師
緣·夢
網路謎蹤
龍貓
印度合夥人
綠毛怪格林奇
最萌警探
春天的馬拉松
re耗時 0.0004856586456298828

程式碼說明

1. lxml

lxml是通過xpath來查詢

使用前需使用呼叫ertee.HTML()方法('()'內填HTML程式碼)生成一個可查詢的物件

常用xpath語法如下

// 兩個斜槓為向下查詢孫子標籤

/ 一個斜槓為查詢直接兒子標籤

[] 方括號內填標籤屬性,如查詢class屬性為name的a標籤,格式為a[@class="name"]

/text() 取出標籤的內容,如查詢網頁中的 <a class="name">KAINHUCK</a> 中的KAINHUCK,格式為//a[@class="name"]/text()

/@attr 取出標籤的屬性,如查詢網頁中的 <a class="name">KAINHUCK</a> 中的class屬性值name,格式為//a[@class="name"]/@class

2. BeautifulSoup

使用前需先將HTML轉換為課查詢物件,格式為

BeautifulSoup(html, 'lxml')

html 為HTML程式碼, 後面的引數為轉換方法(其他方法有'html.parser' , 'html5lib', 推薦使用'lxml')

查詢方法

info = find('a', id='kain') 查詢第一個id屬性為kain的a標籤,並存進info變數中(其他標籤同理)

find_all('a', class_='name') 查詢所有class屬性為name的a標籤(注:class屬性需寫成'class_')

info.p.text 獲取第一個id屬性為kain的a標籤下的p標籤的內容(info為上面例子裡的info,其他同理)

info.p['name'] 獲取第一個id屬性為kain的a標籤下的p標籤的name屬性值(info為上面例子裡的info,其他同理)

當代碼中有很多同級標籤時

<p class='info-list'>
       <a class='name'>text1</a>
       <a class='name'>text2</a>
       <a class='name'>text3</a>
       <a class='name'>text4</a>
   </p>

示例程式碼如下

from bs4 import BeautifulSoup

html = '''
   <p class='info-list'>
       <a class='name'>text1</a>
       <a class='name'>text2</a>
       <a class='name'>text3</a>
       <a class='name'>text4</a>
   </p>
'''
soup = BeautifulSoup(html, 'lxml')
texts = soup.find('p', class_='info-list')
print(texts.contents[1].text)    # 輸出text1
print(texts.contents[2].text)    # 輸出text2
print(texts.contents[3].text)    # 輸出text3
print(texts.contents[4].text)    # 輸出text4

注意:不是從0開始

3. re(正則表示式)

正則表示式內容較多,大家可以參考這裡

總結

使用lxml查詢時可以在目標網頁按F12調出開發者視窗然後再在按Ctrl+f查詢,在查詢欄裡輸入你的xpath語法可以檢查是否能找到對應內容

可以從看例子的輸出中看出三種方法的速度

lxml耗時 0.007623910903930664

BeautifulSoup耗時 0.061043500900268555

re耗時 0.0004856586456298828

對以上三種最常用的解析網頁的方法做個對比

lxml BeautifulSoup re
語法難易度 簡單 簡單 複雜
查詢速度 較快

綜上,對於網頁內容的解析,這裡推薦新手使用lxml方法,而對速度有要求就使用正則表示式(入門有點困難)