puppeteer实战之网页爬虫,模拟操作《二》

时间:2022-12-20 22:13:01

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。这里不过多介绍了。

puppeteer实战之网页爬虫,模拟操作《二》

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;
由于时间的原因,上述代码也写的比较糟心,附上码云地址吧,后期码云上会有更新版的。