D-LAB Python X 使用者體驗設計

Pyladies Taiwan

Speaker : Mars

2016/12/08

Roadmap

  • 什麼是網路爬蟲、網路爬蟲可以做什麼?
  • 第一隻網路爬蟲
  • HTML 解析

什麼是網路爬蟲、網路爬蟲可以做什麼?

程式設計

幫我們自動化處理事情,減少重複的動作

網路爬蟲

幫我們自動化從網頁上去擷取我們所需要的資料,減少重複的動作

  • 點擊網頁 or 輸入查詢資料
  • 等待網頁開啟
  • 選擇「想要」的內容
  • 存到另外的地方
  • 重覆以上動作

網路爬蟲的應用

  • 截圖(jpg,pdf...)
  • 每天的股票訊息
  • 即時外幣匯率
  • 神奇寶貝出沒通知
  • 查詢轉乘最優惠的機票:skiplagged
  • 圖庫/影片下載器
  • 查詢哪家店面賣的商品最便宜
  • 比對網軍

進階-資料分析

第一隻網路爬蟲

網頁的運作原理

利用 Chrome 的 「開發人員工具」觀察

  • 對著網頁按右鍵 > 檢查 > Network
  • 功能列表 > 更多工具 > 開發人員工具

http://blog.marsw.tw 為例

  • 這個網頁 傳遞資料的方法為 GET

Target:抓取網頁,並取出想要的資料!

  • 取得文章類別
  • 取得文章標題
  • ......etc.
In [21]:
from lxml import etree
import requests

url = "http://blog.marsw.tw"
response = requests.get(url)
html = response.text

fileout = open("test.html","w",encoding='utf8')
fileout.write(html)
fileout.close()

page = etree.HTML(html)
first_article_tags = page.xpath("//footer/a/text()")[0]
print (first_article_tags)

for article_title in page.xpath("//h5/a/text()"):
    print (article_title)
旅遊-日本
2014。09~關西9天滾出趣體驗
2014。05 日本 ~ Day9 旅程的最後~滾出趣精神、令人屏息的司馬遼太郎紀念館
2014。05 日本 ~ 滾出趣(調查11) 道頓堀的街頭運動
2014。05 日本 ~ Day8 鞍馬天氣晴、貴船川床流水涼麵、京都風情
2014。05 日本 ~ 高瀬川、鴨川、祇園之美
from lxml import etree

只從 lxml 工具箱,裝備 etree 這個工具

import requests

裝備 requests 工具箱

url = "http://blog.marsw.tw"

"http://blog.marsw.tw" 這個 <字串 string>
儲存到 我們命名的 url <變數 variable>

response = requests.get(url)

使用 requests 工具箱的 get 工具,
這個工具能幫我們抓下網址為 url 的網頁(傳遞資料的方法為 GET )上的資料,
把這些資料存到 我們命名的 response <變數 variable>

html = response.text

response 屬於 text 的資料,也就是網頁原始碼(response是用get抓下來的網頁資料)
存到 我們命名的 html <變數 variable>

檔案輸出

  • 先把檔案存起來,之後要拿來使用的時候就不需要再重爬
    • 網路太慢、沒有網路
    • 網頁的資料量太多
    • 方便用我們習慣的軟體開啟瀏覽
fileout = open("test.html","w",encoding='utf8')

open是讓我們開啟一個檔案的工具,而這個檔案名稱我們叫做test.html
而這個檔案是用來「寫入資料」,因此要加上w,不加的話會是用來「讀取檔案」。
我們把這個工具存在 fileout這個我們命名的變數中。 後面的encoding參數,可以避免windows的cp950編碼錯誤。

fileout.write(html)

然後使用 write 功能將 存在變數 html 的資料寫入(會寫在 test.html 中)

fileout.close()

最後,怕同時間有其他人/程式也一起使用這個檔案,
會造成檔案的內容被影響,所以我們在程式中以close功能關閉這個檔案,
至少現階段這個程式不會再影響檔案了。

在跟程式同一個目錄底下,會看到一個test.html的檔案

開啟檔案後,點選Download可以下載檔案

下載後以瀏覽器開啟,會發覺用程式抓下來的就是跟網址上一模一樣的網頁

Coding Time

更換 url 來抓下不同的網頁看看吧!

