輕鬆學習 Python:在學習網站爬蟲之前

The world’s most valuable resource is no longer oil, but data.——The Economist

摘要

網站爬蟲的核心任務可以簡單區分為兩個,依序是請求資料(requesting data)與解析資料(parsing data),在進行第二項任務時常需要 HTML、XPath 與 CSS Selector 的相關知識。這個小節將簡介前述三項觀念,讓沒有網頁前端基礎的讀者能將之應用於未來解析資料的任務中。

HTML 與她的樹狀結構

HTML,全名 HyperText Markup Language,譯作超文本標記語言,常與CSS 以及 JavaScript 被用於設計網頁(Webpages,泛指靜態、不隨使用者帳戶更動的網站)與網頁應用程式(Web Applications,泛指動態、隨使用者帳戶更動的網站)之使用者介面,瀏覽器(Chrome、Firefox 與 Safari 等)皆可以讀取 HTML 的檔案,並將其呈現為我們雙眼所接收到的網頁內容。


在與 CSS 和 JavaScript 的分工上頭,HTML 是負責描述一個網站的結構,用許多的標記(Tags,外觀像是 </>)來讓瀏覽器得知標題、段落或表格等應該座落在網頁的哪個位置,因此它常被強調是一種標記語言(Markup Language)而非程式語言(Programming Language)。


HTML 的內容符合樹狀結構,在這個簡單的 CodePen 範例中我們可以觀察到三個層次的標記:


  • 第一層是 <html>...</html>

  • 第二層是 <head>...</head> 與 <body>...</body>

  • 第三層是 <title>...</title> 、 <h1>...</h1> 與三組 <p>...</p>


善加利用 HTML 的樹狀結構搭配 Python 的套件(以 BeautifulSoup4 為例),就能完成初階的解析資料任務。

from bs4 import BeautifulSoup


html_str = """

<html>

 <head>

   <title>The Avengers</title>

 </head>

 <body>

   <h1>Movies</h1>

   <p>The Avengers (2012)</p>

   <p>Avengers: Age of Ultron (2015)</p>

   <p>Avengers: Infinity War (2018)</p>

   <p>Avengers: Endgame (2019)</p>

 </body>

</html>

"""


soup = BeautifulSoup(html_str)

print(soup.find("h1").text)

for movie_title in soup.find_all("p"):

   print(movie_title.text)

## Movies

## The Avengers (2012)

## Avengers: Age of Ultron (2015)

## Avengers: Infinity War (2018)

## Avengers: Endgame (2019)

HTML 標記中的屬性

除了解析出 HTML 標記中所記錄的文字資訊,有時我們也會需要標記中的屬性(Attributes),像是 <a>...</a> 標記中的 href 或 <img> 標記中的 src 都能協助取得連結或圖片的網路位址,除了特定標記獨有的屬性以外,尚有 id 與 class 兩種大眾化的屬性,可以將標記分門別類,這也讓解析特定資料任務變得更加簡單,通常 id 用作區隔單一個標記, class 用作區隔同一組標記。


在這個簡單的 CodePen 範例中我們可以觀察到三種標記屬性:


  • id 用來標註不同的 <div>...</div> 、 <li>...</li>

  • class 用來標註三組 <li>...</li>

  • href 是 <a>...</a> 的連結網址屬性


善加利用 HTML 標記中的屬性搭配 Python 的套件(以 BeautifulSoup4 為例),就能完成初階的解析資料任務。

from bs4 import BeautifulSoup


html_str = """

<html>

 <head>

   <title>The Avengers</title>

 </head>

 <body>

   <div id="header">

     <h1>Movies</h1>

   </div>

   <div id="movie-titles">

     <ul>

       <li id="movie-0" class="movies">

         <a href="https://www.imdb.com/title/tt0848228">The Avengers (2012)</a>

       </li>

       <li id="movie-1" class="movies">

         <a href="https://www.imdb.com/title/tt2395427">Avengers: Age of Ultron (2015)</a>

       </li>

       <li id="movie-2" class="movies">

         <a href="https://www.imdb.com/title/tt4154756">Avengers: Infinity War (2018)</a>

       </li>

       <li id="movie-3" class="movies">

         <a href="https://www.imdb.com/title/tt4154796">Avengers: Endgame (2019)</a>

       </li>

     </ul>

   </div>

 </body>

</html>

"""


soup = BeautifulSoup(html_str)

print(soup.find(id="movie-3").find("a").get("href")) # Link to Avengers: Endgame (2019)

## https://www.imdb.com/title/tt4154796

XPath

XPath,全名 XML Path Language,譯作 XML 路徑語言,與 CSS Selector 常用來定位 HTML 檔案中某部分資訊的位置,如果我們熟悉 HTML 的樹狀結構,對於使用 XPath 將不太會遭遇到困難。


簡單的 XPath 宣告可能由下列幾個元件所組成一個路徑:


  • 單個正斜線 /:用來在樹狀結構中向下移動一層

  • 標記名稱:置放於正斜線之間用來定位資料

  • 中括號 []:用來指向同一個標記中的特定資料

  • 兩個正斜線 //:用來指向某個標記下的所有指定標記

  • 星號 * :代表任意標記

  • @ :用來指定屬性,例如 id 、 class 或 href 等

  • text() :用來指定標記中所涵蓋的資料,即 <tag>...</tag> 中的 ...


在下方的 CodePen 範例中我們可以用兩個 XPath 定位同一筆資料 。


  • /html/body/p[4]/text()

  • //p[4]/text()

from lxml import etree


