iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 24
0
自我挑戰組

用Line聊天機器人串起多媒體系統系列 第 24

Day 24 : Python 常用網路資料擷取術

  • 系列文走到此時,您對 Python 及 LINE 聊天機器人開發有些認識,在入門開發的過程中,一方面也肯定自己實作的成果,也許您心中也有更多的疑惑在於實踐。本篇介紹如何擷取您所需的網頁資訊,為後續開發功能鋪路。
  • Colab 支援

Python 資料擷取術

什麼是網頁

  • 網頁的基本組成: HTML + CSS + JavaScript
    • HTML: 主要為定義網頁的內容、結構。
    • CSS: 主要為設定顯示的風格Style
    • JS: 行為...
  • HTML是階層式文件結構,由許多元素(Elements)組成
    • 一個元素包含開始標籤、結束標籤、屬性及內容,例如: <Tag 屬性>內容</Tag>
    • 常用標籤
      標籤名稱 用途
      <h1> ~<h6> 標題
      <p> 段落
      <a href="https://www.123.com"> 超連結
      <table> 表格
      <tr> 表格內的row
      <td> 表格內的cell
      <br/> 換行(無結束標籤)
    • 常用屬性(Attributes)
      屬性名稱 用途
      class 標籤的類別(可重複)
      id 標籤的id(不可重複)
      title 標籤的顯示資訊
      style 標籤的樣式
      data-* 自行定義的屬性

擷取網頁必要知識

  • 在HTTP協定中,定義了多種不同的method做為服務的請求方法,近年來由於行動裝置的普及化,越來越多的產品及網站都提供了WebAPI服務,既然我們要擷取網頁內容,就必須知道對HTTP請求方式。
  • 在HTTP 1.1的版本中定義了八種 Method (方法),如下所示:
    • OPTIONS
    • GET
    • HEAD
    • POST
    • PUT
    • DELETE
    • TRACE
    • CONNECT
  • 常見的method為以下5種:
    method 意義
    GET 取得(想要的服務)的資料或是狀態。
    POST 如同填表般的行為,以新增一項資料。
    PUT 利用更新的方式於"指定位置"新增一項資料。
    PATCH 在現有的資料欄位中,增加或部分更新一筆新的資料。
    DELETE 刪除指定資料。
  • 更進一步了解請參閱W3C制定規範RFC 5789。另外在網頁與"資料庫"的操作過程中,也會經常聽到CRUD這個詞,CRUD是指新增(Create)、讀取(Read)、更新(Update)、刪除(Delete)的主要4個操作資料庫(如MySQL等)常用的功能。

淺談Restful API

  • REST 全名 Resource Representational State Transfer,可譯為具象狀態傳輸,其核心精神在於借用 HTTP 協定做為基礎,讓API規格簡單一致:
    • Resource:資源。
    • Representational:表現形式,如JSON,XML...
    • State Transfer:狀態變化。即上述講到的可利用HTTP動詞們來做呼叫。
  • 舉例運用RESTful API 開發的WebAPI的interface:
    • 獲取商品資料 /GET/items/9527
    • 新增商品資料 /POST/items
    • 更新商品資料 /PATCH/items/9527
    • 刪除商品資料 /DELETE/items/9527

什麼是網路爬蟲(Web Crawler)

  • 網路爬蟲像是機器人,自動化的幫你擷取目標資訊,您熟知的 Google 服務也是基於網頁擷取/搜尋而產生的服務。
  • 爬蟲應用相當多元,可以用來蒐集熱門景點評論、輿論分析系統、銷售分析、旅遊訂票等...
  • 在寫爬蟲之前,要注意的:
    • 有沒有人寫過? 有的話可以減少重工。
    • 該網站是否已經有 API 供人取用?
    • 要有禮貌(大量、頻繁的請求會造成伺服器負荷)。

如何做一個有禮貌的爬蟲

  • 爬取網站資料時,請勿過於頻繁的索取資料,善用 time.sleep() 增加間隔,如:
    • 固定3秒執行:
      import time
      
      print('----start----')
      time.sleep(3)
      print('----done----')
      
    • 隨機範圍時機執行:
      import time
      import random
      
      random_s = 1 + random.randint(0,2) #加入隨機秒數
      print('----start----')
      time.sleep( random_s)
      print('----done----')
      
  • 經過 SEO 的網站可能有允許/禁止爬取的頁面規範,可至該網站網域/robots.txt查看,如https://www.facebook.com/robots.txthttps://twitter.com/robots.txt 。都有寫明禁止爬取之處。
  • robots.txt 只是表明不要到網站這些地方,許多 Web Scraping 自動化工具及服務會遵循(也可以關掉預設值)。
  • 另外也請注意智慧財產權( Intellectual Property, IP ),像是商標、著作權、專利,如果有未獲同意、實際傷害及故意,則有觸法之虞。

