爬虫的原理
爬虫,就是一个自动爬取网页上展示的信息的工具。我们要写一款爬虫,就要满足下面的条件:
- 网络的请求。首先我们要进行网络请求,让目标给我们返回信息(常用的模块有http、http2、https、request、axios、puppeteer)
- 对返回来的数据的处理。(这部分可以随意发挥、正则等技术,而对于html文件的话,有 cheerio 模块(客户端的jQuery)、而返回来的数据可能有编码问题,iconv-lite 模块可以用于编码的转换,当然,请求时也可以直接设置response的编码)
- 数据的储存。(这部分涉及到文件处理和数据库,会在另外的文章叙述)
爬虫的实现
talk is cheap,show me the code
让我们开始写代码吧
const cheerio = require('cheerio');
const fs = require('fs');
const request = require('request');// http://www.feizdy.com 是一个电影网站
// 尝试的时候建议选用http的网站,初学者比较好爬
// 我们的目的是爬取电影的名称和图片信息,并把图片下载保存起来// target
let url = 'http://www.feizdy.com';
// savePath
let photoSavePath = './img';run(url, photoSavePath).catch(err => {console.log('运行错误 => ' + err);
});async function run(url, photoSavePath) {// 获得首页const html = await getHTML(url);// 可以直接用正则进行内容匹配// 把首页 用cheerio模块转换成Dom操作 (注意:load转换时可能会乱码,所以要设置{decodeEntities: false})const $ = cheerio.load(html, {decodeEntities: false});// 2.对返回来的数据进行处理// 用元素审查分析html文件结构(见图 1)// 元素选择器获得所需要的结构的集合// 这里是获得电影详情页的链接$('.container .row .col-sm-6 .movie-item a').each((i, item) => {// 这里的item要转换一下,用$(item)变成dom节点// 由图可以看出链接是片段的,要拼接成完整的urllet detailUrl = url + $(item).attr('href');// 利用详情链接获取电影详细信息getDetailMessage(detailUrl).then(movie => {return addToFile(movie);}).then(imgSrc => {downloadPhoto(imgSrc, photoSavePath, i);}).catch(err => {console.log(err);});});
}// 请求资源
function getHTML(url, timeout = 20000) {return new Promise((resolve, reject) => {// 1.发送请求,等待网站回应request.get(url, {timeout: timeout}, function (error, response, body) {error ? reject('Timeout.\n' ) : resolve(body);})})
}// 获得电影相关的信息
async function getDetailMessage(url) {try {let detailHTML = await getHTML(url);// 结构分析在图 2const $ = cheerio.load(detailHTML, {decodeEntities: false});let movie = {name: await $('.container .container-fluid .row .col-md-12 .movie-title').text(),img: await $('.container .container-fluid .row .col-md-9 .row .col-md-4 a img').attr('src')};// 去除空电影return movie.name && movie.img ? movie : undefined;} catch (e) {console.log('Method: getDetailMessage =>' + e);}
}// 添加到文件
function addToFile(item) {if (item) {let imgSrc = item.img;item = JSON.stringify(item) + '\n';// 把movie 写入文件fs.appendFile('./data/data.txt', item, {encoding: 'utf-8', flag: 'a'}, error => {error ? console.log('Method: addToFile => data write error.\n' + error) : null;})return imgSrc;}
}// 下载图片
function downloadPhoto(src, photoSavePath, filename, timeout = 20000) {if (src) {// 提取后缀名let suffix = src.toString().slice(-4);// 拼接路径let path = photoSavePath + '/' + filename + suffix;// 下载request.get(src, {timeout: timeout}, err => {err ? console.log('Method: downloadPhoto => download error\n' + err) : null;}).pipe(fs.createWriteStream(path));}
}
图 1
图2