1.前言
由于公司有几款新闻,视频类的app产品,于是乎文章和视频的稳定来源成为一个必须解决的问题。 公司也研究了很多的
爬虫方案,最后使用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