被 Ban 怎麼辦?

  • 為了避免頻繁請求被目標伺服器阻擋,測試爬蟲時可採用你的手機( 4G / 5G)網路,如果被 ben,手機改飛航模式一陣子再開 4G 網路,即會在自動分配(取得)新的 IP Address ,或依您所請求的伺服器設定過陣子試試看解鎖沒。

開始動手做GET網頁

  • 以 example 網頁為例
    • 先觀察目標網頁: http://www.example.com/
    • requests.get() 抓取網頁原始碼,如您尚未安裝 requests 模組,請先在終端機執行 pip install requests ,記得結尾有 s 唷。
    • 程式碼執行及輸出結果如下:
      import requests
      
      res = requests.get('http://www.example.com/')
      print(res.text[:500])
      
  • Requests 常用函數
    • response.status_code
      • 200 OK
      • 403 Forbidden (禁止)
      • 404 Not Found
    • response.encoding
      • 如果是中文網站要特別注意編碼的問題。
      • 常用編碼 UTF-8 ,windows可能會遇到 CP950 、 Big5 等編碼問題。
    • response.text
      • 目標網頁的HTML文字,即被 Tag 包起來的內文(目標資訊)。
        res.encoding
        #輸出 UTF-8
        

以 Beautiful Soup 讀取並解析 HTML

  • 參閱Beautiful Soup 文件(此時最新版本為 4.9.0 )。

  • Beautiful Soup 是強大的 HTML 解析器,可創建一個 BeautifulSoup 物件,將網頁讀入。此時soup的 datatype 為 bs4.BeautifulSoup 物件,此物件中包含了整個 HTML 文件的結構樹,有了這個結構樹之後,就可以輕鬆找出任何有興趣的資料了。如尚未安裝請執行 pip install beautifulsoup4 安裝模組,如為筆記本環境請加魔術指令 !

    from bs4 import BeautifulSoup 
    
    soup = BeautifulSoup(res.text, "lxml")
    type(soup)
    #輸出 bs4.BeautifulSoup
    
  • 下表列出了主要的超文本解析器,以及它們的優缺點:

    解析器 使用方法 優勢 劣勢
    Python 標準庫 BeautifulSoup(markup,"html.parser") Python的內建標準庫、執行速度適中、文檔容錯能力強 Python 2.7.3及3.2.2之前的版本中文檔容錯能力差
    lxml HTML 解析器 BeautifulSoup(markup, "lxml") 速度快、文檔容錯能力強(通常用這個) 需要安装C語言庫
    xml XML 解析器 BeautifulSoup(markup , "xml") 速度快、唯一支持XML的解析器 需要安装C語言庫
    html5lib BeautifulSoup(markup, "html5lib") 最好的容錯性、以瀏覽器的方式解析文檔、生成HTML5格式的文檔 速度較慢、不依賴外部模組
  • 輸出排版後的 HTML 程式碼

    print(soup.prettify())
    