from lxml import etree
import requests

url = "http://blog.marsw.tw"
response = requests.get(url)
html = response.text

fileout = open("test.html","w",encoding='utf8')
fileout.write(html)
fileout.close()

HTML 解析

page = etree.HTML(html)

把變數html儲存的資料(網頁抓下來的原始碼),
以 工具etreeHTML功能,轉換成「XPath的節點(node)型態」,
並儲存到變數page中。

範例程式第一行宣告的 lxml 工具箱的 etree 工具,終於要用到啦!

from lxml import etree

HTML(Hyper Text Markup Language)

  • 右鍵 > 檢視網頁原始碼
  • 是用來描述網頁的一種標記語言 (markup language)
  • 由一堆標記標籤(markup tag)所構成

常見標籤(tag)

  • 連結<a href="">連結文字</a>
  • 圖片<img src=""/>
  • 內文標題<h1>~<h6>
  • 換行<br>

範例網頁

<html>
    <head>
        <title>Title</title>
    </head>
    <body>
        <h1>Subtitle</h1>
        <a href="http://tw.pyladies.com/">PyLadies Website</a>
        <p>
            This is a paragraph <br>
            <a href="http://www.meetup.com/PyLadiesTW/">PyLadies Meetup</a> <br>
            <a href="https://www.facebook.com/pyladies.tw">PyLadies FB</a> <br>
        </p>
        <img src="http://tw.pyladies.com/img/logo2.png" width="99px"/>
    </body>
</html>

Nodes

  • 小孩/:「下一層節點」,或是該標籤的「屬性 @」或「文字 text()
  • 子孫//:小孩、小孩的小孩、小孩的小孩的小孩......etc.

範例

  • body
    • 小孩:h1、a(Website)、p、img
    • 子孫:h1、text()、a、@href、p、img、@src、@width
  • a
    • 小孩:@href、text()
<html>
    <head>
        <title>Title</title>
    </head>
    <body>
        <h1>Subtitle</h1>
        <a href="http://tw.pyladies.com/">PyLadies Website</a>
        <p>
            This is a paragraph <br>
            <a href="http://www.meetup.com/PyLadiesTW/">PyLadies Meetup</a> <br>
            <a href="https://www.facebook.com/pyladies.tw">PyLadies FB</a> <br>
        </p>
        <img src="http://tw.pyladies.com/img/logo2.png" width="99px"/>
    </body>
</html>
first_article_tags = page.xpath("//footer/a/text()")[0]

前面的程式碼,我們已經讓名稱page的紀錄是「XPath的節點(node)型態」,

這邊的程式碼是用xpath找尋
整個文件中的子孫 // , 所有是 footer 標籤,
且小孩是 a 標籤的文字 text()的結果(為一串列),
而我們只取「第1個」結果,存到變數 first_article_tags 中。

print (first_article_tags)

將 名稱 first_article_tags的資料,印到螢幕上。

!注意

page.xpath("//footer/a/text()")[0]

這裡的[0]是在xpath()之外,是屬於Python的語法,
而Python的串列索引是以0開始數,所以[0]是取「第1個」結果。

XPath

In [7]:
from lxml import etree

# 這邊先不使用request去抓,直接將原始碼存到 變數 html 中,方便大家理解
# 遇到長篇文章有換行的存在,可用「三個」雙引號或單引號
html = """
<html>
<head>
  <title>Title</title>
</head>
<body>
  <h1>Subtitle</h1>
  <a href="http://tw.pyladies.com/">PyLadies Website</a>
  <p>
      This is a paragraph <br>
      <a href="http://www.meetup.com/PyLadiesTW/">PyLadies Meetup</a> <br>
      <a href="https://www.facebook.com/pyladies.tw">PyLadies FB</a> <br>
  </p>
  <img src="http://tw.pyladies.com/img/logo2.png" width="99px"/>
</body>
</html>
"""
page = etree.HTML(html)
link_text_list = page.xpath("//a/text()")
link_text_p_list = page.xpath("//p/a/text()")
link_list = page.xpath("//a/@href")

print (link_text_list)
print (link_text_p_list)
print (link_list)
['PyLadies Website', 'PyLadies Meetup', 'PyLadies FB']
['PyLadies Meetup', 'PyLadies FB']
['http://tw.pyladies.com/', 'http://www.meetup.com/PyLadiesTW/', 'https://www.facebook.com/pyladies.tw']

