Python爬蟲 (2) — Beautiful Soup的網頁爬取技巧

在網頁爬蟲的世界裡,除了要暸解爬蟲程式如何撰寫外,有一個很重要的前提條件,我們必須先暸解我們爬取的對象。換句話說,就是HTML網頁的構造。如果暸解了網頁的構造,在進行爬取資料時,必定可以事倍功半。

網頁HTML文件的構造

有別於Python等程式語言(Programing Language),網頁是由HTML標籤組成的階層式文件。因此HTML又被稱作標記語言(Markup Language)。簡單來說,一個網頁是由HTML、CSS、Javascript三個要素所組成的(可以參考網頁的構成要素)。其中,HTML負責網頁的內容,屬於骨架的部分、CSS處理網頁的樣式,用以美化網頁視覺,而Javascript則負責與使用者互動的各種程序。

HTML標記語言是由各種標籤(tag)所組成像俄羅斯娃娃一般的巢狀結構,每個標籤都以小於(<)大於(>)符號成對的包覆起來,結尾的部分會多一個斜線符號(/)。如下圖,一個HTML頁面可以分兩個區塊,<head>與<body>,而<head>與<body>被<html>標籤給包覆著。

head區塊記載網頁的描述資訊,一般人無法從瀏覽器看到這部分的資訊,裡面包括網頁的標題、編碼方式語系、需要搭配的CSS與Javascript檔案之路徑等等。而body則是網頁文件的本體內容,我們透過瀏覽器看到的部分就屬於<body>與</body>所包覆的所有標籤。當然,這個區塊中的標籤一樣採行巢狀結構,我們可以將這樣子的結構畫成下面的樹狀結構。這樣的結構又稱作DOM(Document Object Model)。DOM 形成的樹狀結構中最重要的就是各個節點(node)。在DOM裡面每個元素、文字等都算是一個節點。爬蟲其實就是在DOM的節點之間來回的遍歷、尋找目標節點,然後擷取目標內容。

使用BeautifulSoup解析網頁

爬取網頁資料大致上可以分成三個步驟。首先我們要與網站溝通,取得文件。接著解析取得的文件,並且依照我們給的條件定位出DOM的節點們。最後再把找到的節點所包含的文字等內容印出(print)。其中第一個步驟,透過request與網站溝通的部分,我們在前一篇已經說明過。這裏要說明的是第二與第三步驟。

匯入模組

首先匯入requests與BeautifulSoup模組。如果這兩個模組先前已經先安裝在Anaconda的虛擬環境中的話,可以直接呼叫使用。

import requestsfrom bs4 import BeautifulSoup

使用BeautifulSoup的語法如下:

bs物件 = BeautifulSoup(原始碼, 解析器)

其中,在解析器(Parser)的部分可以使用lxml、html5lib等套件,或者是使用Python內建的html.parser來解析原始碼。在這裡我們使用html5lib來解析原始碼。

指定要爬的網址

使用requests裡面的get()方法來抓取網址。

url ='https://tw.news.yahoo.com/'html = requests.get(url)

指定網頁的編碼,這裡使用的是utf-8:

html.encoding = 'UTF-8'

然後再使用html5lib的解析器,解讀抓到的內容。

sp = BeautifulSoup(html.text, 'html5lib')

可以試著印出網頁的標題(title)與內文中的標題(h1):

print(sp.title)print(sp.h1.text)

全部程式碼如下:

url ='https://tw.news.yahoo.com/'html = requests.get(url)html.encoding = 'UTF-8'sp = BeautifulSoup(html.text, 'html5lib')print(sp.title)print(sp.h1.text)

常用屬性

上面的程式碼中,我們在sp後面加上title與h1,結果會把title與h1中的內容返回。實際上title與h1也就是DOM的節點。你也可以試試在sp後面加上其他的節點,例如html、h2、p、甚至於可以加上head、body、script、link等看看會有什麼結果?

因此我們可以在sp後面加上各種DOM樹的節點,只要知道我們的目標在哪個節點上,就可以取出來。這就是為什麼暸解HTML構造是很重要的事。

text屬性

text為BeautifulSoup常用的屬性。會傳回指定標籤中的內容(不包含標籤本身的內容)。如上面的例子,從結果可以發現,沒有加上text的話,得到的是包含標籤的全部內容,反之則是返回標籤中包覆的內容。

text屬性

常用方法:find()

find()方法會尋找「第一個符合條件」的標籤,並且以字串(string)的型態回傳。如果找不到,則會回傳None。例如我們想要找到網頁中第一個超連結,可以使用下面的方式:

sp.find('a')

結果返回:

常用方法:find_all()

find_all()方法會尋找「所有符合條件」的標籤,並且以串列的型態回傳。如果找不到,則會回傳None。同上例如我們想要找到網頁中所有的超連結,可以使用find_all的方式:

sp.find_all('a')

結果返回:

我們可以透過一個for回圈,來列出所有a標籤的文字內容:

for a in sp.find_all('a'):
print(a.text)

這樣子看起來就相對簡潔多了。

加入標籤屬性作為搜尋條件

我們在搜尋標籤時,可以再加上其他屬性作為搜尋條件,來縮小搜尋的範圍。

我們可以將屬性作為find與find_all方法的參數。例如:擷取圖片<img>標籤,標籤中含有class為W(100%) H(110px)的所有圖片。

sp.find_all("img", class_="W(100%) H(110px)")

由於class為Python保留字,所以我們在這裡要使用「class_」來表示。輸出結果如下:

又如我們可以寫一個for回圈,列出所有css class 為 nr-applet-nav-item 的<a>標籤。這樣子的話,只有class裡面含有nr-applet-nav-item的超連結標籤,才會被選到。

for item in sp.find_all('a','nr-applet-nav-item'):print(item.text)

另外,我們還可以使用key-value的方式取得網頁中元素資料。

sp.find_all("img", {'class':'W(100%) H(110px)'}

常用方法:select()

select()會尋找指定CSS選擇器(selector)的內容,並且以串列的型態回傳。

我們可以使用select()選取:

tag(標籤)

可以直接設定標籤來選取。例如:

sp.select('title')

id(編號)

由於id在網頁的使用上有一個特性,就是一個頁面裡面的id編號不會重複,必須是唯一的值。因此,我們可以利用這個id的特性來指定我們想要的元素。例如網頁中有個id,它的值為MasterWrap:

<div id="MasterWrap">

我們可以透過下面方式選取這個id:

sp.select('#MasterWrap')

css class(類別名稱)

我們也可以使用css的類別名稱來選擇我們要找的元素。例如網頁中有個class,它的值為List(n):

<li class="List(n)"></li>

我們可以透過下面方式選取網站中含有這個class的元素:

sp.select('.List(n)')

當有多層標籤、id、class的時候,也可以使用select逐層選取。與我們套用css到元素的方法一樣。例如:

sp.select('div span')

表示選取所有<div>元素內的<span>元素。

sp.select('div > span')

上面的例子則表示選取所有直接在<div>元素內的<span>元素,而且兩者之間沒有其他中間元素。這個用法是不是與CSS的用法類似?

--

--

Sean Yeh
Python Everywhere -from Beginner to Advanced

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。