html_str = """

<html>

 <head>

   <title>The Avengers</title>

 </head>

 <body>

   <h1>Movies</h1>

   <p>The Avengers (2012)</p>

   <p>Avengers: Age of Ultron (2015)</p>

   <p>Avengers: Infinity War (2018)</p>

   <p>Avengers: Endgame (2019)</p>

 </body>

</html>

"""


tree = etree.HTML(html_str)

print(tree.xpath("/html/body/p[4]/text()")[0])

print(tree.xpath("//p[4]/text()")[0])

## Avengers: Endgame (2019)

## Avengers: Endgame (2019)


在下方的 CodePen 範例中我們可以用兩個 XPath 定位同一筆資料。


  • /html/body/div[2]/ul/li[4]/a/@href

  • //a/@href[4]

from lxml import etree


html_str = """

<html>

  <head>

    <title>The Avengers</title>

  </head>

  <body>

    <h1>Movies</h1>

    <p>The Avengers (2012)</p>

    <p>Avengers: Age of Ultron (2015)</p>

    <p>Avengers: Infinity War (2018)</p>

    <p>Avengers: Endgame (2019)</p>

  </body>

</html>

"""


tree = etree.HTML(html_str)

print(tree.xpath("/html/body/p[4]/text()")[0])

print(tree.xpath("//p[4]/text()")[0])

## https://www.imdb.com/title/tt4154796

## https://www.imdb.com/title/tt4154796

CSS Selector

CSS,全名 Cascading Style Sheets,譯作層疊樣式表或階層式樣式表,用來為網頁添加樣式,亦常被強調是一種樣式表語言(Style Sheet Language)而非程式語言(Programming Language)。CSS 運用屬性與選擇器來影響 HTML 中標記在瀏覽器中被呈現出來的外觀,例如在先前的 HTML 範例中添加一個 CSS 檔案,將 <h1>...</h1> 的文字顏色更改為紫色、將 <p>...</p> 的文字顏色更改為紅色。


或者在另一個 HTML 範例中添加一個 CSS 檔案,將第四個 <a>...</a> 的文字大小更改為 28px。


CSS Selector 指的便是大括號 {} 前所宣告的定位,簡單的 CSS Selector 可能由下列幾個元件所組成,與 XPath 有異曲同工之妙:


  • 單個大於符號 >:用來在樹狀結構中向下移動一層(功能與 XPath 中的 / 相同)

  • 標記名稱:置放於大於符號之間用來定位資料

  • :nth-of-type():用來指向同一個標記中的特定資料(功能與 XPath 中的 [] 相同)

  • 空格:用來指向某個標記下的所有指定標記(功能與 XPath 中的 // 相同)

  • 星號 *:代表任意標記

  • 浮點 .:用來指定 class 屬性(功能與 XPath 中的 @ 功能相同)

  • 井字號 #:用來指定 id 屬性(功能與 XPath 中的 @ 功能相同)

  • ::text :用來指定標記中所涵蓋的資料,即 <tag>...</tag> 中的 ...(功能與 XPath 中的 text() 相同)

在下方的 CodePen 範例中我們可以用兩個 CSS Selector 定位同一筆資料 。

  • html > body > p:nth-of-type(4)

  • p:nth-of-type(4)


## Avengers: Endgame (2019)

## Avengers: Endgame (2019)


from bs4 import BeautifulSoup


html_str = """

<html>

 <head>

   <title>The Avengers</title>

 </head>

 <body>

   <h1>Movies</h1>

   <p>The Avengers (2012)</p>

   <p>Avengers: Age of Ultron (2015)</p>

   <p>Avengers: Infinity War (2018)</p>

   <p>Avengers: Endgame (2019)</p>

 </body>

</html>

"""


soup = BeautifulSoup(html_str)

print(soup.select('html > body > p:nth-of-type(4)')[0].text)

print(soup.select('p:nth-of-type(4)')[0].text)

在下方的 CodePen 範例中我們可以用兩個 CSS Selector 定位同一筆資料。


  • html > body > div:nth-of-type(2) > ul > li:nth-of-type(4) > a

  • li:nth-of-type(4) > a


from bs4 import BeautifulSoup


html_str = """

<html>

 <head>

   <title>The Avengers</title>

 </head>

 <body>

   <div id="header">

     <h1>Movies</h1>

   </div>

   <div id="movie-titles">

     <ul>

       <li id="movie-0" class="movies">

         <a href="https://www.imdb.com/title/tt0848228">The Avengers (2012)</a>

       </li>

       <li id="movie-1" class="movies">

         <a href="https://www.imdb.com/title/tt2395427">Avengers: Age of Ultron (2015)</a>

       </li>

       <li id="movie-2" class="movies">

         <a href="https://www.imdb.com/title/tt4154756">Avengers: Infinity War (2018)</a>

       </li>

       <li id="movie-3" class="movies">

         <a href="https://www.imdb.com/title/tt4154796">Avengers: Endgame (2019)</a>

       </li>

     </ul>

   </div>

 </body>

</html>

"""

soup = BeautifulSoup(html_str)

print(soup.select('html > body > div:nth-of-type(2) > ul > li:nth-of-type(4) > a')[0].get("href"))

print(soup.select('li:nth-of-type(4) > a')[0].get("href"))


## https://www.imdb.com/title/tt4154796

## https://www.imdb.com/title/tt4154796

小結

在這個小節中我們簡介 HTML 以及如何透過 XPath 與 CSS Selector 定位 HTML 檔案中的資料,藉此幫助在之後實踐網站爬蟲的第二項核心任務解析資料(parsing data)時能夠更得心應手。

YOTTA 你最專業的學習夥伴,提供優質內容與有趣觀點,擴大豐富你的視野。