今天業務突然來了個爬蟲業務,爬出來的數據以Excel的形式導出,下班前一個小時開始做,加班一個小時就做好了。因為太久沒做爬蟲了!做這個需求都是很興奮!
需求說明
- 訪問網站
- (循環)獲取頁面指定數據源
- 根據頁面數據源再(循環)訪問詳情數據
- 記錄詳情數據,以Excel形式導出。
所需模塊
根據需求所得五個模塊
// 請求模塊(1.訪問網站)
const request = require('request');
// 可以看做成node版的jQuery(2.獲取頁面指定數據源)
const cheerio = require("cheerio");
// node異步流程控制 異步循環(3.根據頁面數據源再訪問詳情數據)
const async = require("async");
// Excel表格導出+node自帶文件系統(4.以Excel形式導出)
const excelPort = require('excel-export');
const fs = require("fs");
安裝模塊:
npm install request cheerio async excel-export --save-dev
開始發送請求
一開始我直接用request請求網站,但直接返回了404,但我在瀏覽器上看又是沒毛病的。然后我就改了下請求的header。嘻嘻
request({
url: 'http://www.foo.cn?page=1',
method: 'get',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
// 這里巨坑!這里開啟了gzip的話http返回來的是Buffer。
// 'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cache-Control': 'no-cache',
},
// 想請求回來的html不是亂碼的話必須開啟encoding為null
encoding: null
}, (err, res, body) => {
// 這樣就可以直接獲取請求回來html了
console.log('打印HTML', body.toString()); // <html>xxxx</html>
}
);
獲取指定數據源
request({
url: 'http://www.foo.cn?page=1',
method: 'get',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
// 'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cache-Control': 'no-cache',
},
encoding: null
}, (err, res, body) => {
console.log('打印HTML', body.toString()); // <html>xxxx</html>
const list = [];
const $ = cheerio.load(body.toString());
// 獲取指定元素
let item = $('.className tbody tr');
// 循環得到元素的跳轉地址和名稱
item.map((i, index) => {
let obj = {};
obj.link = $(index).children('a').attr('href');
obj.name = $(index).children('a').text();
list.push(obj);
});
console.log('list', list); // [{ link: 'http://xxxx.com', name: 'abc' }]
}
);
異步流程控制
先將request封裝多一層,傳入page值和async.series的callback
async function requestPage(page = 1, callback) {
request({
url: 'http://www.masuma.cn/product.php?lm=21&page=' + page,
method: 'get',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
// 'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cache-Control': 'no-cache',
},
encoding: null
}, async (err, res, body) => {
console.log('打印HTML', body.toString()); // <html>xxxx</html>
const list = [];
const $ = cheerio.load(body.toString());
// 獲取指定元素
let item = $('.className tbody tr');
// 循環得到元素的跳轉地址和名稱
item.map((i, index) => {
let obj = {};
obj.link = $(index).children('a').attr('href');
obj.name = $(index).children('a').text();
list.push(obj);
});
console.log('list', list); // [{ link: 'http://xxxx.com', name: 'abc' }]
callback(null, list);
}
);
}
打印出數據 + 導出Excel
async function main() {
const requestList = [];
// 在這里為什么要用到async.series?
// 是因為這個爬蟲需要具有順序性,必須得異步請求完一個地址並獲取數據然后再存到一個變量里才能執行下一個
// 在此期間我想過其他方法。例如:
// for循環 + await 直接否定了
// Promise.all這個並不能保證數據具有順序
// 最終敲定用async.series 用完之后!真香啊!
// 很好奇async.series找個時間也做個源碼解析
for (let i = 1; i < 36; i++) {
requestList.push(callback => {
requestPage(i, callback);
});
}
console.log('requestList', requestList); // [Function, Function] 全是function的數組
async.series(requestList, (err, result) => {
// 因為async.series返回來的結果是[[], [], []]這種二維數組形式,每個function返回來的值都放在一個數組里,我們需要將它弄成一維數組好做導出列表
const arry = [].concat.apply([], result);
console.log('最終結果!!!!', arry); // [{ link: 'http://xxxx.com', name: 'abc' }, ...]
writeExcel(arry);
});
}
const writeExcel = (datas) => {
// 定義一個對象,存放內容
let conf = {};
// 定義表頭
conf.cols = [
{caption:'瑪速瑪編碼', type:'string', width:40},
{caption:'原廠編碼', type:'string', width:60},
];
// 創建一個數組用來多次遍歷行數據
let array = [];
// 循環導入從傳參中獲取的表內容
for (let i=0;i<datas.length;i++){
//依次寫入
array[i] = [
datas[i].name,
datas[i].code,
];
}
// 寫入道conf對象中
conf.rows = array;
// 生成表格
const result = excelPort.execute(conf);
// 定義表格存放路徑
fs.writeFile('./表格.xlsx', result, 'binary',function(err){
if(err){
console.log(err);
}
});
}
main();
總結
其實爬蟲就是:
- 模擬瀏覽器請求,獲取到HTML
- 對HTML做解析,將需要數據提取出來
- 把數據進一步處理,導出Excel,保存數據庫等等
最后
其實這個爬蟲最終是
- 循環訪問帶有分頁的表格
- 提取表格的鏈接並訪問鏈接 去到詳情頁
- 在詳情頁獲取到我所需要的數據
- 最終輸出Excel
但我在這里就寫了獲取各頁表格里的鏈接地址,因為在這里我只想做一個簡單的分享。
這些分享應該都足以觸類旁通了。