node.js爬虫实践
小猫的毕设系统需要一些疾病数据信息,于是决定从某医药网站上爬取相关数据。
小猫以前学爬虫的时候,用的是python,这次决定用node.js试试。
首先说说用node做一个简单的爬虫需要的库
- request:用于http请求
- iconv-lite:用于转换字符编码
- cheerio:用于解析html,
- node-xlsx:将数据生成excel文件
一个简单的爬虫其实就是爬取网页,解析数据,存储数据。
小猫使用request获取网页,当然也可以选择其他方式,比如node自带的http模块。因为小猫爬取的网页字符编码为‘gb2312’,因此使用iconv-lite转换为utf8。并且需要注意的是使用request不传encoding字段时有一个默认编码,因此需要传入{encoding: null}
的参数去除默认格式iconv-lite才会生效。
const request = require('request');
const iconv = require('iconv-lite');
// 获得html
const requestPromise = (url)=>{
return new Promise((resolve,reject)=>{
request(url,{
encoding: null}, (error, response, body)=>{
if(response.statusCode === 200) {
// 编码格式转换 ,{encoding: null}解除默认格式
const bufs = iconv.decode(body, 'gb2312')
const html = bufs.toString('utf8')
resolve(html)
}else {
reject()
}
})
})
}
获取到html后使用cheerio.load
解析html。cheerio.load
可以让使用者通过jquery的方式获取想要的元素,比如$('.class').text()
获取文字。
将获取到的数据整理好后,使用node-xlsx的xlsx.build
创建一个buffer,最后用node的fs文件系统写入文件。
const cheerio = require('cheerio');
var fs = require('fs');
var xlsx = require('node-xlsx');
// 解析数据
const getData = async(url) =>{
const html = await requestPromise(url)
const $ = cheerio.load(html) // 解析html
// 疾病名称
var disease = $('body > div.wrap.mt10.clearfix.graydeep > div.main-sub.fl > div.jib-janj.bor.clearfix > div.jib-articl.fr.f14 > div.jib-articl-con.jib-lh-articl > strong').text()
// 存进excel
var buffer = xlsx.build([
{
name:'sheet1',
data:[[disease]]
}
]);
fs.writeFileSync('diseaseData.xlsx',buffer);
}
下面是完整的demo代码,其中省去了部分数据处理,仅保留一个disease字段,并且隐去了具体网址。
const request = require('request');
const iconv = require('iconv-lite');
const cheerio = require('cheerio');
var fs = require('fs');
var xlsx = require('node-xlsx');
// 获得html
const requestPromise = (url)=>{
return new Promise((resolve,reject)=>{
request(url,{
encoding: null}, (error, response, body)=>{
if(response.statusCode === 200) {
// 编码格式转换 ,{encoding: null}解除默认格式
const bufs = iconv.decode(body, 'gb2312')
const html = bufs.toString('utf8')
resolve(html)
}else {
reject()
}
})
})
}
// 解析数据
const getData = async(url) =>{
const html = await requestPromise(url)
const $ = cheerio.load(html) // 解析html
// 疾病名称
var disease = $('body > div.wrap.mt10.clearfix.graydeep > div.main-sub.fl > div.jib-janj.bor.clearfix > div.jib-articl.fr.f14 > div.jib-articl-con.jib-lh-articl > strong').text()
// 存进excel
var buffer = xlsx.build([
{
name:'sheet1',
data:[[disease]]
}
]);
// fs.appendFileSync('diseaseData.xlsx',buffer);
fs.writeFileSync('diseaseData.xlsx',buffer);
}
// 循环存入url,并进行数据请求
var urls = []
for (let i = 50; i<53;i++) {
const url = `http://xxx/${
i}.htm`
urls.push(url)
}
// 等待一个请求处理结束后再进行第二次请求
urls.reduce((rs,url)=>{
return rs.then(res => {
return new Promise(async (resolve)=>{
await getData(url)
resolve()
})
})
},Promise.resolve())
两个note:
-
为什么使用reduce和Promise,而不直接在for循环中
getData(url)
?reduce方法作为累加器可以将数组的上一次计算结果传给下一项,第二个参数接受
Promise.resolve()
的初始值,在回调函数中只有当一次数据请求和处理完成并且resolve()后才会进行下一项的处理,这样就可以逐次请求数组中的url,避免给服务器造成较大压力。 -
使用
fs.writeFileSync
会覆盖已有文件,追加文件本来应该使用appendFileSync
,但在本例中遇到问题一开始是想使用
appendFileSync
向excel中追加数据的,但是生成的文件一直显示损坏,似乎是写入错误。一直没有找到问题的原因。因为这不是小猫此次毕设的主要任务,决定先留一个坑。后来迫于时间压力,选择了先将适量数据存入一个数组,然后使用writeFileSync
生成多个excel,最后在excel软件中一次性合并多张表。本次共收集10000余条数据,最后晒张图。