BeautifulSoup的常用函數

  • soup.find()

    • 找一個標籤 tag,將回傳第一個被 tag 包圍的區塊,例如soup.find('p')

    • 傳入的參數第一個通常是 tag 名稱,第二個引數若未指明屬性就代表 class 名稱,也可以直接使用 id 等屬性去定位區塊。定位到區塊後,可以取出其屬性與包含的字串值,接受的參數為soup.find(name=None, attrs={}, recursive=True, text=None, **kwargs)

    • 以下程式顯示a標籤內容、a標籤的href屬性內容,以及a標籤的文字內容:

      import requests
      from bs4 import BeautifulSoup 
      
      res = requests.get('http://www.example.com/')
      soup = BeautifulSoup(res.text, "lxml")
      a = soup.find("a")
      
      print(a) #<a href="https://www.iana.org/domains/example">More information...</a>
      print(a["href"]) #https://www.iana.org/domains/example
      print(a.text) #More information...
      
    • 以下程式將 title 標籤抓出來,用 title.string 抓出其內容

      title_tag = soup.title
      print(title_tag) #<title>Example Domain</title>
      print(title_tag.string) #Example Domain
      
    • 取出節點屬性

      • 若要取出 HTML 節點的各種屬性,可以使用 get,如果不用get也可以擷取屬性,但不存在時會出現錯誤,有礙後續爬蟲執行。使用get如無此屬性,回傳結果為none。其他詳細用法可參考 BeautifulSoup的官方文件
        • 會報錯停止執行的例子
        • 如使用get()``,未搜尋到的結果回傳為None`,不會報錯終止。
  • soup.find_all(),我全都要!

    • 回傳全被tag包圍的區塊,回傳為串列 list。
      import requests
      from bs4 import BeautifulSoup 
      
      res = requests.get('http://www.example.com/')
      soup = BeautifulSoup(res.text, "lxml")
      p_tags = soup.find_all("p")
      
      print(p_tags)
      
    • 搜尋節點並從 list 取出內容,就需要 for 迴圈囉:
      p_tags = soup.find_all("p")
      
      for tag in p_tags:
         print(tag.string)
      
    • 以 list 同時搜尋多種標籤,搜尋結果也是 list: soup.find_all(["h1","p","a"]) :
    • limit 參數限制搜尋數量,如: soup.find_all("p", limit=2) ,只有1個就可以改為 find() 就好。
    • 不指定標籤,但找出所有屬性 class = "zzz" 的標籤,建議爾後搜尋屬性都遵循此用大括號{k:v}的樣式,如 soup.find_all("", {"class":"zzz"})
    • 找出所有內容等於 Example Domain 的文字 soup.find_all(text="Example Domain"):
  • soup.select()

    • soup.slect() 的選取方式跟 jQuery 十分相似,是基於 CSS 的搜尋,如果您有 jQuery 基礎會學得相當親切亦用。
    • 如果要搜尋 class 就用 . ;搜尋 id 就用 #
    • 如果是非 classid 的屬性,用中括號 [] 填入要搜尋的[屬性名稱]、或[屬性名稱及其內容]
      import requests
      from bs4 import BeautifulSoup 
      
      res = requests.get('http://www.example.com/')
      soup = BeautifulSoup(res.text, "lxml")
      
      select_a = soup.select("a")
      select_href1 = soup.select('[href]')
      select_href2 = soup.select('[href="https://www.iana.org/domains/example"]')
      
      #以下輸出結果一致,皆為[<a href="https://www.iana.org/domains/example">More information...</a>]
      print(select_a)
      print(select_href1)
      print(select_href2)
      
    • 如果要取出屬性內容,皆以 Python 的 list 操作方式,舉例如select_a[0]["href"]

結合正規表達式 Regular expression 進行搜尋

  • 正規表達式對於精準抓取網頁的各種標籤及內文非常有幫助,解決了許多Xpath與CSS selector無法精確擷取的問題,有必要好好理解。

  • 擷取的文句段落可以使用regex101嘗試,該網站亦可搜尋別人寫好的正規表達式。

    意義 表示 範例
    Start ^ 123ABC /^1/
    End $ 123ABC /5$/
    Range [<Start>-End>] 123ABC /^[0-2]/
    Number \d 123ABC /^\d/
    Character \w 123ABC /\w$/
    Invisible Character \s Tab, Space, Escape, …
    Zero or One +
    Zero or More * 123ABC /\w+$/
    One or More ? 123ABC /[0-2]/
    Named Group (?P<name>expression)
    Named Group (?<name>expression)
  • Python 的正規表達是可使用內建的 re 模組:

    • 推薦使用 re.findall() 函數。
    • 常用參考寫法:
      import re
      
      pattern = "我寫好的 regular expression" 
      string = "我想要找的字串" 
      re.findall(pattern, string)
      
    • 尋找超連結的範例:
      import requests
      import re
      
      res = requests.get('http://www.example.com/')
      
      #找出html裡的超連結 
      pattern = r'href=\"(.*)\"|href=\'(.*)\'' #參閱https://regex101.com/r/uw6MLH/1
      string = res.text
      re.findall(pattern, string)[0][0]
      

小結

這篇文章主要介紹網頁資料擷取常用的 requests 及 的 soup.find()soup.find_all()soup.select() 用法、正規表達式的 re.findall() 模組,已經可以實踐抓取許多網頁,但網路資料擷取/爬蟲基本上都要針對不同網站下功夫處理,時常伺服器為了反爬蟲也在更動屬性/標籤,下篇會包含實作,我們下篇見!


上一篇
Day 23 : DialogFlow X LINE Chat BOT - 實作篇
下一篇
Day 25 : 用 Python 擷取 PTT 、 匯率及熱門迷因圖實作
系列文
用Line聊天機器人串起多媒體系統30

尚未有邦友留言

立即登入留言