1.前言
由于公司有几款新闻,视频类的app产品,于是乎文章和视频的稳定来源成为一个必须解决的问题。 公司也研究了很多的
爬虫方案,最后使用puppeteer开发了一个文章的采集中心。 这是一个基于node的服务器,主要设计的思路是:当接收到抓取某个站点文章的任务后,node服务器就启动一个 爬虫器,将该网站的文章信息解析出来,然后上报给一个java服务器,由java负责数据的处理和存储。在此简单介绍一下node端的实现,这是一个简化版的。
爬虫方案,最后使用puppeteer开发了一个文章的采集中心。 这是一个基于node的服务器,主要设计的思路是:当接收到抓取某个站点文章的任务后,node服务器就启动一个 爬虫器,将该网站的文章信息解析出来,然后上报给一个java服务器,由java负责数据的处理和存储。在此简单介绍一下node端的实现,这是一个简化版的。
2.使用node写一个接口,负责接收中心服务器的文章爬取任务
node搭建一个微服务的话有很多种,这里使用的是express , 使用 npm install --save express 即可。
var express = require('express'); var app = express(); var download163 = require('../download163.js'); // 设置跨域访问 app.all('*',function(req,res,next){ res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "X-Requested-With"); res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS"); res.header("X-Powered-By",' 3.2.1'); res.header("Content-Type", "application/json;charset=utf-8"); next(); }); app.get('/start',function(req,res){ console.log('接收到分配任务.....'); var url = req.query.url; console.log('url:'+req.query.url) console.log('pas:'+req.query.pas) try { console.log('开始执行任务.....'); new download163(url); res.json({ code:200, work:true }); } catch(e){ http404(req,res) } }); var http404 = app.get('/404',function(req,res){ res.end("404"); }); // 配置服务端口 var server = app.listen(3000,function(){ var host = server.address(),address; var port = server.address().port; console.log("正在启动服务器,监听3000........."); });
接收到 开始任务的时候,创建一个下载网易新闻 对象。
2.写一个工具类,请求中心服务器,获取目标网站解析方式的json
var request = require('request'); var qs = require('querystring'); class Tools{ static timeout(delay){ return new Promise((resolve,reject) => { setTimeout(() => { try{ resolve(1); } catch(e){ reject(0) } },delay); }); } static getArticeRule(type){ return new Promise((resolve,reject) => { request('******?uuid='+type,function(error,response,body){ if (!error && response.statusCode == 200) { console.log('body-------->'+body); resolve(JSON.parse(body)); } else{ reject(error) } }); }); } } module.exports = Tools;
这里有个非常需要注意的点,由于js都是异步执行的, 返回值都是需要 使用 Promise容器里面的。不然你会发现一个很坑的事情,就是 上述的 getAriceRule方法的返回值一直是个 undefined ,打日志的话你会发现, 这个方法在 赋值之后才执行的。如下所示:
static getArticeRule(type){ request('http://ck.chatting365.xyz/api/imgCode/rfCode?uuid='+type,function(error,response,body){ if (!error && response.statusCode == 200) { console.log('body-------->'+body); return body; } }); // return new Promise((resolve,reject) => { // request('http://ck.chatting365.xyz/api/imgCode/rfCode?uuid='+type,function(error,response,body){ // if (!error && response.statusCode == 200) { // console.log('body-------->'+body); // resolve(JSON.parse(body)); // } else{ // reject(error) // } // }); // }); }这个tool我是用下面的js调用:
const puppeteer = require('puppeteer') var tools = require('./src/tools.js'); var type = require('./src/model/type.js')(); (async() => { console.log('开始打开浏览器...'); var a = await tools.getArticeRule(1); console.log('lala---------->'+JSON.stringify(a)); })();结果就是: lala 先打印了 。。。 我们理想的结果应该是先请求中心服务器的接口, 然后才输出返回的结果值。 有兴趣的同学可以去研究一下 Promise。这里不过多介绍了。
3.写一个puppeteer 去抓取目标网站的内容, 然后将结果上报给中心服务器
const puppeteer = require('puppeteer'); var tools = require('./tools.js'); var request = require('request'); var delay = 2000; class Download163 { constructor(url, type) { // this.url = 'http://sports.163.com'; this.url = url; this.type = type; this.init(); } async init() { console.log('正在启动浏览器...'); this.browser = await puppeteer.launch({headless: false}); console.log('正在打开新页面...'); this.page = await this.browser.newPage(); await this.loadSport163(this.url); console.log('正在关闭浏览器...'); await tools.timeout(delay); await this.browser.close(); } async loadSport163(url) { console.log('163 is ready ----------------'); let page = this.page; await page.goto(url); try { await page.keyboard.down('PageDown'); await page.keyboard.up('PageDown'); console.log('向下翻页......'); await tools.timeout(3000); // 开始抓取文章列表页 var listMap = await page.evaluate(() => { var alist = [...document.querySelectorAll('.topnews_news ul li a')]; return alist.map((el) => { return { href: el.href, title: el.innerText } }); }); for (var i = 0; i < listMap.length; i++) { var a = listMap[i].href; await this.loadContent(a, this.type); } } catch (e) { console.log(e); } } async loadContent(url, type) { try { await tools.timeout(2000); let page = this.page; await page.goto(url); // 这里使用tools请求服务器,获取到 网站解析方式,其实就是各个节点 // 的选择器 page.$eval的第一个参数就是一个css选择器。网易体育的新闻详情页差不多就如下: /** { "id": "0", "target": "163", "title": "#epContentLeft h1", "meta": "meta[name=keywords]", "content": "#endText p", "source": "#ne_article_source" } */ var rule = tools.getArticeRule(type); var title = await page.$eval(rule.title, el => el.innerText); var source = await page.$eval(rule.source, el => el.innerText); var contentP = await page.evaluate(() => { var pList = [...document.querySelectorAll(rule.content)]; return pList.map(el => { return { p: el.innerHTML }; }); }); var meta = await page.$eval(rule.meta, el => el.content); var data = { title: title, source: source, content: contentP, meta: meta } // 上传服务器 request('*****', data, function (error, response, body) { if (!error && response.statusCode == 200) { console.log('上传成功-------->' + body); } else { console.log("上传失败--------->" + error) } }) } catch (e) { console.log(e); } } } module.exports = Download163;由于时间的原因,上述代码也写的比较糟心,附上码云地址吧,后期码云上会有更新版的。
源码地址:
https://gitee.com/xiaoxia_dyh/PuppeteerDemo
puppeteer相关api地址: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md
puppeteer相关api地址: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md