為了盡可能重現目標網站,我們會需要原來的商品資料,但總不可能一筆一筆複製貼上,這時候我們就會需要爬蟲,爬蟲就是一隻去造訪網站並且分析網站回傳的資料,我們就可以只拿出我們需要部分的資料,透過爬蟲我們可以做很多數據統計的分析,比如:分析某某競爭對手電商的產品趨勢、分析PTT八卦版最常出現的用詞…等。
Python是最常見用於撰寫爬蟲的語言之一,但其實大多數語言都可以用來撰寫爬蟲,因為目前正在學習Rails,而且Ruby還能夠透過’active-record’使用ORM跟資料庫溝通,非常方便,所以今天我會用ruby來示範如何撰寫一隻爬蟲。
Outline - 流程
爬蟲從獲取資料到分析完資料之間可細分為幾個步驟:
- 觀察url規律
- 模擬送出HTTP Request
- 取得網站Response
- 分析html內容結構
- 取出需要的部分並整理
- 與資料庫取得連線
- 整理後存入資料庫
使用工具
要進行以上幾個動作,我們會需要幾個套件,分別是:
- nokogiri:可以讓我們使用與jquery選擇器一樣的方式去選出html內容
- rest-client: 可以模擬並發出http請求
- active-record :讓我們可以使用ORM與資料庫溝通
- pry :讓我可以下中斷並且觀察資料
觀察Url規律
既然是要拿到所有相同類型的資料,只要觀察不同筆資料間url的相異之處就可以了,如
https://www.leisurecosmetics.com/index.php?route=product/product&product_id=75
https://www.leisurecosmetics.com/index.php?route=product/product&product_id=77
可看出改網站用product_id來識別商品。
模擬送出HTTP Request並取回html網頁結構
寫過前端ajax的人,一定常用axios.get(‘http://example.com/api/xxx’) 去送出get請求拿回資料,其實一般我們進入瀏覽器時,就是在對該網站的server送出Http的get請求,只不過跟ajax不同的是,拿回來的不是api資料,而是網站的畫面,這就是常聽到的Server Side Rendering,圖為進入youtube時所送出的Get請求。
接下來利用rest-client去模擬http get請求到該目標網站:
html = RestClient.get('https://www.leisurecosmetics.com/index.php?route=product/product&product_id=75')
binding.pry 下中斷並觀察取回的資料
我們已經把網頁的html拿到手了。
分析Html結構
透過RestClient取回資料後,接下來我們需要Nokigiri去讓我們可以很方便找到我們要的資料,他的使用方式其實就很跟jquery選擇元素的時候一樣,例如,想要選到商品title,我先觀察,商品title上面是否有唯一且具有識別性的元素?
<h2 class="text-primary">BS03 輕透底光 - 面部化妝刷具套裝</h2>
觀察到商品名是用h2包起來並且有 .text-primary 這個class, 我可以先用瀏覽器確認是不是用h2.text-primary可以只選到標題:
取出需要的部分並整理
太棒了!上面正好是我要的,之後我們只要用Nokogiri以同樣的方式選出該元素就行:
html = RestClient.get('https://www.leisurecosmetics.com/index.php?route=product/product&product_id=75')
doc = Nokogiri::HTML(html)
doc.css('h2.text-primary').text //===> get product title
用gsub方法把空白字元去掉,ruby的gsub就跟js的replace一樣都是用來代換字串:
doc.css('h2.text-primary').first.text()
.gsub("\n","")
.gsub("\t","")
拿到我要的資料。
也可以用正規表達式來匹配資料:
doc.css('.price h2').first().text.match(/(\w+)\$([\d,]+)/)[2] //get product price
與資料庫取得連線
Ruby 的active record 其實不一定只能跟著Rails 搭配使用,反過來說,Rails也不一定要使用這套ORM(也有其他的)。這裡我們就把active record單獨拿出來使用,作為與資料庫溝通的橋樑。因為我們上面已經有require了,這邊只要直接使用他的class就可以:
ActiveRecord::Base.establish_connection({
adapter: 'mysql2',
encoding: 'utf8',
database: 'leisure_development',
username: 'your_db_username',
password: 'your_db_password',
})
記得資料庫的資訊要設好,否則會連不上。
存入資料庫
這邊用的資料庫要是跟Rails專案同樣的資料庫,才能夠直接讓專案使用。
最後就是依照資料表格式整理成Hash然後把需要的資料包成Hash,我把整段程式碼包成method,這個method 直接回傳商品資料:
def parse_page(doc)
product_name = doc.css('h2.text-primary').first.text()
.gsub("\n","")
.gsub("\t","")
price = doc.css('.price h2').first().text.match(/(\w+)\$([\d,]+)/)[2]
price_origin = doc.css('.price .strike').first().text.match(/(\w+)\$([\d,]+)/)[2]
discount_value = price_origin.to_i - price.to_i
tab_content = doc.css('.tab-content')
sku = doc.css('.condition li').first.text
.gsub("\t","")
.gsub("\n","").match(/:(\w+)/)[1]
return {
:name => product_name,
:price => price,
:content=>tab_content,
:discount_value=>discount_value,
:sku=>sku,
:stock=> 100 ,
}
end
接下來我只要用迴圈一筆一筆去送出request拿回html並且找到我要的商品訊息,就可以放進資料庫了,這裡因為不確定商品有幾筆,所以試試看從第一筆到第一百筆抓抓看,記得嘗試抓資料時在迴圈裡面要做例外處理,避免沒有資料而發生錯誤:
def run
(1..100).each do |product_id|
puts "Parsing product id : #{product_id} \n"
product_url = "#{BASE_URL}?route=product/product&product_id=#{product_id}"
begin
html = RestClient.get(product_url)
doc = Nokogiri::HTML(html)
product_data = parse_page(doc)
Product.create!(product_data)
rescue => exception
puts "#{exception.message}"
end
sleep 0.1
end
end
你可以在這裡看到我的網站實作成果。