Coding Time

  • 使用串列功能,印出網頁中第2個連結網址,也就是"PyLadies Meetup"的網址
  • 使用串列功能,印出網頁中所有連結的文字
  • 印出網頁的圖片網址

抓取網頁上的圖片

from lxml import etree
import requests

url = "http://blog.marsw.tw"
response = requests.get(url)
html = response.text

page = etree.HTML(html)

for img_src in page.xpath("//img/@src"):
    img_response = requests.get(img_src)
    img = img_response.content

    filename=img_src.split("/")[-1]
    filepath="tmp/"+filename
    fileout = open(filepath,"wb")
    fileout.write(img)

注意:圖片的內容不是文字!

  • 所以用的是 content ,而不是像之前抓網頁的時候是用 text ,是取得bytes型式的內容
  • 寫檔的時候,因為取得的是bytes型式的內容,因此用的是 wb ,而不是 w
  • 因為會抓下很多圖片,所以想用另外一個資料夾「tmp」存放,記得要先建立「tmp」資料夾,否則程式會出錯!
In [22]:
img_src = "http://1.bp.blogspot.com/-lP9M5nJ-kb0/U3nAOYRLCAI/AAAAAAAAUaw/1SbrOZBwz3g/s1600/2014-05-01%2B22.26.27.jpg"
print (img_src.split("/"))
print (img_src.split("/")[-1])
['http:', '', '1.bp.blogspot.com', '-lP9M5nJ-kb0', 'U3nAOYRLCAI', 'AAAAAAAAUaw', '1SbrOZBwz3g', 's1600', '2014-05-01%2B22.26.27.jpg']
2014-05-01%2B22.26.27.jpg

函式 Function

from lxml import etree
import requests

url = "http://blog.marsw.tw"
response = requests.get(url)  #
html = response.text          #

page = etree.HTML(html)

for img_src in page.xpath("//img/@src"):
    img_response = requests.get(img_src) #
    img = img_response.content           #

    filename=img_src.split("/")[-1]
    filepath="tmp/"+filename
    fileout = open(filepath,"wb")
    fileout.write(img)
In [ ]:
from lxml import etree
import requests

def get_html(url,content_type):
    response = requests.get(url)
    if content_type=="html":
        html = response.text
    else:
        html = response.content 
    return html

html = get_html("http://blog.marsw.tw","html")
page = etree.HTML(html)

for img_src in page.xpath("//img/@src"):
    img = get_html(img_src,"img")

    filename=img_src.split("/")[-1]
    filepath="tmp/"+filename
    fileout = open(filepath,"wb")
    fileout.write(img)

觀察技巧

好用 Chrome 擴充功能

利用 Chrome 的 「開發人員工具」觀察

  • 對著想要的元素按右鍵>檢查
  • 得知我們想要的資料對應到的原始碼是哪一區段
  • 還可以使用 Copy XPath 快速得知這個元素的 Xpath 語法
    • 對該區段原始碼點右鍵 > Copy > Copy XPath

利用 XPath Helper 來看看會抓到哪些資訊

網誌第一篇、第二篇文章標題的XPath分別是

//*[@id="Blog1"]/div[1]/div[1]/div/div[1]/article/header/h5/a
//*[@id="Blog1"]/div[1]/div[1]/div/div[2]/article/header/h5/a
  • 提取共同的部分 //article/header/h5/a
  • 試試看最短的XPath寫法 //h5/a

Coding Time

More~

  • 瞭解更多的XPath語法 //*[@id="Blog1"]/div[1]/div[1]/div/div[1]/article/header/h5/a
  • 想用某些網頁內的選單查詢功能 -> POST
  • 有些網頁會需要年齡檢測或是需要登入才能進入 -> cookie
  • 抓取JavaScript產生的網頁內容
  • 處理需要登入、驗證碼的網頁
  • ...etc.

請關注 PyLadies 的 「從0到1的網頁爬蟲」系列活動

  • 3rd:POST、cookie
    • 12/14(三) 19:30~21:30:台北
    • 12/17(六) 09:30~11:30:高雄
    • 12/28(日) 14:30~16:30:台中