爬蟲實戰 - Using Python3 Part2

Level1: 基本處理

這題的目標是下載網站內容處理成json格式,並沒有任何的防禦機制,因此相對來講非常簡單,我們的步驟如下:

  1. 下載網頁並轉換成DOM 格式
  2. 解析、處理網頁內容
  3. 儲存成json 格式

因為是第一個實戰題,所以我會盡量詳細說明。

首先我們必須讓python 直譯器能以正確的編碼處理我們的程式碼內容以及程式內所出現的中文字,因此我們必須在開頭加上

1
# -*- coding: utf8 -*-

接著必需import 我們會使用到的套件,其中的etree 是為了讓我們把下載下來的網頁原始碼轉成element tree的格式,方便我們之後走訪節點抓取資料

1
2
from lxml import etree, html
import requests, json

準備工作結束後就可以開始來寫我們的main function了,首先我們先把目標網站下載下來:

1
2
result = requests.get("http://axe-level-1.herokuapp.com/")
result.encoding = 'utf8'

result.encoding = ‘utf8’ 這行非常重要,透過requests 所下載的內容皆為unicode,而unicode 是為了讓程式語言可以處理各式各樣編碼而誕生出來的概念,python 的字串有兩種型態,分別是unicode 及str,若想了解更詳細的內容可以直接參考這篇文章

回歸正題,由於unicode 無法直接處理,所以我們要把取得的內容編碼成utf8才會是我們平常看到的中文字,但你可能會好奇為什麼是utf8而不是big5 ? big5同樣也是中文字啊!!!

這就要從網頁的原始碼去解釋,打開瀏覽器看到 <head> 裡面的 <meta> 標籤可以知道這個網站是用utf8編碼的,所以我們當然要從unicode 編碼成 utf8
axe-meta

接下來我們需要把所有的網頁原始碼轉成element tree的結構:

1
root = etree.fromstring(result.content, etree.HTMLParser())

etree.HTMLParser() 是在告訴lxml必須以HTML的格式來解析

當資料都處理成element tree之後,我們只需要依照題目需求抓取資料即可,題目的要求是需要拿出每一列的學生資料並且依據特定格式排序,因此我們先打開瀏覽器的 開發人員工具 找到我們的目標位置:

初步了解後發現我們需要抓取的資料是從 <table class="table"> 下的第2個 <tr> 抓到最後一個 <tr>

1
root.xpath("//table[@class='table']/tr[position()>1]")

xpath 是一種針對XML 文檔路徑表達式的方法,這一行的意思是說我們從整個網頁的根節點開始尋找<table>,這個 table的class 名稱必須是 ‘table’,找到 <table>之後繼續往這個節點的子節點開始尋找 <tr>,但只需要從第二個 <tr>開始尋找即可。

若你對其他更多的xpath 寫法有興趣,可以參考W3School的說明

接著我們必須取得每個<tr>內的<td>值,題目需求的格式如下:

1
[{"name": "王小明", "grades": {"國語": 90, "數學": 89, ...}}, ... ]

因此我們必須從剛剛抓到的資料在往下一層分析:

1
2
3
for row in root.xpath("//table[@class='table']/tr[position()>1]"):
column = row.xpath("./td/text()")
tmp= '{"name": "%s", "grades": {"國語": %s, "數學": %s, "自然": %s, "社會": %s, "健康教育": %s}},' % (column[0], column[1], column[2], column[3], column[4], column[5])

透過for 迴圈把每一個<tr> 都獨立成一個變數 row,再對其中一個<tr>內的所有<td>進行走訪並且取得該<td> 內的值,因為每個<tr> 內都有五個<td>,分別是: 姓名、國語、數學、自然、社會以及健康教育, 所以再透過字串組合把該位學生的資料組合起來並且存成變數: tmp

最後只要把每次迴圈生成的 tmp 變數組合成答案就可以了,最好整理好的code 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding: utf8 -*-
from lxml import etree
import requests
def main():
result = requests.get("http://axe-level-1.herokuapp.com/")
result.encoding='utf8'
root = etree.fromstring(result.content, etree.HTMLParser())
jsonData = "["
for row in root.xpath("//table[@class='table']/tr[position()>1]"):
column = row.xpath("./td/text()")
tmp= '{"name": "%s", "grades": {"國語": %s, "數學": %s, "自然": %s, "社會": %s, "健康教育": %s}},' % (column[0], column[1], column[2], column[3], column[4], column[5])
jsonData += tmp
# 刪除最後一個逗號
print(jsonData[0:-1] + ']')

if __name__ == "__main__":
main()