前端工程師後端之旅(6) - 利用爬蟲取得網站原始商品資料

為了盡可能重現目標網站,我們會需要原來的商品資料,但總不可能一筆一筆複製貼上,這時候我們就會需要爬蟲,爬蟲就是一隻去造訪網站並且分析網站回傳的資料,我們就可以只拿出我們需要部分的資料,透過爬蟲我們可以做很多數據統計的分析,比如:分析某某競爭對手電商的產品趨勢、分析PTT八卦版最常出現的用詞…等。

Python是最常見用於撰寫爬蟲的語言之一,但其實大多數語言都可以用來撰寫爬蟲,因為目前正在學習Rails,而且Ruby還能夠透過’active-record’使用ORM跟資料庫溝通,非常方便,所以今天我會用ruby來示範如何撰寫一隻爬蟲。

Outline - 流程

爬蟲從獲取資料到分析完資料之間可細分為幾個步驟:

  1. 觀察url規律
  2. 模擬送出HTTP Request
  3. 取得網站Response
  4. 分析html內容結構
  5. 取出需要的部分並整理
  6. 與資料庫取得連線
  7. 整理後存入資料庫

使用工具

要進行以上幾個動作,我們會需要幾個套件,分別是:

  • 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請求。
螢幕快照 2019-01-23 上午10.42.09

接下來利用rest-client去模擬http get請求到該目標網站:

html = RestClient.get('https://www.leisurecosmetics.com/index.php?route=product/product&product_id=75')

binding.pry 下中斷並觀察取回的資料
螢幕快照 2019-01-23 上午10.54.54
我們已經把網頁的html拿到手了。

分析Html結構

透過RestClient取回資料後,接下來我們需要Nokigiri去讓我們可以很方便找到我們要的資料,他的使用方式其實就很跟jquery選擇元素的時候一樣,例如,想要選到商品title,我先觀察,商品title上面是否有唯一且具有識別性的元素?

<h2 class="text-primary">BS03 輕透底光 - 面部化妝刷具套裝</h2>

觀察到商品名是用h2包起來並且有 .text-primary 這個class, 我可以先用瀏覽器確認是不是用h2.text-primary可以只選到標題:
螢幕快照 2019-01-23 上午11.09.25

取出需要的部分並整理

太棒了!上面正好是我要的,之後我們只要用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

螢幕快照 2019-01-23 上午11.16.50

用gsub方法把空白字元去掉,ruby的gsub就跟js的replace一樣都是用來代換字串:

doc.css('h2.text-primary').first.text()
        .gsub("\n","")
        .gsub("\t","")

螢幕快照 2019-01-23 上午11.22.05
拿到我要的資料。
也可以用正規表達式來匹配資料:

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

你可以在這裡看到我的網站實作成果

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×