ดึงข้อมูลเว็บไซต์ด้วย Nodejs และ Cheerio
บทความนี้เป็นตัวอย่างการดึงข้อมูลเว็บไซต์ด้วยการใช้ Node.js และ Cheerio ซึ่งเทคนิคการดึงข้อมูลเว็บไซต์ต่างๆนี้ เราเรียกมันว่า "Web Scraping" หรือ "Web Crawler" ก็แล้วแต่ หลักการมันก็คล้ายๆกับเว็บไซต์ Google ที่จะเข้าไปเก็บข้อมูล index ทุกๆเว็บไซต์ไว้เพื่อทำ search engine นั่นเอง
สำหรับตัวอย่างบทความนี้ จะเป็นตัวอย่าง การดึงข้อมูลของแอพใน Google Play มาแสดง ซึ่งนอกจากในบทความนี้แล้ว ยังสามารถนำไปประยุกต์ใช้ได้หลากหลาย ไม่ว่าจะเป็น ดึงข้อมูล ราคาทอง ราคาน้ำมัน ตารางหนังเข้าฉาย ราคาเกมส์ ราคาสินค้า Amazon, Wallmart เยอะแยะไปหมด
Web Scraping จะมองว่าเป็นสายเทาก็ได้นะครับ หากเราใช้ดึงข้อมูลสำหรับ personal use คิดว่าไม่น่าจะมีปัญหาอะไร ทางที่ดีควรจะได้รับอนุญาตจากทางเจ้าของเว็บไซต์นั้นๆจะดีที่สุดครับ :)
Getting Started
ก่อนอื่นเลย สิ่งที่ต้องเตรียมตัวสำหรับโปรเจ็คนี้มีอะไรบ้าง
อ่านเพิ่มเติม
Step 1 : Create project
เริ่มต้นสร้างโปรเจ็คกันเลยครับด้วย npm init
หรือใช้ไฟล์ package.json
ตามข้างล่างนี้
{
"name": "nodejs-google-play-information",
"version": "1.0.0",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"engines": {
"node": "^4.2.0"
},
"dependencies": {
"cheerio": "^0.20.0",
"hapi": "^13.0.0",
"request": "^2.69.0"
}
}
จัดการลง dependencies ให้เรียบร้อย
npm install
โดยเป้าหมายของเราคือดึงข้อมูลรายละเอียดของ App บน Google Play โดยใช้ชื่อของ applicationId หรือก็คือชื่อ Package Name เวลาทำ App Android ซึ่งมันจะเป็นชื่อที่ไม่ซ้ำกัน ทำให้เราสามารถใช้ชื่อนี้ในการเข้าดูรายละเอียดแอพแต่ละหน้าได้ เช่น
- app.akexorcist.mobileinternetsetting : แอพ Mobile Internet Setting [TH]
- com.facebook.orca : แอพ Facebook
- jp.naver.line.android : แอพ LINE
สิ่งที่เราจะทำคือ ทำ route สำหรับรับค่า appId เหล่านี้ คือ
- GET
/{appId}
Step 2 : Create Server with Hapi.js
จากนั้นสร้างไฟล์ index.js
ขึ้นมาและสร้าง Server ขึ้นมาง่ายๆ ด้วย Hapi.js ดังนี้ (รายละเอียดของ Hapi.js จะไม่ขอพูดถึงมากนัก แนะนำให้อ่านจากเว็บ Hapi.js หรือบทความที่ผมเคยเขียนด้านบนครับ)
'use strict'
const Hapi = require('hapi')
const server = new Hapi.Server()
server.connection({
host: 'localhost',
port: 8088
})
server.route({
method: 'GET',
path: '/{appId}',
handler: (req, reply) => {
reply({ message: 'Hello World' })
}
})
server.start(err => {
console.log(`Server running at ${server.info.uri}`)
})
จากโค๊ดด้านบน เขียนด้วย ES6 ซึ่งมีใน Node v4.2.4 ที่ผมใช้ในบทความนี้ โดยต้องกำหนด use strict
ให้มัน โดยไม่ต้องใช้ Babel ในการ compile เป็น ES5 เลย
ส่วนโค๊ดอื่นๆ ก็เป็นการเริ่มกำหนด route โดย path /appId
ทดสอบสั่งรัน server
node index.js
และเมื่อเข้า http://localhost:8088/appId ก็จะได้ข้อความ
{
"message": "Hello World"
}
ซึ่งตอนนี้ appId
จะเป็นอะไรก็ได้ มันก็จะได้ผลลัพธ์เหมือนกันหมด สิ่งที่เราต้องทำต่อคือรับค่า appId
มาจากนั้นก็ใช้ request
module เพื่อเปิดหน้าเว็บของ Google Play ด้วย appId
ก็เลยเพิ่มโค๊ดด้านล่างเพิ่มเติม
const URL = 'https://play.google.com/store/apps/details?id='
server.route({
method: 'GET',
path: '/{appId}',
handler: (req, reply) => {
let appId = req.params.appId
let lang = req.query.lang || 'en'
let url = `${URL}${appId}&hl=${lang}`
reply({
url: url
})
}
})
Step 3 : Use Request
ต่อมา module ที่จะทำให้เราสามารถ call HTTP request ได้ก็คือ request นั่นเอง ซึ่งการใช้งาน Request แบบคร่าวๆ โดยมี syntax ดังนี้
request(URL, callback)
:URL
คือ url ที่เราต้องการ call ส่วนcallback
เป็น callback function ซึ่งมี(err, response, body)
3 ตัวerr
: หากการ call HTTP มี error เกิดขึ้นresponse
: เป็นค่า response ที่ตอบกลับมาจาก server มีพวก header, statusCodebody
: เป็นข้อมูล body ที่ server ส่งกลับมา เหมือนหน้า HTML ทั่วไปเวลาเราเปิดเว็บไซต์
ตัวอย่าง
const request = require('request');
request('https://devahoy.com', (err, response, body) {
if (!err && response.statusCode === 200) {
console.log('body : ' + body);
}
});
Step 4 : Cheerio
ต่อมา สิ่งที่เราจะทำ เมื่อเวลาที่เรา ได้ค่า body จากการ call HTTP ก็คือ เราจะใช้ cheerio เพื่อหา DOM element ในหน้า HTML นั้นๆ โดยมี syntax คร่าวๆ คล้ายๆ jQuery ทำให้ศึกษาเพิ่มเติมไม่ยาก ตัวอย่างเช่น
มี html ดังนี
<ul id="fruits">
<li class="apple">Apple</li>
<li class="orange">Orange</li>
<li class="pear">Pear</li>
</ul>
การใช้ Cheerio และการ Selector
const cheerio = require('cheerio')
let $ = cheerio.load(html)
$('.apple', '#fruits').text()
//=> Apple
$('ul .pear').attr('class')
//=> pear
$('li[class=orange]').html()
//=> Orange
ลองนำมาประยุกต์ใช้กับโปรเจ็คที่กำลังทำอยู่ร่วมกับ request ก็จะได้โค๊ดตรงส่วนของ server.route()
ดังนี้
const URL = 'https://play.google.com/store/apps/details?id='
server.route({
method: 'GET',
path: '/{appId}',
handler: (req, reply) => {
let appId = req.params.appId
let lang = req.query.lang || 'en'
let url = `${URL}${appId}&hl=${lang}`
request(url, (err, response, body) => {
if (!err && response.statusCode === 200) {
let $ = cheerio.load(body)
reply({})
} else {
reply({
message: `We're sorry, the requested ${url} was not found on this server.`
})
}
})
}
})
Step 5 : Cheerio Selector
ต่อมา เราลองมาดูหน้าตัวอย่างที่เราต้องการดึงข้อมูลมา โดยเข้าเว็บ Facebook on Google Play
ขั้นตอนนี้เราจะใช้ Chrome Developer Tools เข้ามาช่วย ทำได้โดยการเลือก More Tools => Developer Tool
สิ่งแรกที่เราต้องการคือ Title ของแอพ ด้านบน จะเห็นว่าเราต้องการ .document-title
selector จะเป็น $('.document-title').text()
ต่อมาด้านบน เราจะดึงข้อมูล Publisher และ Category แต่จะแตกต่างกับ Title คือมันจะแยกเป็น 2 กรณีคือ
- class
document-subtitle primary
และ classdocument-subtitle cagetory
- selector ของ cheerio จะเป็น
$('.document-subtitle.primary').text()
และ$('.document-subtitle.category').text()
สิ่งที่เราต้องการต่อมาคือ ข้อมูลส่วน Additional Information หากลองดู DOM Element เราจะเห็นว่าส่วนที่เราต้องการคือ meta-info > .content
แต่ว่า meta-info
มีหลาย element ทำให้เราจะได้ข้อมูลเป็น list กลับมา สิ่งที่ต้องการ ผมแค่ต้องการ version และ จำนวนครั้งในการ Install ซึ่งมันอยู่ในตำแหน่งที่ 2 และ 3 เพราะฉะนั้น cheerio selector จะได้เป็น
$('.meta-info > .content').eq(2).text()
และ$('.meta-info > .content').eq(3).text()
สุดท้ายเมื่อได้ข้อมูลที่เราต้องการแล้ว ก็แค่สั่ง reply()
ด้วยข้อมูลที่เราได้มา
โค๊ด index.js
สุดท้ายเป็นดังนี้
'use strict'
const Hapi = require('hapi')
const request = require('request')
const cheerio = require('cheerio')
const URL = 'https://play.google.com/store/apps/details?id='
const server = new Hapi.Server()
server.connection({
host: 'localhost',
port: 8088
})
server.route({
method: 'GET',
path: '/{appId}',
handler: (req, reply) => {
let appId = req.params.appId
let lang = req.query.lang || 'en'
let url = `${URL}${appId}&hl=${lang}`
request(url, (err, response, body) => {
if (!err && response.statusCode === 200) {
let $ = cheerio.load(body)
let title = $('.document-title').text().trim()
let publisher = $('.document-subtitle.primary').text().trim()
let category = $('.document-subtitle.category').text().trim()
let score = $('.score-container > .score').text().trim()
let install = $('.meta-info > .content').eq(2).text().trim()
let version = $('.meta-info > .content').eq(3).text().trim()
reply({
data: {
title: title,
publisher: publisher,
category: category,
score: score,
install: install,
version: version
}
})
} else {
reply({
message: `We're sorry, the requested ${url} was not found on this server.`
})
}
})
}
})
server.start(err => {
console.log(`Server running at ${server.info.uri}`)
})
ทดสอบรันเว็บของเรา node index.js
และลองเข้าหน้าเว็บ โดยใส่ appId เช่น http://localhost:8088/app.akexorcist.mobileinternetsetting ก็จะได้ข้อมูลประมาณนี้
{
"data": {
"title": "Mobile Internet Setting [TH]",
"publisher": "Akexorcist",
"category": "Communication",
"score": "4.4",
"install": "50,000 - 100,000",
"version": "1.5.1"
}
}
ซึ่งเมื่อได้ข้อมูลมาแล้ว ก็จะเอาไปทำอะไรก็แล้วแต่ ขั้นตอนต่อไปก็ไม่ยากแล้ว :)
สรุป
บทความนี้เป็นแค่ตัวอย่างง่ายๆในการใช้ Cheerio ในการดึงข้อมูลเว็บไซต์เท่านั้น ไม่ได้ลงรายละเอียดเชิงลึก ซึ่ง Cheerio มันสามารถทำอะไรได้อีกเยอะ ซึ่งหวังว่าจะเป็นแค่ตัวอย่างให้ผู้สนใจได้นำไปศึกษาเพิ่มเติมดูครับ
ส่วนใครสนใจ Source Code ก็ดูได้จาก Github เลยครับ
- Authors
- Name
- Chai Phonbopit
- Website
- @Phonbopit