nodejs抓取动态网页

nodejs抓取动态网页

nodejs抓取动态网页( 响应设计分为数据结构数据结构设计和响应码设计(组图) )

网站优化优采云 发表了文章 • 0 个评论 • 72 次浏览 • 2022-02-27 23:17 • 来自相关话题

  nodejs抓取动态网页(
响应设计分为数据结构数据结构设计和响应码设计(组图)
)
   分页获取部分博客
GET /api/blogs?page=1&size=15&sort=time

- 按层次组织资源:

// 获取某个博客下面的所有评论

GET /api/blogs/143/comments

层次化的设计有可能会让URL太长,不便于阅读,比如:

// 获取某个博客下面的某个评论的某个回复

GET /api/blogs/123/comments/12343/replys/2231

所以,我们也可以使用扁平化的方式设计所有的资源:

// 获取某个博客的所有评论,使用查询参数来限定条件

GET /api/comments?blogId=123
// 获取某个评论的所有回复

GET /api/replys?commentId=123
  响应式设计
  响应式设计分为数据结构设计和响应代码设计。
  前后分离
  在以前的时代,当用户请求一个网页时,服务器通过JSP技术或者其他模板引擎技术动态渲染,然后返回给用户。它们看起来像这样:
  
  这样做的缺点是后端和前端是一体的,即使前端代码不是后端人员编写的,双方仍然需要进行必要的交流和沟通,减少了开发效率;并且前后端一起部署,灵活性差;而静态资源的后台处理性能较差,不应该处理静态资源压缩、缓存等问题,应该交给代理服务器,比如Nginx。
  前后端分离最极端的方式是:前后端独立编写,独立部署,通过API接口交互。它们看起来像这样:
  
  项目实战:电商管理后台
  目标是为商家和管理员实现一个收录账户模块、权限模块、商品分类模块和订单模块的后台系统。
  项目采用前后端分离的开发方式。它只实现 API 功能,不提供接口。
  项目结构搭建及实施过程
  在之前的TODO项目的基础上,增加了一个中间件包和一个测试包。前者用于存放中间件包,因为权限管理需要通过中间件来实现;后者是一个与测试相关的包。
  各个模块的实现顺序为:模型层-->服务层-->路由器层。单元测试:服务层编写脚本测试;路由器层使用邮递员测试。配置文件的环境切换
  开发环境和生产环境的配置一般是不同的,比如端口配置和数据库配置。一般我们通过环境变量NODE_ENV来区分。为了动态切换配置,需要根据NODE_ENV的当前值加载不同的配置对象。
  这样做的方法是:
  创建config目录,创建dev.js和prod.js存放对应的配置信息,编写index.js实现动态配置切换的逻辑。
  目录结构:
  
  写入入口文件
  添加依赖项:
  npm i body-parser express express-async-errors mongoose morgan
  编写 app.js 和 db.js 文件。
  应用程序.js
   //引入dib
require('./db')

const config = require('./config');
const morgan = require('morgan')
const bodyParser = require('body-parser');
const express = require('express')
// 引入异常捕获处理
require('express-async-errors');

const app = express();

//注册中间件
// log中间件
app.use(morgan('combined'));

//注册自定义的中间件

// 注册body-parser中间件
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));
// parse application/json
app.use(bodyParser.json());

// 注册路由
app.use("/users", require('./router/account'));

// 注册异常处理中间件
app.use((err, req, res, next)=>{
res.fail(err.toString())
});

//启动
app.listen(config.PORT);
  数据库.js
   const config = require('./config');
const mongoose = require('mongoose');
mongoose.connect(`mongodb://127.0.0.1/${config.DB}`);

const db = mongoose.connection;

db.on('error', (err)=>{
console.log(err);
});

db.on("open", ()=>{
console.log("mongodb connect successfully!");
});

  账户模块
  先写模型,再写服务,最后写路由器;最后,测试服务和路由器。
  REST 中间件
  为了方便返回 REST 结果,我们编写了一个 res_md.js 中间件,用于为每个 res 对象安装 2 个方法。注意中间件的注册顺序要放在前面。代码显示如下:
   module.exports = function (req, res, next) {
res.success = (data = null) =>{
res.send({
code: 0,
msg: "操作成功",
data: data
})
};
res.fail = (msg)=>{
res.send({
code: -1,
msg: msg
})
};

next();
};
  账户模型
   const mongoose = require('mongoose')
const schema = new mongoose.Schema({
username: {
type: String,
required: [true, "用户名不能缺少"]
},
password: {
type: String,
required: [true, "密码不能缺少"]
},
age: {
type: Number,
min: [0, "年龄不能小于0"],
max: [120, "年龄不能大于120"]
},
role: {
type: Number,
default: 0 // 0是商家, 10086是管理员
},
created:{
type: Date,
default: Date.now()
}
});

module.exports = mongoose.model('user', schema);
  账户服务
   const User = require('../model/user');
const config = require('../config')
const crypto = require('lxj-crypto')

/**
* 根据用户名获取某个用户
* @param username
* @returns {Promise}
*/
async function getUserByUsername(username) {
return await User.findOne({username: username}).select("-__v")
}


async function checkCanInsertUser(user) {
//检查密码是否为空
if(!user.password || user.password.length===0){
throw Error("密码不能为空")
}
//检查用户是否已经存在
let res = await getUserByUsername(user.username);
if(res){
throw Error("用户名已经存在")
}
}

/**
* 添加普通用户
* @param user
* @returns {Promise}
*/
async function addUser(user) {
await checkCanInsertUser(user);

user.role = 0;
user.created = Date.now();

//对密码进行加密,加密的方式:使用username作为随机数对password进行哈希
user.password = crypto.md5Hmac(user.password, user.username)
await User.create(user)
}

async function deleteUser(id) {
let res = await User.deleteOne({_id:id});
if(!res || res.n===0){
throw Error("删除用户失败")
}
}

/**
* 登录的方法
* @param user
* @returns token
*/
async function login(user) {
// 1. 对密码进行加密
user.password = crypto.md5Hmac(user.password, user.username)
// 2. 进行查询
let res = await User.findOne({username:user.username, password: user.password});
if(!res){
throw Error("用户名或者密码错误")
}

// 说明用户名和密码验证成功,需要生产token返回给客户端,以后客户端的header中带上这个token
// token 生产方法:用aes进行对指定的data加密
let tokenData = {
username: user.username,
expire: Date.now() + config.TokenDuration
};
let token = crypto.aesEncrypt(JSON.stringify(tokenData), config.TokenKey);
return token
}

module.exports = {
getUserByUsername,
addUser,
deleteUser,
login
};

  帐号路由器
   let router = require("express").Router();
let accountService = require('../service/accout')


router.get("/:username", async (req, res)=>{
let user = await accountService.getUserByUsername(req.params.username);
res.success(user);
});

// 登录
router.post("/login", async (req, res)=>{
let token = await accountService.login(req.body);
res.success({token});
});

// 注册
router.post("/register", async (req, res)=>{
await accountService.register(req.body)
res.success()
});

router.delete("/:id", async (req, res)=>{
await accountService.deleteUser(req.params.id)
res.success()
});

module.exports = router;


``` 查看全部

  nodejs抓取动态网页(
响应设计分为数据结构数据结构设计和响应码设计(组图)
)
   分页获取部分博客
GET /api/blogs?page=1&size=15&sort=time

- 按层次组织资源:

// 获取某个博客下面的所有评论

GET /api/blogs/143/comments

层次化的设计有可能会让URL太长,不便于阅读,比如:

// 获取某个博客下面的某个评论的某个回复

GET /api/blogs/123/comments/12343/replys/2231

所以,我们也可以使用扁平化的方式设计所有的资源:

// 获取某个博客的所有评论,使用查询参数来限定条件

GET /api/comments?blogId=123
// 获取某个评论的所有回复

GET /api/replys?commentId=123
  响应式设计
  响应式设计分为数据结构设计和响应代码设计。
  前后分离
  在以前的时代,当用户请求一个网页时,服务器通过JSP技术或者其他模板引擎技术动态渲染,然后返回给用户。它们看起来像这样:
  
  这样做的缺点是后端和前端是一体的,即使前端代码不是后端人员编写的,双方仍然需要进行必要的交流和沟通,减少了开发效率;并且前后端一起部署,灵活性差;而静态资源的后台处理性能较差,不应该处理静态资源压缩、缓存等问题,应该交给代理服务器,比如Nginx。
  前后端分离最极端的方式是:前后端独立编写,独立部署,通过API接口交互。它们看起来像这样:
  
  项目实战:电商管理后台
  目标是为商家和管理员实现一个收录账户模块、权限模块、商品分类模块和订单模块的后台系统。
  项目采用前后端分离的开发方式。它只实现 API 功能,不提供接口。
  项目结构搭建及实施过程
  在之前的TODO项目的基础上,增加了一个中间件包和一个测试包。前者用于存放中间件包,因为权限管理需要通过中间件来实现;后者是一个与测试相关的包。
  各个模块的实现顺序为:模型层-->服务层-->路由器层。单元测试:服务层编写脚本测试;路由器层使用邮递员测试。配置文件的环境切换
  开发环境和生产环境的配置一般是不同的,比如端口配置和数据库配置。一般我们通过环境变量NODE_ENV来区分。为了动态切换配置,需要根据NODE_ENV的当前值加载不同的配置对象。
  这样做的方法是:
  创建config目录,创建dev.js和prod.js存放对应的配置信息,编写index.js实现动态配置切换的逻辑。
  目录结构:
  
  写入入口文件
  添加依赖项:
  npm i body-parser express express-async-errors mongoose morgan
  编写 app.js 和 db.js 文件。
  应用程序.js
   //引入dib
require('./db')

const config = require('./config');
const morgan = require('morgan')
const bodyParser = require('body-parser');
const express = require('express')
// 引入异常捕获处理
require('express-async-errors');

const app = express();

//注册中间件
// log中间件
app.use(morgan('combined'));

//注册自定义的中间件

// 注册body-parser中间件
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));
// parse application/json
app.use(bodyParser.json());

// 注册路由
app.use("/users", require('./router/account'));

// 注册异常处理中间件
app.use((err, req, res, next)=>{
res.fail(err.toString())
});

//启动
app.listen(config.PORT);
  数据库.js
   const config = require('./config');
const mongoose = require('mongoose');
mongoose.connect(`mongodb://127.0.0.1/${config.DB}`);

const db = mongoose.connection;

db.on('error', (err)=>{
console.log(err);
});

db.on("open", ()=>{
console.log("mongodb connect successfully!");
});

  账户模块
  先写模型,再写服务,最后写路由器;最后,测试服务和路由器。
  REST 中间件
  为了方便返回 REST 结果,我们编写了一个 res_md.js 中间件,用于为每个 res 对象安装 2 个方法。注意中间件的注册顺序要放在前面。代码显示如下:
   module.exports = function (req, res, next) {
res.success = (data = null) =>{
res.send({
code: 0,
msg: "操作成功",
data: data
})
};
res.fail = (msg)=>{
res.send({
code: -1,
msg: msg
})
};

next();
};
  账户模型
   const mongoose = require('mongoose')
const schema = new mongoose.Schema({
username: {
type: String,
required: [true, "用户名不能缺少"]
},
password: {
type: String,
required: [true, "密码不能缺少"]
},
age: {
type: Number,
min: [0, "年龄不能小于0"],
max: [120, "年龄不能大于120"]
},
role: {
type: Number,
default: 0 // 0是商家, 10086是管理员
},
created:{
type: Date,
default: Date.now()
}
});

module.exports = mongoose.model('user', schema);
  账户服务
   const User = require('../model/user');
const config = require('../config')
const crypto = require('lxj-crypto')

/**
* 根据用户名获取某个用户
* @param username
* @returns {Promise}
*/
async function getUserByUsername(username) {
return await User.findOne({username: username}).select("-__v")
}


async function checkCanInsertUser(user) {
//检查密码是否为空
if(!user.password || user.password.length===0){
throw Error("密码不能为空")
}
//检查用户是否已经存在
let res = await getUserByUsername(user.username);
if(res){
throw Error("用户名已经存在")
}
}

/**
* 添加普通用户
* @param user
* @returns {Promise}
*/
async function addUser(user) {
await checkCanInsertUser(user);

user.role = 0;
user.created = Date.now();

//对密码进行加密,加密的方式:使用username作为随机数对password进行哈希
user.password = crypto.md5Hmac(user.password, user.username)
await User.create(user)
}

async function deleteUser(id) {
let res = await User.deleteOne({_id:id});
if(!res || res.n===0){
throw Error("删除用户失败")
}
}

/**
* 登录的方法
* @param user
* @returns token
*/
async function login(user) {
// 1. 对密码进行加密
user.password = crypto.md5Hmac(user.password, user.username)
// 2. 进行查询
let res = await User.findOne({username:user.username, password: user.password});
if(!res){
throw Error("用户名或者密码错误")
}

// 说明用户名和密码验证成功,需要生产token返回给客户端,以后客户端的header中带上这个token
// token 生产方法:用aes进行对指定的data加密
let tokenData = {
username: user.username,
expire: Date.now() + config.TokenDuration
};
let token = crypto.aesEncrypt(JSON.stringify(tokenData), config.TokenKey);
return token
}

module.exports = {
getUserByUsername,
addUser,
deleteUser,
login
};

  帐号路由器
   let router = require("express").Router();
let accountService = require('../service/accout')


router.get("/:username", async (req, res)=>{
let user = await accountService.getUserByUsername(req.params.username);
res.success(user);
});

// 登录
router.post("/login", async (req, res)=>{
let token = await accountService.login(req.body);
res.success({token});
});

// 注册
router.post("/register", async (req, res)=>{
await accountService.register(req.body)
res.success()
});

router.delete("/:id", async (req, res)=>{
await accountService.deleteUser(req.params.id)
res.success()
});

module.exports = router;


```

nodejs抓取动态网页(Nodejs将JavaScript语言带到了服务器端(图)网络爬虫)

网站优化优采云 发表了文章 • 0 个评论 • 60 次浏览 • 2022-02-22 10:08 • 来自相关话题

  nodejs抓取动态网页(Nodejs将JavaScript语言带到了服务器端(图)网络爬虫)
  Nodejs 将 JavaScript 语言带到了服务器端。Nodejs作为js主要用户的前端,已经获得了服务器端的开发能力,但是除了用express搭建博客,还能做哪些好玩的项目呢?你为什么不只是一个网络爬虫。据说互联网上90%以上的流量是由爬虫贡献的。不知道是真是假,但至少证明了爬虫被广泛使用,尤其是在电商比价领域。它做的是爬虫中的弱鸡,只实现基本功能,见底部代码。
  运行结果:
  
  下面简要介绍实现过程。
  先选择爬取对象,这里也是门槛比较低的新闻站,因为我每天早上都要去逛街,比较熟悉,为什么新闻站比较简单,因为通常是这种类型的网站不需要用户登录,所有爬虫请求都很容易伪造。
  爬虫的基本思路是获取页面 -> 构造信息选择器 -> 分析页面链接 -> 获取相邻页面 -> 循环的第一步,对于新闻站来说,我们想要得到什么就是新闻内容,向页面请求html代码后,只要从页面中找到内容容器元素,就很容易得到新闻内容。下一步是获取相邻页面的地址。cnBeta新闻有上一页和下一页的链接,但仔细看会发现其实是用的。js是动态生成的。这里我们需要了解一下js是如何获取链接的。得到链接后,我们会继续获取下一条新闻的html代码,而且会一次又一次的开始。
  期间肯定不会一帆风顺。比如我遇到了301跳转。幸运的是,从请求头中很容易找到跳转目标。找到301跳转后,立即放弃请求,重新请求跳转地址。
  另外,爬虫不能无限爬取,否则很容易被IP屏蔽,需要设置爬取次数限制。最好再设置一个爬取间隔,不过这个只是用于学习Nodejs,不会有爬取,所以不需要再设置一个爬取间隔。
  详细流程分析见原文地址:%E7%88%AC%E8%99%AB%E5%AE%9E%E8%B7%B5%E5%B0%8F%E8%AE%B0/。
  项目源代码:Github。 查看全部

  nodejs抓取动态网页(Nodejs将JavaScript语言带到了服务器端(图)网络爬虫)
  Nodejs 将 JavaScript 语言带到了服务器端。Nodejs作为js主要用户的前端,已经获得了服务器端的开发能力,但是除了用express搭建博客,还能做哪些好玩的项目呢?你为什么不只是一个网络爬虫。据说互联网上90%以上的流量是由爬虫贡献的。不知道是真是假,但至少证明了爬虫被广泛使用,尤其是在电商比价领域。它做的是爬虫中的弱鸡,只实现基本功能,见底部代码。
  运行结果:
  
  下面简要介绍实现过程。
  先选择爬取对象,这里也是门槛比较低的新闻站,因为我每天早上都要去逛街,比较熟悉,为什么新闻站比较简单,因为通常是这种类型的网站不需要用户登录,所有爬虫请求都很容易伪造。
  爬虫的基本思路是获取页面 -> 构造信息选择器 -> 分析页面链接 -> 获取相邻页面 -> 循环的第一步,对于新闻站来说,我们想要得到什么就是新闻内容,向页面请求html代码后,只要从页面中找到内容容器元素,就很容易得到新闻内容。下一步是获取相邻页面的地址。cnBeta新闻有上一页和下一页的链接,但仔细看会发现其实是用的。js是动态生成的。这里我们需要了解一下js是如何获取链接的。得到链接后,我们会继续获取下一条新闻的html代码,而且会一次又一次的开始。
  期间肯定不会一帆风顺。比如我遇到了301跳转。幸运的是,从请求头中很容易找到跳转目标。找到301跳转后,立即放弃请求,重新请求跳转地址。
  另外,爬虫不能无限爬取,否则很容易被IP屏蔽,需要设置爬取次数限制。最好再设置一个爬取间隔,不过这个只是用于学习Nodejs,不会有爬取,所以不需要再设置一个爬取间隔。
  详细流程分析见原文地址:%E7%88%AC%E8%99%AB%E5%AE%9E%E8%B7%B5%E5%B0%8F%E8%AE%B0/。
  项目源代码:Github。

nodejs抓取动态网页(1.在项目文件夹安装两个必须的依赖包npminstallsupe是什么)

网站优化优采云 发表了文章 • 0 个评论 • 73 次浏览 • 2022-02-16 02:24 • 来自相关话题

  nodejs抓取动态网页(1.在项目文件夹安装两个必须的依赖包npminstallsupe是什么)
  在开始之前,请确保您已经安装了 Node.js 环境。如果没有安装,可以到Somei Technology下载安装。
  1.在项目文件夹中安装两个需要的依赖
  npminstallsupe
  在开始之前,请确保您已经安装了 Node.js 环境。如果没有安装,可以到Somei Technology下载安装。
  1.在项目文件夹中安装两个需要的依赖
  
npm install superagent --save-dev
  superagent是一个轻量级的渐进式ajax api,可读性好,学习曲线低,内部依赖nodejs原生请求api,适用于nodejs环境
  
npm install cheerio --save-dev
  Cheerio 是一个用于 nodejs 的爬虫页面模块。它是专门为服务器定制的一个快速、灵活且可实现的 jQuery 核心实现。适用于各种网络爬虫程序。等效于 node.js 中的 jQuery
  2.新建一个 crawler.js 文件
  
//导入依赖包
const http = require("http");
const path = require("path");
const url = require("url");
const fs = require("fs");
const superagent = require("superagent");
const cheerio = require("cheerio");
  3.获取 Boos 直接就业数据
  
superagent
.get("https://www.zhipin.com/job_det ... 6quot;)
.end((error,response)=>{
//获取页面文档数据
var content = response.text;
//cheerio也就是nodejs下的jQuery 将整个文档包装成一个集合,定义一个变量$接收
var $ = cheerio.load(content);
//定义一个空数组,用来接收数据
var result=[];
//分析文档结构 先获取每个li 再遍历里面的内容(此时每个li里面就存放着我们想要获取的数据)
$(".job-list li .job-primary").each((index,value)=>{
//地址和类型为一行显示,需要用到字符串截取
//地址
let address=$(value).find(".info-primary").children().eq(1).html();
//类型
let type=$(value).find(".info-company p").html();
//解码
address=unescape(address.replace(/&#x/g,'%u').replace(/;/g,''));
type=unescape(type.replace(/&#x/g,'%u').replace(/;/g,''))
//字符串截取
let addressArr=address.split('');
let typeArr=type.split('');
//将获取的数据以对象的形式添加到数组中
result.push({
title:$(value).find(".name .job-title").text(),
money:$(value).find(".name .red").text(),
address:addressArr,
company:$(value).find(".info-company a").text(),
type:typeArr,
position:$(value).find(".info-publis .name").text(),
txImg:$(value).find(".info-publis img").attr("src"),
time:$(value).find(".info-publis p").text()
});
// console.log(typeof $(value).find(".info-primary").children().eq(1).html());
});
//将数组转换成字符串
result=JSON.stringify(result);
//将数组输出到json文件里 刷新目录 即可看到当前文件夹多出一个boss.json文件(打开boss.json文件,ctrl+A全选之后 ctrl+K,再Ctrl+F即可将json文件自动排版)
fs.writeFile("boss.json",result,"utf-8",(error)=>{
//监听错误,如正常输出,则打印null
if(error==null){
console.log("恭喜您,数据爬取成功!请打开json文件,先Ctrl+A,再Ctrl+K,最后Ctrl+F格式化后查看json文件(仅限Visual Studio Code编辑器)");
}
});
});  
  总结
  以上就是小编介绍的爬虫爬取数据的Nodejs实现。我希望它对你有帮助。有任何问题请给我留言,小编会及时回复你的。还要感谢大家对染美科技的支持网站! 查看全部

  nodejs抓取动态网页(1.在项目文件夹安装两个必须的依赖包npminstallsupe是什么)
  在开始之前,请确保您已经安装了 Node.js 环境。如果没有安装,可以到Somei Technology下载安装。
  1.在项目文件夹中安装两个需要的依赖
  npminstallsupe
  在开始之前,请确保您已经安装了 Node.js 环境。如果没有安装,可以到Somei Technology下载安装。
  1.在项目文件夹中安装两个需要的依赖
  
npm install superagent --save-dev
  superagent是一个轻量级的渐进式ajax api,可读性好,学习曲线低,内部依赖nodejs原生请求api,适用于nodejs环境
  
npm install cheerio --save-dev
  Cheerio 是一个用于 nodejs 的爬虫页面模块。它是专门为服务器定制的一个快速、灵活且可实现的 jQuery 核心实现。适用于各种网络爬虫程序。等效于 node.js 中的 jQuery
  2.新建一个 crawler.js 文件
  
//导入依赖包
const http = require("http");
const path = require("path");
const url = require("url");
const fs = require("fs");
const superagent = require("superagent");
const cheerio = require("cheerio");
  3.获取 Boos 直接就业数据
  
superagent
.get("https://www.zhipin.com/job_det ... 6quot;)
.end((error,response)=>{
//获取页面文档数据
var content = response.text;
//cheerio也就是nodejs下的jQuery 将整个文档包装成一个集合,定义一个变量$接收
var $ = cheerio.load(content);
//定义一个空数组,用来接收数据
var result=[];
//分析文档结构 先获取每个li 再遍历里面的内容(此时每个li里面就存放着我们想要获取的数据)
$(".job-list li .job-primary").each((index,value)=>{
//地址和类型为一行显示,需要用到字符串截取
//地址
let address=$(value).find(".info-primary").children().eq(1).html();
//类型
let type=$(value).find(".info-company p").html();
//解码
address=unescape(address.replace(/&#x/g,'%u').replace(/;/g,''));
type=unescape(type.replace(/&#x/g,'%u').replace(/;/g,''))
//字符串截取
let addressArr=address.split('');
let typeArr=type.split('');
//将获取的数据以对象的形式添加到数组中
result.push({
title:$(value).find(".name .job-title").text(),
money:$(value).find(".name .red").text(),
address:addressArr,
company:$(value).find(".info-company a").text(),
type:typeArr,
position:$(value).find(".info-publis .name").text(),
txImg:$(value).find(".info-publis img").attr("src"),
time:$(value).find(".info-publis p").text()
});
// console.log(typeof $(value).find(".info-primary").children().eq(1).html());
});
//将数组转换成字符串
result=JSON.stringify(result);
//将数组输出到json文件里 刷新目录 即可看到当前文件夹多出一个boss.json文件(打开boss.json文件,ctrl+A全选之后 ctrl+K,再Ctrl+F即可将json文件自动排版)
fs.writeFile("boss.json",result,"utf-8",(error)=>{
//监听错误,如正常输出,则打印null
if(error==null){
console.log("恭喜您,数据爬取成功!请打开json文件,先Ctrl+A,再Ctrl+K,最后Ctrl+F格式化后查看json文件(仅限Visual Studio Code编辑器)");
}
});
});  
  总结
  以上就是小编介绍的爬虫爬取数据的Nodejs实现。我希望它对你有帮助。有任何问题请给我留言,小编会及时回复你的。还要感谢大家对染美科技的支持网站!

nodejs抓取动态网页(网络爬虫(webcrawler)、增量式网络机器人(incremental) )

网站优化优采云 发表了文章 • 0 个评论 • 90 次浏览 • 2022-02-16 02:22 • 来自相关话题

  nodejs抓取动态网页(网络爬虫(webcrawler)、增量式网络机器人(incremental)
)
  前言
  ?? 网络爬虫(又称网络蜘蛛、网络机器人,在foaf社区,更常称为网页追逐者),是一种按照一定的规则自动爬取万维网上信息的程序或脚本。其他不太常用的名称是 ant、autoindex、emulator 或 worm。
  ?? 我们可以使用网络爬虫来自动采集的数据信息,比如搜索引擎中的站点收录的爬取,对数据采集的数据分析和挖掘,用于财务分析进行采集在金融数据上,此外,网络爬虫还可用于舆情监测分析、目标客户数据采集等各个领域。
  1、网络爬虫分类
  根据系统结构和实现技术,网络爬虫大致可以分为以下几种:通用网络爬虫、聚焦网络爬虫、增量网络爬虫、深度网络爬虫。实际的网络爬虫系统通常是通过几种爬虫技术的组合来实现的。下面简单介绍一下这几种爬虫。
  1.1、万能网络爬虫
  又称可伸缩网络爬虫,爬取对象从一些种子URL扩展到整个网络,主要针对门户网站搜索引擎和大型网络服务商采集数据。
  1.2、关注网络爬虫
  话题爬虫也称为话题爬虫,是指选择性地爬取与预定义话题相关的页面的网络爬虫。与一般的网络爬虫相比,专注爬虫只需要爬取主题相关的页面,大大节省了硬件和网络资源,而且由于页面少,保存的页面更新也很快。信息需求。
  1.3、增量网络爬虫
  指增量更新下载的网页的爬虫,只爬取新生成或更改的网页。可以在一定程度上保证爬取的页面尽可能的新。.
  1.4、深网爬虫
  网页按存在方式可分为表层网页(surface web)和深层网页(deep web,也称为隐形网页或隐藏网页)。表面网页是指可以被传统搜索引擎检索到的页面,以及主要由可以通过超链接到达的静态网页组成的网页。深网是那些大部分内容无法通过静态链接访问的网页,隐藏在搜索表单后面,只有在用户提交一些 关键词 时才可用。
  2、创建一个简单的爬虫应用
  ?? 简单了解以上爬虫后,我们来实现一个简单的爬虫应用。
  2.1、实现目标
  说到爬虫,大概率会想到大数据,然后会想到python。百度之后,python的爬虫确实多了。由于我主要做前端开发,所以相对来说javascript更熟练,也更简单。实现一个小目标,然后用nodejs爬取首页文章列表(自己常用的一个开发者网站),然后写入本地json文件。
  2.2、环境建设
  nodejs安装好后,打开命令行,使用node -v查看nodejs是否安装成功,使用npm -v查看nodejs是否安装成功,如果安装成功应该打印如下信息(不同版本不同):
  
  2.3、具体实现
  2.3.1、安装依赖
  在目录下执行 npm install superagentcheerio --save-dev 安装superagent和cheerio。创建一个 crawler.js 文件。
  // 导入依赖包
const http = require("http");
const path = require("path");
const url = require("url");
const fs = require("fs");
const superagent = require("superagent");
const cheerio = require("cheerio");
  2.3.2、爬取数据
  然后获取请求页面,获取页面内容后,根据你想要的数据解析返回的DOM,最后将处理后的结果json转成字符串保存在本地。
  //爬取页面地址
const pageurl="https://www.cnblogs.com/";
// 解码字符串
function unescapestring(str){
if(!str){
return ''
}else{
return unescape(str.replace(/&#x/g,'%u').replace(/;/g,''));
}
}
// 抓取数据
function fetchdata(){
console.log('爬取数据时间节点:',new date());
superagent.get(pageurl).end((error,response)=>{
// 页面文档数据
let content=response.text;
if(content){
console.log('获取数据成功');
}
// 定义一个空数组来接收数据
let result=[];
let $=cheerio.load(content);
let postlist=$("#main #post_list .post_item");
postlist.each((index,value)=>{
let titlelnk=$(value).find('a.titlelnk');
let itemfoot=$(value).find('.post_item_foot');
let title=titlelnk.html(); //标题
let //链接
let author=itemfoot.find('a.lightblue').html(); //作者
let headlogo=$(value).find('.post_item_summary a img').attr('src'); //头像
let summary=$(value).find('.post_item_summary').text(); //简介
let postedtime=itemfoot.text().split('发布于 ')[1].substr(0,16); //发布时间
let readnum=itemfoot.text().split('阅读')[1]; //阅读量
readnum=readnum.substr(1,readnum.length-1);
title=unescapestring(title);

author=unescapestring(author);
headlogo=unescapestring(headlogo);
summary=unescapestring(summary);
postedtime=unescapestring(postedtime);
readnum=unescapestring(readnum);
result.push({
index,
title,
href,
author,
headlogo,
summary,
postedtime,
readnum
});
});
// 数组转换为字符串
result=json.stringify(result);
// 写入本地cnblogs.json文件中
fs.writefile("cnblogs.json",result,"utf-8",(err)=>{
// 监听错误,如正常输出,则打印null
if(!err){
console.log('写入数据成功');
}
});
});
}
fetchdata();
  3、执行优化
  3.1、生成结果
  在项目目录下打开命令行,输入node crawler.js,
  
  你会发现目录下会创建一个cnblogs.json文件,打开文件如下:
   查看全部

  nodejs抓取动态网页(网络爬虫(webcrawler)、增量式网络机器人(incremental)
)
  前言
  ?? 网络爬虫(又称网络蜘蛛、网络机器人,在foaf社区,更常称为网页追逐者),是一种按照一定的规则自动爬取万维网上信息的程序或脚本。其他不太常用的名称是 ant、autoindex、emulator 或 worm。
  ?? 我们可以使用网络爬虫来自动采集的数据信息,比如搜索引擎中的站点收录的爬取,对数据采集的数据分析和挖掘,用于财务分析进行采集在金融数据上,此外,网络爬虫还可用于舆情监测分析、目标客户数据采集等各个领域。
  1、网络爬虫分类
  根据系统结构和实现技术,网络爬虫大致可以分为以下几种:通用网络爬虫、聚焦网络爬虫、增量网络爬虫、深度网络爬虫。实际的网络爬虫系统通常是通过几种爬虫技术的组合来实现的。下面简单介绍一下这几种爬虫。
  1.1、万能网络爬虫
  又称可伸缩网络爬虫,爬取对象从一些种子URL扩展到整个网络,主要针对门户网站搜索引擎和大型网络服务商采集数据。
  1.2、关注网络爬虫
  话题爬虫也称为话题爬虫,是指选择性地爬取与预定义话题相关的页面的网络爬虫。与一般的网络爬虫相比,专注爬虫只需要爬取主题相关的页面,大大节省了硬件和网络资源,而且由于页面少,保存的页面更新也很快。信息需求。
  1.3、增量网络爬虫
  指增量更新下载的网页的爬虫,只爬取新生成或更改的网页。可以在一定程度上保证爬取的页面尽可能的新。.
  1.4、深网爬虫
  网页按存在方式可分为表层网页(surface web)和深层网页(deep web,也称为隐形网页或隐藏网页)。表面网页是指可以被传统搜索引擎检索到的页面,以及主要由可以通过超链接到达的静态网页组成的网页。深网是那些大部分内容无法通过静态链接访问的网页,隐藏在搜索表单后面,只有在用户提交一些 关键词 时才可用。
  2、创建一个简单的爬虫应用
  ?? 简单了解以上爬虫后,我们来实现一个简单的爬虫应用。
  2.1、实现目标
  说到爬虫,大概率会想到大数据,然后会想到python。百度之后,python的爬虫确实多了。由于我主要做前端开发,所以相对来说javascript更熟练,也更简单。实现一个小目标,然后用nodejs爬取首页文章列表(自己常用的一个开发者网站),然后写入本地json文件。
  2.2、环境建设
  nodejs安装好后,打开命令行,使用node -v查看nodejs是否安装成功,使用npm -v查看nodejs是否安装成功,如果安装成功应该打印如下信息(不同版本不同):
  
  2.3、具体实现
  2.3.1、安装依赖
  在目录下执行 npm install superagentcheerio --save-dev 安装superagent和cheerio。创建一个 crawler.js 文件。
  // 导入依赖包
const http = require("http");
const path = require("path");
const url = require("url");
const fs = require("fs");
const superagent = require("superagent");
const cheerio = require("cheerio");
  2.3.2、爬取数据
  然后获取请求页面,获取页面内容后,根据你想要的数据解析返回的DOM,最后将处理后的结果json转成字符串保存在本地。
  //爬取页面地址
const pageurl="https://www.cnblogs.com/";
// 解码字符串
function unescapestring(str){
if(!str){
return ''
}else{
return unescape(str.replace(/&#x/g,'%u').replace(/;/g,''));
}
}
// 抓取数据
function fetchdata(){
console.log('爬取数据时间节点:',new date());
superagent.get(pageurl).end((error,response)=>{
// 页面文档数据
let content=response.text;
if(content){
console.log('获取数据成功');
}
// 定义一个空数组来接收数据
let result=[];
let $=cheerio.load(content);
let postlist=$("#main #post_list .post_item");
postlist.each((index,value)=>{
let titlelnk=$(value).find('a.titlelnk');
let itemfoot=$(value).find('.post_item_foot');
let title=titlelnk.html(); //标题
let //链接
let author=itemfoot.find('a.lightblue').html(); //作者
let headlogo=$(value).find('.post_item_summary a img').attr('src'); //头像
let summary=$(value).find('.post_item_summary').text(); //简介
let postedtime=itemfoot.text().split('发布于 ')[1].substr(0,16); //发布时间
let readnum=itemfoot.text().split('阅读')[1]; //阅读量
readnum=readnum.substr(1,readnum.length-1);
title=unescapestring(title);

author=unescapestring(author);
headlogo=unescapestring(headlogo);
summary=unescapestring(summary);
postedtime=unescapestring(postedtime);
readnum=unescapestring(readnum);
result.push({
index,
title,
href,
author,
headlogo,
summary,
postedtime,
readnum
});
});
// 数组转换为字符串
result=json.stringify(result);
// 写入本地cnblogs.json文件中
fs.writefile("cnblogs.json",result,"utf-8",(err)=>{
// 监听错误,如正常输出,则打印null
if(!err){
console.log('写入数据成功');
}
});
});
}
fetchdata();
  3、执行优化
  3.1、生成结果
  在项目目录下打开命令行,输入node crawler.js,
  
  你会发现目录下会创建一个cnblogs.json文件,打开文件如下:
  

nodejs抓取动态网页(第二篇自动爬虫入门(二)爬取动态页面(puppeteer))

网站优化优采云 发表了文章 • 0 个评论 • 58 次浏览 • 2022-02-14 15:14 • 来自相关话题

  nodejs抓取动态网页(第二篇自动爬虫入门(二)爬取动态页面(puppeteer))
  上次写第二篇爬虫教程Node.js爬虫介绍(二)爬取动态页面(puppeteer)讲解使用puppeteer打开无头浏览器进行动态数据爬取,这是一年前的事了,现在的原因这么久没更新是因为开始学爬虫的目的是为了搭建一个自动爬虫系统,去年我开发了陌陌热榜,实现之后就停止研究爬虫了。
  
  陌陌热榜的开发涉及到很多功能模块,包括界面设计和前端开发,后端高度自定义爬虫功能和登录模块,数据表设计,自动化部署等,学到了很多,这次我将使用自动爬虫我们来谈谈这个功能。这次我们来说说定时爬取功能的实现:
  先打开你要爬取的网站看看是动态网页还是静态网页。重点就在这里。如果是静态网页,则很容易处理。使用cheerio进行dom爬取请参考第一讲。; 如果是ajax动态加载的话,除了第二讲,其实还有另一种方式,用F12打开开发者工具,找到网络请求,找到你要爬取的请求地址。
  
  以百度热榜为例,我每分钟抓取一次百度热榜的标题数据,拼接标题和搜索量,打印出来。它是一个动态加载的网页。按照上面的方法,找到请求地址为/mobile_v2/buzz/hotspot,然后尝试爬取:
  const request = require('request')
request('http://top.baidu.com/mobile_v2 ... 39%3B, (err, res) => {
if (err) {
console.log(err.code)
return false;
}
let data = JSON.parse(res.body).result.topwords
console.log(`${data[0].keyword} - ${data[0].searches}次搜索`)
})
// 运行结果 -> 湖北十堰爆炸致11死 省长赶往现场 - 4974558次搜索
  爬行没有问题。实现定时爬取,使用调度库实现简单的定时任务。使用 schedule.scheduleJob() 方法启动计划任务。第一个参数是定时器,即你想多久执行一次;第二个参数是执行函数。
  定时器建议使用 Cron 样式,如:'* * *' 6 个占位符代表秒、分、小时、天、月、星期几,星号代表完全匹配。- 在每分钟的第 30 秒触发:'30 * *' - 在每小时的 1 分 30 秒触发:'30 1 * *' - 在每天凌晨 1:1:30 触发:'30 1 1* ' - 每天 每月 1 日 1:1:30 触发:'30 1 1 1*' - 2016 年 1 月 1 日 1:1:30 触发:'30 1 1 1 2016' - 1 点 1:1每周的分 30 秒触发:'30 1 1 * 1'
  现在想每分钟抓取一次百度热搜头条的数据,那么
  const schedule = require('node-schedule')
const request = require('request')
schedule.scheduleJob('0 * * * * *', () => {
request('http://top.baidu.com/mobile_v2 ... 39%3B, (err, res) => {
if (err) {
console.log(err.code)
return false;
}
let data = JSON.parse(res.body).result.topwords
console.log(`${data[0].keyword} - ${data[0].searches}次搜索`)
})
})
  运行结果是:
  
  做吧~ 查看全部

  nodejs抓取动态网页(第二篇自动爬虫入门(二)爬取动态页面(puppeteer))
  上次写第二篇爬虫教程Node.js爬虫介绍(二)爬取动态页面(puppeteer)讲解使用puppeteer打开无头浏览器进行动态数据爬取,这是一年前的事了,现在的原因这么久没更新是因为开始学爬虫的目的是为了搭建一个自动爬虫系统,去年我开发了陌陌热榜,实现之后就停止研究爬虫了。
  
  陌陌热榜的开发涉及到很多功能模块,包括界面设计和前端开发,后端高度自定义爬虫功能和登录模块,数据表设计,自动化部署等,学到了很多,这次我将使用自动爬虫我们来谈谈这个功能。这次我们来说说定时爬取功能的实现:
  先打开你要爬取的网站看看是动态网页还是静态网页。重点就在这里。如果是静态网页,则很容易处理。使用cheerio进行dom爬取请参考第一讲。; 如果是ajax动态加载的话,除了第二讲,其实还有另一种方式,用F12打开开发者工具,找到网络请求,找到你要爬取的请求地址。
  
  以百度热榜为例,我每分钟抓取一次百度热榜的标题数据,拼接标题和搜索量,打印出来。它是一个动态加载的网页。按照上面的方法,找到请求地址为/mobile_v2/buzz/hotspot,然后尝试爬取:
  const request = require('request')
request('http://top.baidu.com/mobile_v2 ... 39%3B, (err, res) => {
if (err) {
console.log(err.code)
return false;
}
let data = JSON.parse(res.body).result.topwords
console.log(`${data[0].keyword} - ${data[0].searches}次搜索`)
})
// 运行结果 -> 湖北十堰爆炸致11死 省长赶往现场 - 4974558次搜索
  爬行没有问题。实现定时爬取,使用调度库实现简单的定时任务。使用 schedule.scheduleJob() 方法启动计划任务。第一个参数是定时器,即你想多久执行一次;第二个参数是执行函数。
  定时器建议使用 Cron 样式,如:'* * *' 6 个占位符代表秒、分、小时、天、月、星期几,星号代表完全匹配。- 在每分钟的第 30 秒触发:'30 * *' - 在每小时的 1 分 30 秒触发:'30 1 * *' - 在每天凌晨 1:1:30 触发:'30 1 1* ' - 每天 每月 1 日 1:1:30 触发:'30 1 1 1*' - 2016 年 1 月 1 日 1:1:30 触发:'30 1 1 1 2016' - 1 点 1:1每周的分 30 秒触发:'30 1 1 * 1'
  现在想每分钟抓取一次百度热搜头条的数据,那么
  const schedule = require('node-schedule')
const request = require('request')
schedule.scheduleJob('0 * * * * *', () => {
request('http://top.baidu.com/mobile_v2 ... 39%3B, (err, res) => {
if (err) {
console.log(err.code)
return false;
}
let data = JSON.parse(res.body).result.topwords
console.log(`${data[0].keyword} - ${data[0].searches}次搜索`)
})
})
  运行结果是:
  
  做吧~

nodejs抓取动态网页(/module/cvsresovle/js)

网站优化优采云 发表了文章 • 0 个评论 • 77 次浏览 • 2022-02-13 18:13 • 来自相关话题

  nodejs抓取动态网页(/module/cvsresovle/js)
  有这样一个需求,先从cvs文件中读取要解析的url数据,然后使用puppeteer和puppeteer-har获取浏览器的HAR数据。在调试的过程中发现for循环中怎么操作是异步的,终于找到了解决办法,这里也记录一下。
  1、创建解析csv文件的代码(ultra-harlog/module/cvsresovle.js)
  const fs = require("fs");
const path = require("path");
const csv =require('csv');
const parse = require('csv-parse/lib/sync')
const iconv = require('iconv-lite');
/*
npm install iconv-lite
*/
function readUrlRecord(csvpath){
console.log('开始解析文件:' + csvpath) ;
//读取文件
const input = fs.readFileSync(csvpath,'utf8') ;
/*
解析文件,生成JSON格式
{ ' ': '142',
AREA_NAME: '湖北',
SITE_LINK: 'www.banggo.com',
BEARING_MODE: '移动接入',
SITE_NAME: '邦购',
MENU_TYPE: '二级' }
*/
const records = parse(input, {
columns: true,
skip_empty_lines: true,
delimiter: ',',
}) ;
return records ;
}
//readUrlRecord('../top300.csv') ;
exports.readUrlRecord = readUrlRecord;
  2、创建抓取的主代码(ultra-harlog/module/puppeteerhar-event.js)
  const fs = require('fs');
const { promisify } = require('util');
const path = require("path");
const puppeteer = require('puppeteer');
const { harFromMessages } = require('chrome-har');
const logger=require("./log");
const log = logger.getPuppeteerHarEventRecordLogger() ;
//https://michaljanaszek.com/blo ... eteer
//https://www.npmjs.com/package/chrome-har
// 设置要监控的事件
const observe = [
'Page.loadEventFired',
'Page.domContentEventFired',
'Page.frameStartedLoading',
'Page.frameAttached',
'Network.requestWillBeSent',
'Network.requestServedFromCache',
'Network.dataReceived',
'Network.responseReceived',
'Network.resourceChangedPriority',
'Network.loadingFinished',
'Network.loadingFailed',
];
/*
启动浏览器
*/
async function launchBrowser(){
//启动浏览器实例 [puppeteer.createBrowserFetcher([options])]
let browser = await puppeteer.launch({
// 若是手动下载的chromium需要指定chromium地址, 默认引用地址为 /项目目录/node_modules/puppeteer/.local-chromium/
//executablePath: '/Users/huqiyang/Documents/project/z/chromium/Chromium.app/Contents/MacOS/Chromium',
//如果是访问https页面 此属性会忽略https错误
ignoreHTTPSErrors: true,
// 关闭headless模式, 不会打开浏览器
headless: true,
//浏览器启动参数 https://peter.sh/experiments/c ... ches/ --timeout
args:["--disk-cache-size=0","--disable-cache",'--disable-infobars','--window-size=800,600','--ignore-certificate-errors','--enable-feaures'],
//是否为每个选项卡自动打开DevTools面板。 如果此选项为true,则headless选项将设置为false。
devtools: false,
//Defaults to 30000 (30 seconds). Pass 0 to disable timeout.
timeout: 0
//放慢puppeteer执行的动作,方便调试
//slowMo: 250
});
return browser ;
}
async function saveHarlog(url,dirPath,filename){
let homesite = url ;
//保存的文件路径
let harFilePath = path.join(dirPath,filename) ;
//处理URL
if(!(url.startsWith('http://') || url.startsWith('https://'))){
url = "http://" + url ;
}
//打开浏览器
let browser = await launchBrowser() ;
//创建一个新页面
//let page = await browser.newPage();
let page = (await browser.pages())[0];
// 注册事件监听器
const client = await page.target().createCDPSession();
await client.send('Page.enable');
await client.send('Network.enable');
//用于保存用于转为为HAR数据的事件
const events = [];
observe.forEach(method => {
client.on(method, params => {
events.push({ method, params });
});
});
try{
// 执行跳转,访问制定的资源
await page.goto(url,{
timeout:0
});
}catch(error){
log.info('resovle error :' + url + "; error message:" + error) ;
}finally{
if(browser){
await browser.close();
}
}
const har = harFromMessages(events);
//resovleHar(har) ;
//log.info(JSON.stringify(har));
await promisify(fs.writeFile)(harFilePath, JSON.stringify(har));
}
exports.launchBrowser = launchBrowser;
exports.saveHarlog = saveHarlog;
  3、创建启动文件(ultra-harlog/puppeteerhar-event-app.js) 查看全部

  nodejs抓取动态网页(/module/cvsresovle/js)
  有这样一个需求,先从cvs文件中读取要解析的url数据,然后使用puppeteer和puppeteer-har获取浏览器的HAR数据。在调试的过程中发现for循环中怎么操作是异步的,终于找到了解决办法,这里也记录一下。
  1、创建解析csv文件的代码(ultra-harlog/module/cvsresovle.js)
  const fs = require("fs");
const path = require("path");
const csv =require('csv');
const parse = require('csv-parse/lib/sync')
const iconv = require('iconv-lite');
/*
npm install iconv-lite
*/
function readUrlRecord(csvpath){
console.log('开始解析文件:' + csvpath) ;
//读取文件
const input = fs.readFileSync(csvpath,'utf8') ;
/*
解析文件,生成JSON格式
{ ' ': '142',
AREA_NAME: '湖北',
SITE_LINK: 'www.banggo.com',
BEARING_MODE: '移动接入',
SITE_NAME: '邦购',
MENU_TYPE: '二级' }
*/
const records = parse(input, {
columns: true,
skip_empty_lines: true,
delimiter: ',',
}) ;
return records ;
}
//readUrlRecord('../top300.csv') ;
exports.readUrlRecord = readUrlRecord;
  2、创建抓取的主代码(ultra-harlog/module/puppeteerhar-event.js)
  const fs = require('fs');
const { promisify } = require('util');
const path = require("path");
const puppeteer = require('puppeteer');
const { harFromMessages } = require('chrome-har');
const logger=require("./log");
const log = logger.getPuppeteerHarEventRecordLogger() ;
//https://michaljanaszek.com/blo ... eteer
//https://www.npmjs.com/package/chrome-har
// 设置要监控的事件
const observe = [
'Page.loadEventFired',
'Page.domContentEventFired',
'Page.frameStartedLoading',
'Page.frameAttached',
'Network.requestWillBeSent',
'Network.requestServedFromCache',
'Network.dataReceived',
'Network.responseReceived',
'Network.resourceChangedPriority',
'Network.loadingFinished',
'Network.loadingFailed',
];
/*
启动浏览器
*/
async function launchBrowser(){
//启动浏览器实例 [puppeteer.createBrowserFetcher([options])]
let browser = await puppeteer.launch({
// 若是手动下载的chromium需要指定chromium地址, 默认引用地址为 /项目目录/node_modules/puppeteer/.local-chromium/
//executablePath: '/Users/huqiyang/Documents/project/z/chromium/Chromium.app/Contents/MacOS/Chromium',
//如果是访问https页面 此属性会忽略https错误
ignoreHTTPSErrors: true,
// 关闭headless模式, 不会打开浏览器
headless: true,
//浏览器启动参数 https://peter.sh/experiments/c ... ches/ --timeout
args:["--disk-cache-size=0","--disable-cache",'--disable-infobars','--window-size=800,600','--ignore-certificate-errors','--enable-feaures'],
//是否为每个选项卡自动打开DevTools面板。 如果此选项为true,则headless选项将设置为false。
devtools: false,
//Defaults to 30000 (30 seconds). Pass 0 to disable timeout.
timeout: 0
//放慢puppeteer执行的动作,方便调试
//slowMo: 250
});
return browser ;
}
async function saveHarlog(url,dirPath,filename){
let homesite = url ;
//保存的文件路径
let harFilePath = path.join(dirPath,filename) ;
//处理URL
if(!(url.startsWith('http://') || url.startsWith('https://'))){
url = "http://" + url ;
}
//打开浏览器
let browser = await launchBrowser() ;
//创建一个新页面
//let page = await browser.newPage();
let page = (await browser.pages())[0];
// 注册事件监听器
const client = await page.target().createCDPSession();
await client.send('Page.enable');
await client.send('Network.enable');
//用于保存用于转为为HAR数据的事件
const events = [];
observe.forEach(method => {
client.on(method, params => {
events.push({ method, params });
});
});
try{
// 执行跳转,访问制定的资源
await page.goto(url,{
timeout:0
});
}catch(error){
log.info('resovle error :' + url + "; error message:" + error) ;
}finally{
if(browser){
await browser.close();
}
}
const har = harFromMessages(events);
//resovleHar(har) ;
//log.info(JSON.stringify(har));
await promisify(fs.writeFile)(harFilePath, JSON.stringify(har));
}
exports.launchBrowser = launchBrowser;
exports.saveHarlog = saveHarlog;
  3、创建启动文件(ultra-harlog/puppeteerhar-event-app.js)

nodejs抓取动态网页(【技术创作101训练营】用NodeJS来入门image.png第二页)

网站优化优采云 发表了文章 • 0 个评论 • 59 次浏览 • 2022-02-13 03:10 • 来自相关话题

  nodejs抓取动态网页(【技术创作101训练营】用NodeJS来入门image.png第二页)
  【科技创造101训练营】使用NodeJS上手爬虫
  image.png 演讲第一页:大家好,今天主要给大家分享使用NodeJS上手爬虫 image.png 演讲第二页:现在简单介绍一下我讲的一些内容今天,首先,什么是爬行动物?还有为什么会有爬虫,然后怎么做爬虫,最后是扩展和总结 image.png 演讲第三页:什么是爬虫,嗯,我们写了一个爬虫程序,然后会是这样的一个爬虫,然后就会在网上层出不穷,自动模仿人,image.png第四页演讲:那为什么要有爬虫呢,嗯,比如说搜索引擎可以用爬虫爬一些关键词,一些内容,然后方便我们搜索;那么它也可以聚合信息,比如一些内容< @网站,比如头条等,可以利用爬虫整合其他平台的信息并下载HTML源代码,然后去获取数据;还有一种情况,当我们发现HTML源代码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 他们可以使用爬虫整合其他平台的信息并下载HTML源代码,然后去获取数据;还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 他们可以使用爬虫整合其他平台的信息并下载HTML源代码,然后去获取数据;还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png Lecture第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题
  346 查看全部

  nodejs抓取动态网页(【技术创作101训练营】用NodeJS来入门image.png第二页)
  【科技创造101训练营】使用NodeJS上手爬虫
  image.png 演讲第一页:大家好,今天主要给大家分享使用NodeJS上手爬虫 image.png 演讲第二页:现在简单介绍一下我讲的一些内容今天,首先,什么是爬行动物?还有为什么会有爬虫,然后怎么做爬虫,最后是扩展和总结 image.png 演讲第三页:什么是爬虫,嗯,我们写了一个爬虫程序,然后会是这样的一个爬虫,然后就会在网上层出不穷,自动模仿人,image.png第四页演讲:那为什么要有爬虫呢,嗯,比如说搜索引擎可以用爬虫爬一些关键词,一些内容,然后方便我们搜索;那么它也可以聚合信息,比如一些内容< @网站,比如头条等,可以利用爬虫整合其他平台的信息并下载HTML源代码,然后去获取数据;还有一种情况,当我们发现HTML源代码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 他们可以使用爬虫整合其他平台的信息并下载HTML源代码,然后去获取数据;还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 他们可以使用爬虫整合其他平台的信息并下载HTML源代码,然后去获取数据;还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png Lecture第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题
  346

nodejs抓取动态网页(前端渲染和后端渲染的优势是怎样的??)

网站优化优采云 发表了文章 • 0 个评论 • 48 次浏览 • 2022-02-11 08:06 • 来自相关话题

  nodejs抓取动态网页(前端渲染和后端渲染的优势是怎样的??)
  同构渲染是指在前后端使用js。第一次渲染时,使用 nodejs 来加载我们的 HTML 文件。用小白的话来说,同构渲染既不是后端渲染,也不是前端渲染,是两者之间的共同部分。同构渲染的实现其实是SSR+CSH,都是通过代码实现的。
  
  这里说的是前端渲染和后端渲染,各有各的优势。前端渲染的优点包括:可以部分刷新,可以实现懒加载技术,可以用js实现各种炫酷效果,可以通过cdn服务器访问资源,前后端开发都可以分离,学习成本相对较低。后端的主要优点是:可以解决首屏加载问题,有利于seo的优化。
  随着技术的发展,像Vue、React这样的框架已经能够实现很好的同构渲染,也就是我们常说的SSR,比如nuxt.js和next.js。在前端渲染非常普遍的今天,为什么还要有同构渲染的技术呢?
  如前所述,同构渲染其实可以解决首屏加载慢和SEO优化的问题。
  传统的搜索引擎从 HTML 文件中爬取我们需要的数据,所以前端渲染的页面无法爬取。开发过vue和react项目的开发者都知道,我们经常使用的SPA会将所有JS打包成一个整体。一个不容忽视的问题是文件太大,导致渲染前的等待时间过长。尤其是网速不好的时候,等待白屏结束对用户来说体验不是很好。
  服务端渲染可以先将使用过的数据渲染成最终的HTML直接展示,理想的避免了白屏问题。但没有什么是绝对的。如果一个页面的数据量太大,难免会出现等待的情况。
  说到PHP和JSP的渲染方式,都是前端将页面写到后端后渲染出来的HTML页面。其实pass中的SSR原理是一样的,只是多了一层CSH。其实现需要服务器提供初始化数据。 查看全部

  nodejs抓取动态网页(前端渲染和后端渲染的优势是怎样的??)
  同构渲染是指在前后端使用js。第一次渲染时,使用 nodejs 来加载我们的 HTML 文件。用小白的话来说,同构渲染既不是后端渲染,也不是前端渲染,是两者之间的共同部分。同构渲染的实现其实是SSR+CSH,都是通过代码实现的。
  
  这里说的是前端渲染和后端渲染,各有各的优势。前端渲染的优点包括:可以部分刷新,可以实现懒加载技术,可以用js实现各种炫酷效果,可以通过cdn服务器访问资源,前后端开发都可以分离,学习成本相对较低。后端的主要优点是:可以解决首屏加载问题,有利于seo的优化。
  随着技术的发展,像Vue、React这样的框架已经能够实现很好的同构渲染,也就是我们常说的SSR,比如nuxt.js和next.js。在前端渲染非常普遍的今天,为什么还要有同构渲染的技术呢?
  如前所述,同构渲染其实可以解决首屏加载慢和SEO优化的问题。
  传统的搜索引擎从 HTML 文件中爬取我们需要的数据,所以前端渲染的页面无法爬取。开发过vue和react项目的开发者都知道,我们经常使用的SPA会将所有JS打包成一个整体。一个不容忽视的问题是文件太大,导致渲染前的等待时间过长。尤其是网速不好的时候,等待白屏结束对用户来说体验不是很好。
  服务端渲染可以先将使用过的数据渲染成最终的HTML直接展示,理想的避免了白屏问题。但没有什么是绝对的。如果一个页面的数据量太大,难免会出现等待的情况。
  说到PHP和JSP的渲染方式,都是前端将页面写到后端后渲染出来的HTML页面。其实pass中的SSR原理是一样的,只是多了一层CSH。其实现需要服务器提供初始化数据。

nodejs抓取动态网页(ES6ES6模块的只读代码和javascript的区别?模块)

网站优化优采云 发表了文章 • 0 个评论 • 54 次浏览 • 2022-02-10 10:12 • 来自相关话题

  nodejs抓取动态网页(ES6ES6模块的只读代码和javascript的区别?模块)
  安装node,百度查,node -v查询版本
  node.js 和 javascript 之间的区别
  es
定义了语法,js和nodejs都要遵守
不能操作dom,不能监听click,不能发送ajax
js
使用es语法,外加webAPI,可以操作dom,事件绑定,ajax等
nodejs
使用es语法,外加nodejsAPI,处理htpp,处理文件等
  commonjs 模块化,node.js 使用了这种模块化
  一个.js
  function add(a, b) {
return a + b
}
function mul(a, b) {
return a * b
}
module.exports = {
add,
mul
}
  b.js
  const { add, mul } = require('./a')
const _ = require('lodash')
const sum = add(10, 20)
const result = mul(100, 200)
console.log(sum)
console.log(result)
const arr = _.concat([1, 2], 3)
console.log('arr...', arr)
  npm init 初始化,运行,节点 b.js
  CommonJS 模块和 ES6 模块 CommonJS 的区别在于基本数据类型的复制。将被模块缓存。同时,该模块输出的变量可以在另一个模块中重新赋值。对于复杂的数据类型,它是一个浅拷贝。由于两个模块引用的对象都指向同一个内存空间,所以改变这个模块的值会影响到另一个模块。当使用 require 命令加载模块时,会运行整个模块的代码。当使用 require 命令加载同一个模块时,该模块不会被执行,而是会检索缓存中的值。也就是说,CommonJS 模块无论加载多少次,第一次加载时只会运行一次。如果稍后加载,它将返回第一次运行的结果,除非手动清除系统缓存。当循环被加载时,它属于加载时执行。也就是当需要脚本代码的时候,都会全部执行。一旦一个模块被“循环加载”,只会输出执行的部分,不会输出未执行的部分。ES6 Modules ES6 模块中的值属于[Dynamic Read-Only References]。对于只读,即不允许修改导入变量的值。导入的变量是只读的,无论是基本数据类型还是复杂数据类型。当模块遇到导入命令时,会生成一个只读引用。当脚本实际执行时,根据这个只读引用,去加载的模块获取值。对于动态,原创值发生变化,导入加载的值也发生变化。无论是基本数据类型还是复杂数据类型。ES6 模块在循环加载时被动态引用。只要两个模块之间有一些引用,代码就可以执行。
  默认导出   export default 变量或者函数或者对象
默认引入  import name from "相对或绝对路径" 
导出的名字和引入的名字可以不一致
按需导出  export 需要声明 变量用const var let  函数用function
按需引入  import {变量名或函数名} form "路径"    
全部引入 使用  import * as 自定义name "路径"
会将默认导出和按需导出 全部引入
  服务器开发和前端开发的区别
  1.服务稳定,服务端可能遭受恶意攻击
  2.日志记录,单个客户端可以意外挂掉,但是服务端不行,pm2正在等待进程
  3.内存优化,服务器端承载很多请求,CPU和内存都是稀缺资源,用stream写日志,用redis存储session
  4.安全,服务器端可能会受到恶意攻击,未经授权的操作,数据库攻击,防止xss攻击,spl注入
  5.集群和服务拆分,扩大服务和机器拆分流量
  6.日志记录,服务器要记录日志,存储日志,分析日志。 查看全部

  nodejs抓取动态网页(ES6ES6模块的只读代码和javascript的区别?模块)
  安装node,百度查,node -v查询版本
  node.js 和 javascript 之间的区别
  es
定义了语法,js和nodejs都要遵守
不能操作dom,不能监听click,不能发送ajax
js
使用es语法,外加webAPI,可以操作dom,事件绑定,ajax等
nodejs
使用es语法,外加nodejsAPI,处理htpp,处理文件等
  commonjs 模块化,node.js 使用了这种模块化
  一个.js
  function add(a, b) {
return a + b
}
function mul(a, b) {
return a * b
}
module.exports = {
add,
mul
}
  b.js
  const { add, mul } = require('./a')
const _ = require('lodash')
const sum = add(10, 20)
const result = mul(100, 200)
console.log(sum)
console.log(result)
const arr = _.concat([1, 2], 3)
console.log('arr...', arr)
  npm init 初始化,运行,节点 b.js
  CommonJS 模块和 ES6 模块 CommonJS 的区别在于基本数据类型的复制。将被模块缓存。同时,该模块输出的变量可以在另一个模块中重新赋值。对于复杂的数据类型,它是一个浅拷贝。由于两个模块引用的对象都指向同一个内存空间,所以改变这个模块的值会影响到另一个模块。当使用 require 命令加载模块时,会运行整个模块的代码。当使用 require 命令加载同一个模块时,该模块不会被执行,而是会检索缓存中的值。也就是说,CommonJS 模块无论加载多少次,第一次加载时只会运行一次。如果稍后加载,它将返回第一次运行的结果,除非手动清除系统缓存。当循环被加载时,它属于加载时执行。也就是当需要脚本代码的时候,都会全部执行。一旦一个模块被“循环加载”,只会输出执行的部分,不会输出未执行的部分。ES6 Modules ES6 模块中的值属于[Dynamic Read-Only References]。对于只读,即不允许修改导入变量的值。导入的变量是只读的,无论是基本数据类型还是复杂数据类型。当模块遇到导入命令时,会生成一个只读引用。当脚本实际执行时,根据这个只读引用,去加载的模块获取值。对于动态,原创值发生变化,导入加载的值也发生变化。无论是基本数据类型还是复杂数据类型。ES6 模块在循环加载时被动态引用。只要两个模块之间有一些引用,代码就可以执行。
  默认导出   export default 变量或者函数或者对象
默认引入  import name from "相对或绝对路径" 
导出的名字和引入的名字可以不一致
按需导出  export 需要声明 变量用const var let  函数用function
按需引入  import {变量名或函数名} form "路径"    
全部引入 使用  import * as 自定义name "路径"
会将默认导出和按需导出 全部引入
  服务器开发和前端开发的区别
  1.服务稳定,服务端可能遭受恶意攻击
  2.日志记录,单个客户端可以意外挂掉,但是服务端不行,pm2正在等待进程
  3.内存优化,服务器端承载很多请求,CPU和内存都是稀缺资源,用stream写日志,用redis存储session
  4.安全,服务器端可能会受到恶意攻击,未经授权的操作,数据库攻击,防止xss攻击,spl注入
  5.集群和服务拆分,扩大服务和机器拆分流量
  6.日志记录,服务器要记录日志,存储日志,分析日志。

nodejs抓取动态网页( ()函数的处理方法与对应的修改 )

网站优化优采云 发表了文章 • 0 个评论 • 54 次浏览 • 2022-02-07 22:15 • 来自相关话题

  nodejs抓取动态网页(
()函数的处理方法与对应的修改
)
  <p>现存在如下图所示的文件目录: <br /> <br /> 我们希望通过02.js来实现能够静态加载static中的文件 <br /> 这篇博客是我的上一篇博客静态资源管理续篇,在上一篇博客中,因为遇到加载json格式的文件遇到了问题,所以写了一个续篇,如果有感兴趣的小伙伴可以看一下 <br /> 实现代码如下:
var http = require(&#39;http&#39;);
var url = require(&#39;url&#39;);
var fs = require(&#39;fs&#39;);
var path = require(&#39;path&#39;);
var server = http.createServer(function (req,res) {
//这里如果不用req.url来判断,那么不管用户输入什么操作,执行的操作都是一样的
//得到地址
var pathname = url.parse(req.url).pathname;
//不处理小图标的请求
if(pathname == &#39;/favicon.ico&#39;){
return;
}
//判断此时用户输入的是文件地址还是文件夹地址
//如果是文件夹地址,那么自动请求这个文件夹中的index.html
if(pathname.indexOf(&#39;.&#39;) == -1){
pathname += &#39;index.html&#39;;
}
//得到用户输入的文件的拓展名
var extname = path.extname(pathname);
console.log(pathname);
fs.readFile(&#39;./static/&#39;+pathname,function (err,data) {
if(err){
//如果页面报错,则让它返回一个新的页面
fs.readFile(&#39;./static/404.html&#39;,function (err,data) {
res.writeHead(200,{&#39;Content-Type&#39;:&#39;text/html;charset=UTF8&#39;});
res.end(data);
})
return;
}
//读完文件之后做的事情
console.log(process.cwd());
var mime = getMime(extname);
res.writeHead(200,{&#39;Content-Type&#39;:mime+&#39;;charset=UTF8&#39;});
res.end(data);
})
}).listen(80,&#39;127.0.0.1&#39;);
function getMime(extname) {
//读取mime.josn文件,得到JOSN,根据extname key ,f返回对应的value值
switch (extname){
case &#39;.html&#39;:
return &#39;text/html&#39;;
break;
case &#39;.css&#39;:
return &#39;text/css&#39;;
break;
case &#39;.img&#39;:
return &#39;image/img&#39;;
break;
case &#39;.json&#39;:
fs.readFile(&#39;./static/mime.json&#39;,function (err,data) {
if(err){
throw Error("找不到mime.json文件!");
return;
}
//转成JSON
console.log(typeof data);
//利用json.parse将一个josn字符串解析成为json格式
var mimeJSON = JSON.parse(data);
console.log(mimeJSON[extname]);
return mimeJSON[extname];
})
}
}</p>
  当我们读取到文件的扩展名是json时,我们读取的是json格式的文件,然后使用JSON.parse()方法将JSON字符串转换为JSON对象,然后检索JSON对象。转成json扩展名对应的MIME类型。
  然而,我们惊讶地发现响应头中的 content-type 类型是未定义的,
  
  但是我们不是已经在这部分代码中写好文件类型了吗?
  
  这是因为getMime()函数,当传入参数为'.json'时,函数执行代码中收录异步操作,即读取json文件,所以在未读取文件时,mime有还没读完。赋值时mime已写入content-type,未定义;
  那我们该怎么办呢?
  我们采取强制回调函数getMime的方法,传入文件的扩展名后,再执行回调函数中的内容,并将mime的值赋值给content-type,避免出现问题异步加载。
  修改代码如下:
  getMime(extname,function(mime){
res.writeHead(200,{&#39;Content-Type&#39;:mime+&#39;;charset=UTF8&#39;});
res.end(data);
})
  这样,我们就可以在参数mime的值返回后,进行将type值赋给Content-type的操作。对应的getMime()函数也应该相应修改:
  function getMime(extname,callback){
//读取mime的json文件,将对应扩展名的MIME类型名返回
fs.readfile(&#39;./static/mime.json&#39;,function(err,data){
if(err){
throw Error(&#39;找不到mime.json类型的文件!&#39;);
}
//将json字符串转换为json对象
var mimeJSON = JSON.parse(data);
var mime = mimeJSON[extname] || &#39;text/plain
&#39;;
callback(mime);
})
} 查看全部

  nodejs抓取动态网页(
()函数的处理方法与对应的修改
)
  <p>现存在如下图所示的文件目录: <br /> <br /> 我们希望通过02.js来实现能够静态加载static中的文件 <br /> 这篇博客是我的上一篇博客静态资源管理续篇,在上一篇博客中,因为遇到加载json格式的文件遇到了问题,所以写了一个续篇,如果有感兴趣的小伙伴可以看一下 <br /> 实现代码如下:
var http = require(&#39;http&#39;);
var url = require(&#39;url&#39;);
var fs = require(&#39;fs&#39;);
var path = require(&#39;path&#39;);
var server = http.createServer(function (req,res) {
//这里如果不用req.url来判断,那么不管用户输入什么操作,执行的操作都是一样的
//得到地址
var pathname = url.parse(req.url).pathname;
//不处理小图标的请求
if(pathname == &#39;/favicon.ico&#39;){
return;
}
//判断此时用户输入的是文件地址还是文件夹地址
//如果是文件夹地址,那么自动请求这个文件夹中的index.html
if(pathname.indexOf(&#39;.&#39;) == -1){
pathname += &#39;index.html&#39;;
}
//得到用户输入的文件的拓展名
var extname = path.extname(pathname);
console.log(pathname);
fs.readFile(&#39;./static/&#39;+pathname,function (err,data) {
if(err){
//如果页面报错,则让它返回一个新的页面
fs.readFile(&#39;./static/404.html&#39;,function (err,data) {
res.writeHead(200,{&#39;Content-Type&#39;:&#39;text/html;charset=UTF8&#39;});
res.end(data);
})
return;
}
//读完文件之后做的事情
console.log(process.cwd());
var mime = getMime(extname);
res.writeHead(200,{&#39;Content-Type&#39;:mime+&#39;;charset=UTF8&#39;});
res.end(data);
})
}).listen(80,&#39;127.0.0.1&#39;);
function getMime(extname) {
//读取mime.josn文件,得到JOSN,根据extname key ,f返回对应的value值
switch (extname){
case &#39;.html&#39;:
return &#39;text/html&#39;;
break;
case &#39;.css&#39;:
return &#39;text/css&#39;;
break;
case &#39;.img&#39;:
return &#39;image/img&#39;;
break;
case &#39;.json&#39;:
fs.readFile(&#39;./static/mime.json&#39;,function (err,data) {
if(err){
throw Error("找不到mime.json文件!");
return;
}
//转成JSON
console.log(typeof data);
//利用json.parse将一个josn字符串解析成为json格式
var mimeJSON = JSON.parse(data);
console.log(mimeJSON[extname]);
return mimeJSON[extname];
})
}
}</p>
  当我们读取到文件的扩展名是json时,我们读取的是json格式的文件,然后使用JSON.parse()方法将JSON字符串转换为JSON对象,然后检索JSON对象。转成json扩展名对应的MIME类型。
  然而,我们惊讶地发现响应头中的 content-type 类型是未定义的,
  
  但是我们不是已经在这部分代码中写好文件类型了吗?
  
  这是因为getMime()函数,当传入参数为'.json'时,函数执行代码中收录异步操作,即读取json文件,所以在未读取文件时,mime有还没读完。赋值时mime已写入content-type,未定义;
  那我们该怎么办呢?
  我们采取强制回调函数getMime的方法,传入文件的扩展名后,再执行回调函数中的内容,并将mime的值赋值给content-type,避免出现问题异步加载。
  修改代码如下:
  getMime(extname,function(mime){
res.writeHead(200,{&#39;Content-Type&#39;:mime+&#39;;charset=UTF8&#39;});
res.end(data);
})
  这样,我们就可以在参数mime的值返回后,进行将type值赋给Content-type的操作。对应的getMime()函数也应该相应修改:
  function getMime(extname,callback){
//读取mime的json文件,将对应扩展名的MIME类型名返回
fs.readfile(&#39;./static/mime.json&#39;,function(err,data){
if(err){
throw Error(&#39;找不到mime.json类型的文件!&#39;);
}
//将json字符串转换为json对象
var mimeJSON = JSON.parse(data);
var mime = mimeJSON[extname] || &#39;text/plain
&#39;;
callback(mime);
})
}

nodejs抓取动态网页(puppeteer和nodejs的区别)

网站优化优采云 发表了文章 • 0 个评论 • 104 次浏览 • 2022-02-06 15:03 • 来自相关话题

  nodejs抓取动态网页(puppeteer和nodejs的区别)
  傀儡师
  google chrome 团队出品的 puppeteer 是一个依赖 nodejs 和 chromium 的自动化测试库。它最大的优点是可以处理网页中的动态内容,比如JavaScript,可以更好地模拟用户。
  一些网站的反爬方法隐藏了一些javascript/ajax请求中的部分内容,使得直接获取a标签的方法不起作用。甚至一些 网站 会设置隐藏元素“陷阱”,对用户不可见,脚本将其作为机器触发。在这种情况下,puppeteer的优势就凸显出来了。
  它可以实现以下功能:
  生成页面的屏幕截图和 PDF。抓取 SPA 并生成预渲染内容(即“xxxx”)。自动提交表单、UI测试、键盘输入等。创建最新的自动化测试环境。使用最新的 JavaScript 和浏览器功能直接在最新版本的 Chrome 中运行测试。捕获跟踪您的 网站 的时间线以帮助诊断性能问题。
  开源地址:[][1]
  安装
  npm i puppeteer
  注意先安装nodejs,在nodejs文件的根目录下执行(与npm文件同级)。
  安装过程中会下载chromium,大约120M。
  经过两天(大约10个小时)的探索,绕过了很多异步的坑,作者对puppeteer和nodejs有一定的把握。
  长图,抢博客文章列表:
  
  爬博客文章
  以csdn博客为例,文章的内容需要通过点击“阅读全文”获取,使得只能阅读dom的脚本失效。
  /**
* load blog.csdn.net article to local files
**/
const puppeteer = require('puppeteer');
//emulate iphone
const userAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1';
const workPath = './contents';
const fs = require("fs");
if (!fs.existsSync(workPath)) {
fs.mkdirSync(workPath)
}
//base url
const rootUrl = 'https://blog.csdn.net/';
//max wait milliseconds
const maxWait = 100;
//max loop scroll times
const makLoop = 10;
(async () => {
let url;
let countUrl=0;
const browser = await puppeteer.launch({headless: false});//set headless: true will hide chromium UI
const page = await browser.newPage();
await page.setUserAgent(userAgent);
await page.setViewport({width:414, height:736});
await page.setRequestInterception(true);
//filter to block images
page.on('request', request => {
if (request.resourceType() === 'image')
request.abort();
else
request.continue();
});
await page.goto(rootUrl);
for(let i= 0; iwindow.scrollTo(0, document.body.scrollHeight));
await page.waitForNavigation({timeout:maxWait,waitUntil: ['networkidle0']});
}catch(err){
console.log('scroll to bottom and then wait '+maxWait+'ms.');
}
}
await page.screenshot({path: workPath+'/screenshot.png',fullPage: true, quality :100, type :'jpeg'});
//#feedlist_id li[data-type="blog"] a
const sel = '#feedlist_id li[data-type="blog"] h2 a';
const hrefs = await page.evaluate((sel) => {
let elements = Array.from(document.querySelectorAll(sel));
let links = elements.map(element => {
return element.href
})
return links;
}, sel);
console.log('total links: '+hrefs.length);
process();
async function process(){
if(countUrl {
if (request.resourceType() === 'image')
request.abort();
else
request.continue();
});
await tab.goto(url);
//execute tap request
try{
await tab.tap('.read_more_btn');
}catch(err){
console.log('there\'s none read more button. No need to TAP');
}
let title = await tab.evaluate(() => document.querySelector('#article .article_title').innerText);
let contents = await tab.evaluate(() => document.querySelector('#article .article_content').innerText);
contents = 'TITLE: '+title+'\nURL: '+url+'\nCONTENTS: \n'+contents;
const fs = require("fs");
fs.writeFileSync(workPath+'/'+tab.url().substring(tab.url().lastIndexOf('/'),tab.url().length)+'.txt',contents);
console.log(title + " has been downloaded to local.");
await tab.close();
}catch(err){
console.log('url: '+tab.url()+' \n'+err.toString());
}finally{
process();
}
}
})();
  实施过程
  录屏可以在我的公众号查看,截图如下:
  
  结果
  文章内容列表:
  
  文章内容:
  
  结束语
  之前我想既然nodejs使用的是JavaScript脚本语言,那么它一定能够处理网页的JavaScript内容,但是我还没有找到一个合适/高效的库。直到我找到了 puppeteer,我才决定试水。
  说了这么多,nodejs的异步性真是让人头疼。这几百行代码我折腾了10个小时。
  您可以在代码中展开 process() 方法并使用 async.eachSeries。我使用的递归方法不是最佳解决方案。
  事实上,一个一个地处理是没有效率的。本来我写了一个异步关闭浏览器的方法:
<p>let tryCloseBrowser = setInterval(function(){
console.log("check if any process running...")
if(countDown 查看全部

  nodejs抓取动态网页(puppeteer和nodejs的区别)
  傀儡师
  google chrome 团队出品的 puppeteer 是一个依赖 nodejs 和 chromium 的自动化测试库。它最大的优点是可以处理网页中的动态内容,比如JavaScript,可以更好地模拟用户。
  一些网站的反爬方法隐藏了一些javascript/ajax请求中的部分内容,使得直接获取a标签的方法不起作用。甚至一些 网站 会设置隐藏元素“陷阱”,对用户不可见,脚本将其作为机器触发。在这种情况下,puppeteer的优势就凸显出来了。
  它可以实现以下功能:
  生成页面的屏幕截图和 PDF。抓取 SPA 并生成预渲染内容(即“xxxx”)。自动提交表单、UI测试、键盘输入等。创建最新的自动化测试环境。使用最新的 JavaScript 和浏览器功能直接在最新版本的 Chrome 中运行测试。捕获跟踪您的 网站 的时间线以帮助诊断性能问题。
  开源地址:[][1]
  安装
  npm i puppeteer
  注意先安装nodejs,在nodejs文件的根目录下执行(与npm文件同级)。
  安装过程中会下载chromium,大约120M。
  经过两天(大约10个小时)的探索,绕过了很多异步的坑,作者对puppeteer和nodejs有一定的把握。
  长图,抢博客文章列表:
  
  爬博客文章
  以csdn博客为例,文章的内容需要通过点击“阅读全文”获取,使得只能阅读dom的脚本失效。
  /**
* load blog.csdn.net article to local files
**/
const puppeteer = require('puppeteer');
//emulate iphone
const userAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1';
const workPath = './contents';
const fs = require("fs");
if (!fs.existsSync(workPath)) {
fs.mkdirSync(workPath)
}
//base url
const rootUrl = 'https://blog.csdn.net/';
//max wait milliseconds
const maxWait = 100;
//max loop scroll times
const makLoop = 10;
(async () => {
let url;
let countUrl=0;
const browser = await puppeteer.launch({headless: false});//set headless: true will hide chromium UI
const page = await browser.newPage();
await page.setUserAgent(userAgent);
await page.setViewport({width:414, height:736});
await page.setRequestInterception(true);
//filter to block images
page.on('request', request => {
if (request.resourceType() === 'image')
request.abort();
else
request.continue();
});
await page.goto(rootUrl);
for(let i= 0; iwindow.scrollTo(0, document.body.scrollHeight));
await page.waitForNavigation({timeout:maxWait,waitUntil: ['networkidle0']});
}catch(err){
console.log('scroll to bottom and then wait '+maxWait+'ms.');
}
}
await page.screenshot({path: workPath+'/screenshot.png',fullPage: true, quality :100, type :'jpeg'});
//#feedlist_id li[data-type="blog"] a
const sel = '#feedlist_id li[data-type="blog"] h2 a';
const hrefs = await page.evaluate((sel) => {
let elements = Array.from(document.querySelectorAll(sel));
let links = elements.map(element => {
return element.href
})
return links;
}, sel);
console.log('total links: '+hrefs.length);
process();
async function process(){
if(countUrl {
if (request.resourceType() === 'image')
request.abort();
else
request.continue();
});
await tab.goto(url);
//execute tap request
try{
await tab.tap('.read_more_btn');
}catch(err){
console.log('there\'s none read more button. No need to TAP');
}
let title = await tab.evaluate(() => document.querySelector('#article .article_title').innerText);
let contents = await tab.evaluate(() => document.querySelector('#article .article_content').innerText);
contents = 'TITLE: '+title+'\nURL: '+url+'\nCONTENTS: \n'+contents;
const fs = require("fs");
fs.writeFileSync(workPath+'/'+tab.url().substring(tab.url().lastIndexOf('/'),tab.url().length)+'.txt',contents);
console.log(title + " has been downloaded to local.");
await tab.close();
}catch(err){
console.log('url: '+tab.url()+' \n'+err.toString());
}finally{
process();
}
}
})();
  实施过程
  录屏可以在我的公众号查看,截图如下:
  
  结果
  文章内容列表:
  
  文章内容:
  
  结束语
  之前我想既然nodejs使用的是JavaScript脚本语言,那么它一定能够处理网页的JavaScript内容,但是我还没有找到一个合适/高效的库。直到我找到了 puppeteer,我才决定试水。
  说了这么多,nodejs的异步性真是让人头疼。这几百行代码我折腾了10个小时。
  您可以在代码中展开 process() 方法并使用 async.eachSeries。我使用的递归方法不是最佳解决方案。
  事实上,一个一个地处理是没有效率的。本来我写了一个异步关闭浏览器的方法:
<p>let tryCloseBrowser = setInterval(function(){
console.log("check if any process running...")
if(countDown

nodejs抓取动态网页(我目前正在使用Express.js创建我的网站。。)

网站优化优采云 发表了文章 • 0 个评论 • 54 次浏览 • 2022-02-06 05:28 • 来自相关话题

  nodejs抓取动态网页(我目前正在使用Express.js创建我的网站。。)
  我目前正在使用 Express.js 来创建我的 网站。我的主服务器脚本称为 index.coffee。我还创建了一个脚本 request.js,它发出一个 GET 请求并显示响应
   console.log(list);
  我从控制台运行脚本没有任何问题:node request.js
  我的问题是:如何让页面上的“获取此列表”按钮通过在同一页面上显示列表来响应点击(即request.js在服务器上执行并显示结果)?
  app.js
  /**
* Module dependencies.
*/
var express = require('express')
, routes = require('./routes');
var app = module.exports = express.createServer();
// Configuration
app.configure(function(){
app.set('views', __dirname + '/views');
app.set ('view engine', 'coffee');
app.register('.coffee', require('coffeekup').adapters.express);
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
app.configure('development', function(){
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
app.use(express.errorHandler());
});
app.get('/', function(req, res) {
res.render('index',{ layout: false });
});
app.listen(3000);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
  索引咖啡
  doctype 5
html ->
head ->
body
  p->“嘿” 查看全部

  nodejs抓取动态网页(我目前正在使用Express.js创建我的网站。。)
  我目前正在使用 Express.js 来创建我的 网站。我的主服务器脚本称为 index.coffee。我还创建了一个脚本 request.js,它发出一个 GET 请求并显示响应
   console.log(list);
  我从控制台运行脚本没有任何问题:node request.js
  我的问题是:如何让页面上的“获取此列表”按钮通过在同一页面上显示列表来响应点击(即request.js在服务器上执行并显示结果)?
  app.js
  /**
* Module dependencies.
*/
var express = require('express')
, routes = require('./routes');
var app = module.exports = express.createServer();
// Configuration
app.configure(function(){
app.set('views', __dirname + '/views');
app.set ('view engine', 'coffee');
app.register('.coffee', require('coffeekup').adapters.express);
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
app.configure('development', function(){
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
app.use(express.errorHandler());
});
app.get('/', function(req, res) {
res.render('index',{ layout: false });
});
app.listen(3000);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
  索引咖啡
  doctype 5
html ->
head ->
body
  p->“嘿”

nodejs抓取动态网页( 如何在NodeJS的活跃生态系统帮助下高效地抓取Web内容)

网站优化优采云 发表了文章 • 0 个评论 • 462 次浏览 • 2022-02-06 01:15 • 来自相关话题

  nodejs抓取动态网页(
如何在NodeJS的活跃生态系统帮助下高效地抓取Web内容)
  
  作者 | Shenesh Perera 翻译 | 王强策划| 李俊辰 Javascript 多年来发展迅速,引入了一个叫做 NodeJS 的运行时,因此它已经成为最流行和使用最广泛的语言之一。无论您是在编写 Web 应用程序还是移动应用程序,您都可以在 Javascript 生态系统中找到合适的工具。本文是关于如何借助 NodeJS 的活跃生态系统有效地抓取 Web 内容,以满足大多数相关需求。
  本文首发于网站,经网站授权由InfoQ中文站翻译分享。
  前提
  本文章主要针对有一定Javascript开发经验的开发者。但是,如果您熟悉 Web 内容抓取,那么您可以从本文中学到很多东西,而无需任何 Javascript 经验。
  成就
  阅读此 文章 可以帮助读者:
  了解 NodeJS:简介
  Javascript 是一种简单而现代的语言,最初是为了向 网站 浏览器访问添加动态行为而创建的。网站加载后,Javascript 通过浏览器的 JS 引擎运行并翻译成一堆计算机可以理解的代码。为了让 Javascript 与您的浏览器交互,后者提供了一个运行时环境(文档、窗口等)。
  换句话说,Javascript 编程语言不能直接与计算机或它们的资源交互,或操纵它们。例如,在 Web 服务器中,服务器必须能够与文件系统交互才能读取文件或将记录存储在数据库中。
  NodeJS 的想法是让 Javascript 不仅可以在客户端运行,还可以在服务器端运行。为此,资深开发人员 Ryan Dahl 将 Google Chrome 的 v8 JS 引擎嵌入到名为 Node.js 的 C++ 程序中。所以 NodeJS 是一个运行时环境,它允许用 Javascript 编写的应用程序也可以在服务器上运行。
  大多数语言(例如 C 或 C++)使用多个线程来处理并发,相比之下 NodeJS 只使用单个主线程,并借助 Event Loop 以非阻塞的方式使用它来执行任务。我们可以轻松地设置一个简单的 Web 服务器,如下所示:
  1const http = require(&#x27;http&#x27;);
2const PORT = 3000;
3const server = http.createServer((req, res) => {
4  res.statusCode = 200;
5  res.setHeader(&#x27;Content-Type&#x27;, &#x27;text/plain&#x27;);
6  res.end(&#x27;Hello World&#x27;);
7});
8server.listen(port, () => {
9  console.log(`Server running at PORT:${port}/`);
10});
11
  如果您安装了 NodeJS,请运行 node.js(删除符号),然后打开浏览器并导航到 localhost:3000 以查看文本“HelloWorld”。NodeJS 非常适合 I/O 密集型应用程序。HTTP 客户端:查询 Web
  HTTP 客户端是一种向服务器发送请求,然后从服务器接收响应的工具。本文中讨论的大多数工具在后台使用 HTTP 客户端来查询您将尝试抓取的 网站 服务器。
  RequestRequest 是 Javascript 生态系统中使用最广泛的 HTTP 客户端之一,但是现在 Request 库的作者已经正式表示不建议大家继续使用它。不是说不能用,还有很多库还在用,真的很好用。使用 Request 发出 HTTP 请求非常简单:
  1const request = require(&#x27;request&#x27;)
2request(&#x27;https://www.reddit.com/r/progr ... 27%3B, function (
3  error,
4  response,
5  body) {
6  console.error(&#x27;error:&#x27;, error)
7  console.log(&#x27;body:&#x27;, body)
8})
9
  你可以在 Github 上找到 Request 库并运行 npm install request 来安装它。在这里您可以参考弃用通知和详细信息:
  AxiosAxios 是一个基于 Promise 的 HTTP 客户端,在浏览器和 NodeJS 中运行。如果你使用 Typescript,axios 可以覆盖内置类型。通过axios发起HTTP请求非常简单。默认情况下它具有内置的 Promise 支持,不像 Request,它必须使用回调:
  1const axios = require(&#x27;axios&#x27;)
2axios
3    .get(&#x27;https://www.reddit.com/r/programming.json&#x27;)
4    .then((response) => {
5        console.log(response)
6    })
7    .catch((error) => {
8        console.error(error)
9    });
10
  1如果你喜欢 Promises API 的 async/await 语法糖,那么也可以用它们,但由于顶级的 await 仍处于第 3 阶段,我们只能用 Async Function 来代替:
2
  1async function getForum() {
2    try {
3        const response = await axios.get(
4            &#x27;https://www.reddit.com/r/progr ... 27%3B
5        )
6        console.log(response)
7    } catch (error) {
8        console.error(error)
9    }
10}
11
  你只需调用getForum!你可以在 Github 上找到 Axios 库并运行 npm install axios 来安装它。
  超级代理
  与 Axios 类似,Superagent 是另一个强大的 HTTP 客户端,支持 Promises 和 async/await 语法糖。它的 API 和 Axios 一样简单,但 Superagent 依赖较多,不太流行。
  在 Superagent 中,HTTP 请求是使用 Promise、async/await 或回调发出的,如下所示:
  1const superagent = require("superagent")
2const forumURL = "https://www.reddit.com/r/programming.json"
3// callbacks
4superagent
5    .get(forumURL)
6    .end((error, response) => {
7        console.log(response)
8    })
9// promises
10superagent
11    .get(forumURL)
12    .then((response) => {
13        console.log(response)
14    })
15    .catch((error) => {
16        console.error(error)
17    })
18// promises with async/await
19async function getForum() {
20    try {
21        const response = await superagent.get(forumURL)
22        console.log(response)
23    } catch (error) {
24        console.error(error)
25    }
26
  你可以在 Github 上找到 Superagent 库并运行 npm install superagent 来安装它。
  对于下面介绍的网页抓取工具,本文将使用 Axios 作为 HTTP 客户端。正则表达式:艰难之路
  在没有任何依赖关系的情况下开始抓取 Web 内容的最简单方法是在使用 HTTP 客户端查询网页时收到的 HTML 字符串上应用一组正则表达式 - 但这种方法绕路太远了。正则表达式不是那么灵活,许多专业人士和爱好者很难编写正确的正则表达式。
  对于复杂的网页抓取任务,正则表达式很快就会成为瓶颈。无论如何,让我们先尝试一下。假设有一个带有用户名的标签,我们需要用户名在其中,那么使用正则表达式时的方法几乎是这样的:
  1const htmlString = &#x27;Username: John Doe&#x27;
2const result = htmlString.match(/(.+)/)
3console.log(result[1], result[1].split(": ")[1])
4// Username: John Doe, John Doe
5
  在 Javascript 中,match() 通常返回一个收录与正则表达式匹配的所有内容的数组。第二个元素(在索引 1 处)将找到标签的 textContent 或 innerHTML,这正是我们想要的。但是这个结果将收录一些我们不需要的文本(“用户名:”),必须将其删除。如您所见,这种方法对于一个非常简单的用例来说很麻烦。所以我们应该使用 HTML 解析器之类的工具,这些工具将在后面讨论。Cheerio:在其核心遍历 DOM JQuery Cheerio 是一个高效且轻量级的库,允许您在服务器端使用 JQuery 丰富而强大的 API。如果您以前使用过 JQuery,那么使用 Cheerio 很容易上手。它消除了 DOM 的所有不一致和与浏览器相关的特性,并公开了一个用于解析和操作 DOM 的高效 API。
  1const cheerio = require(&#x27;cheerio&#x27;)
2const $ = cheerio.load(&#x27;
3
  你好世界
  ')
  $('h2.title').text('你好!')
  $('h2').addClass('欢迎')
  $.html()
  //
  你好呀!
  如您所见,Cheerio 的工作方式与 JQuery 非常相似。但是,它与 Web 浏览器的工作方式不同,这意味着它不能:
  因此,如果您尝试抓取的 网站 或 Web 应用程序有很多 Javascript 内容(例如“单页应用程序”),那么 Cheerio 不是您的最佳选择,您可能不得不依赖以下讨论一些其他选项。
  为了展示 Cheerio 的强大功能,我们将尝试爬取 Reddit 中的 r/programming 论坛以获取帖子标题列表。
  首先,运行以下命令安装 Cheerio 和 axios:npm install Cheerio axios。然后创建一个名为 crawler.js 的新文件并复制/粘贴以下代码:
  1const axios = require(&#x27;axios&#x27;);
2const cheerio = require(&#x27;cheerio&#x27;);
3const getPostTitles = async () => {
4    try {
5        const { data } = await axios.get(
6            &#x27;https://old.reddit.com/r/programming/&#x27;
7        );
8        const $ = cheerio.load(data);
9        const postTitles = [];
10        $(&#x27;div > p.title > a&#x27;).each((_idx, el) => {
11            const postTitle = $(el).text()
12            postTitles.push(postTitle)
13        });
14        return postTitles;
15    } catch (error) {
16        throw error;
17    }
18};
19getPostTitles()
20.then((postTitles) => console.log(postTitles));
21
  getPostTitles() 是一个异步函数,用于抓取旧 reddit 的 r/programming 论坛。首先,使用来自 axios HTTP 客户端库的简单 HTTP GET 请求获取 网站 的 HTML,然后使用cheerio.load() 函数将 html 数据提供给 Cheerio。
  接下来,使用浏览器的开发工具,您可以获得通常可以针对所有明信片的选择器。如果您使用过 JQuery,那么 $('div &gt; p.title &gt; a') 非常熟悉。这将获取所有帖子,因为您只想获取每个帖子的标题,您必须遍历每个帖子(使用 each() 函数进行迭代)。
  要从每个标题中提取文本,必须在 Cheerio 的帮助下获取 DOM 元素(当前元素的 el)。然后在每个元素上调用 text() 以获取文本。
  现在,您可以弹出一个终端并运行 node crawler.js,您会看到一长串大约 25 或 26 个帖子标题。虽然这是一个非常简单的用例,但它显示了 Cheerio 提供的 API 是多么容易使用。
  如果您的用例需要执行 Javascript 和加载外部资源,这里有几个选项可供考虑。JSDOM:节点的 DOM
  JSDOM 是 NodeJS 中使用的文档对象模型 (DOM) 的纯 Javascript 实现。如前所述,DOM 不适用于 Node,而 JSDOM 是最接近的替代品。它或多或少地模拟了浏览器的机制。
  一旦创建了 DOM,我们就可以通过编程方式与要抓取的 Web 应用程序或 网站 进行交互,还可以完成单击按钮之类的操作。如果您熟悉 DOM 的工作原理,那么 JSDOM 也非常易于使用。
  1const { JSDOM } = require(&#x27;jsdom&#x27;)
2const { document } = new JSDOM(
3    &#x27;
4
  你好世界
  '
  )。窗户
  constheading = document.querySelector('.title')
  heading.textContent = '你好!'
  heading.classList.add('欢迎')
  标题.innerHTML
  //
  你好呀!
  如您所见,JSDOM 创建了一个 DOM,然后您可以使用与浏览器 DOM 相同的方法和属性对其进行操作。为了演示如何使用 JSDOM 与 网站 交互,我们将在 Redditr/programming 论坛上发表第一篇文章,点赞它,然后我们将验证该帖子是否已被点赞。首先运行以下命令安装jsdom和axios:
  1npm install jsdom axios
2
  1然后创建一个名为 rawler.js 的文件,并复制 / 粘贴以下代码:
2
  1const { JSDOM } = require("jsdom")
2const axios = require(&#x27;axios&#x27;)
3const upvoteFirstPost = async () => {
4  try {
5    const { data } = await axios.get("https://old.reddit.com/r/programming/");
6    const dom = new JSDOM(data, {
7      runScripts: "dangerously",
8      resources: "usable"
9    });
10    const { document } = dom.window;
11    const firstPost = document.querySelector("div > div.midcol > div.arrow");
12    firstPost.click();
13    const isUpvoted = firstPost.classList.contains("upmod");
14    const msg = isUpvoted
15      ? "Post has been upvoted successfully!"
16      : "The post has not been upvoted!";
17    return msg;
18  } catch (error) {
19    throw error;
20  }
21};
22upvoteFirstPost().then(msg => console.log(msg));
23
  upvoteFirstPost() 是一个异步函数,它将获得 r/programming 中的第一个帖子并对其进行投票。为此,axios 发送一个 HTTP GET 请求以获取指定 URL 的 HTML。然后将先前获取的 HTML 馈送到 JSDOM 以创建新的 DOM。JSDOM 构造函数将 HTML 作为第一个参数,将选项作为第二个参数。添加的 2 个选项执行以下功能: 查看全部

  nodejs抓取动态网页(
如何在NodeJS的活跃生态系统帮助下高效地抓取Web内容)
  
  作者 | Shenesh Perera 翻译 | 王强策划| 李俊辰 Javascript 多年来发展迅速,引入了一个叫做 NodeJS 的运行时,因此它已经成为最流行和使用最广泛的语言之一。无论您是在编写 Web 应用程序还是移动应用程序,您都可以在 Javascript 生态系统中找到合适的工具。本文是关于如何借助 NodeJS 的活跃生态系统有效地抓取 Web 内容,以满足大多数相关需求。
  本文首发于网站,经网站授权由InfoQ中文站翻译分享。
  前提
  本文章主要针对有一定Javascript开发经验的开发者。但是,如果您熟悉 Web 内容抓取,那么您可以从本文中学到很多东西,而无需任何 Javascript 经验。
  成就
  阅读此 文章 可以帮助读者:
  了解 NodeJS:简介
  Javascript 是一种简单而现代的语言,最初是为了向 网站 浏览器访问添加动态行为而创建的。网站加载后,Javascript 通过浏览器的 JS 引擎运行并翻译成一堆计算机可以理解的代码。为了让 Javascript 与您的浏览器交互,后者提供了一个运行时环境(文档、窗口等)。
  换句话说,Javascript 编程语言不能直接与计算机或它们的资源交互,或操纵它们。例如,在 Web 服务器中,服务器必须能够与文件系统交互才能读取文件或将记录存储在数据库中。
  NodeJS 的想法是让 Javascript 不仅可以在客户端运行,还可以在服务器端运行。为此,资深开发人员 Ryan Dahl 将 Google Chrome 的 v8 JS 引擎嵌入到名为 Node.js 的 C++ 程序中。所以 NodeJS 是一个运行时环境,它允许用 Javascript 编写的应用程序也可以在服务器上运行。
  大多数语言(例如 C 或 C++)使用多个线程来处理并发,相比之下 NodeJS 只使用单个主线程,并借助 Event Loop 以非阻塞的方式使用它来执行任务。我们可以轻松地设置一个简单的 Web 服务器,如下所示:
  1const http = require(&#x27;http&#x27;);
2const PORT = 3000;
3const server = http.createServer((req, res) => {
4  res.statusCode = 200;
5  res.setHeader(&#x27;Content-Type&#x27;, &#x27;text/plain&#x27;);
6  res.end(&#x27;Hello World&#x27;);
7});
8server.listen(port, () => {
9  console.log(`Server running at PORT:${port}/`);
10});
11
  如果您安装了 NodeJS,请运行 node.js(删除符号),然后打开浏览器并导航到 localhost:3000 以查看文本“HelloWorld”。NodeJS 非常适合 I/O 密集型应用程序。HTTP 客户端:查询 Web
  HTTP 客户端是一种向服务器发送请求,然后从服务器接收响应的工具。本文中讨论的大多数工具在后台使用 HTTP 客户端来查询您将尝试抓取的 网站 服务器。
  RequestRequest 是 Javascript 生态系统中使用最广泛的 HTTP 客户端之一,但是现在 Request 库的作者已经正式表示不建议大家继续使用它。不是说不能用,还有很多库还在用,真的很好用。使用 Request 发出 HTTP 请求非常简单:
  1const request = require(&#x27;request&#x27;)
2request(&#x27;https://www.reddit.com/r/progr ... 27%3B, function (
3  error,
4  response,
5  body) {
6  console.error(&#x27;error:&#x27;, error)
7  console.log(&#x27;body:&#x27;, body)
8})
9
  你可以在 Github 上找到 Request 库并运行 npm install request 来安装它。在这里您可以参考弃用通知和详细信息:
  AxiosAxios 是一个基于 Promise 的 HTTP 客户端,在浏览器和 NodeJS 中运行。如果你使用 Typescript,axios 可以覆盖内置类型。通过axios发起HTTP请求非常简单。默认情况下它具有内置的 Promise 支持,不像 Request,它必须使用回调:
  1const axios = require(&#x27;axios&#x27;)
2axios
3    .get(&#x27;https://www.reddit.com/r/programming.json&#x27;)
4    .then((response) => {
5        console.log(response)
6    })
7    .catch((error) => {
8        console.error(error)
9    });
10
  1如果你喜欢 Promises API 的 async/await 语法糖,那么也可以用它们,但由于顶级的 await 仍处于第 3 阶段,我们只能用 Async Function 来代替:
2
  1async function getForum() {
2    try {
3        const response = await axios.get(
4            &#x27;https://www.reddit.com/r/progr ... 27%3B
5        )
6        console.log(response)
7    } catch (error) {
8        console.error(error)
9    }
10}
11
  你只需调用getForum!你可以在 Github 上找到 Axios 库并运行 npm install axios 来安装它。
  超级代理
  与 Axios 类似,Superagent 是另一个强大的 HTTP 客户端,支持 Promises 和 async/await 语法糖。它的 API 和 Axios 一样简单,但 Superagent 依赖较多,不太流行。
  在 Superagent 中,HTTP 请求是使用 Promise、async/await 或回调发出的,如下所示:
  1const superagent = require("superagent")
2const forumURL = "https://www.reddit.com/r/programming.json"
3// callbacks
4superagent
5    .get(forumURL)
6    .end((error, response) => {
7        console.log(response)
8    })
9// promises
10superagent
11    .get(forumURL)
12    .then((response) => {
13        console.log(response)
14    })
15    .catch((error) => {
16        console.error(error)
17    })
18// promises with async/await
19async function getForum() {
20    try {
21        const response = await superagent.get(forumURL)
22        console.log(response)
23    } catch (error) {
24        console.error(error)
25    }
26
  你可以在 Github 上找到 Superagent 库并运行 npm install superagent 来安装它。
  对于下面介绍的网页抓取工具,本文将使用 Axios 作为 HTTP 客户端。正则表达式:艰难之路
  在没有任何依赖关系的情况下开始抓取 Web 内容的最简单方法是在使用 HTTP 客户端查询网页时收到的 HTML 字符串上应用一组正则表达式 - 但这种方法绕路太远了。正则表达式不是那么灵活,许多专业人士和爱好者很难编写正确的正则表达式。
  对于复杂的网页抓取任务,正则表达式很快就会成为瓶颈。无论如何,让我们先尝试一下。假设有一个带有用户名的标签,我们需要用户名在其中,那么使用正则表达式时的方法几乎是这样的:
  1const htmlString = &#x27;Username: John Doe&#x27;
2const result = htmlString.match(/(.+)/)
3console.log(result[1], result[1].split(": ")[1])
4// Username: John Doe, John Doe
5
  在 Javascript 中,match() 通常返回一个收录与正则表达式匹配的所有内容的数组。第二个元素(在索引 1 处)将找到标签的 textContent 或 innerHTML,这正是我们想要的。但是这个结果将收录一些我们不需要的文本(“用户名:”),必须将其删除。如您所见,这种方法对于一个非常简单的用例来说很麻烦。所以我们应该使用 HTML 解析器之类的工具,这些工具将在后面讨论。Cheerio:在其核心遍历 DOM JQuery Cheerio 是一个高效且轻量级的库,允许您在服务器端使用 JQuery 丰富而强大的 API。如果您以前使用过 JQuery,那么使用 Cheerio 很容易上手。它消除了 DOM 的所有不一致和与浏览器相关的特性,并公开了一个用于解析和操作 DOM 的高效 API。
  1const cheerio = require(&#x27;cheerio&#x27;)
2const $ = cheerio.load(&#x27;
3
  你好世界
  ')
  $('h2.title').text('你好!')
  $('h2').addClass('欢迎')
  $.html()
  //
  你好呀!
  如您所见,Cheerio 的工作方式与 JQuery 非常相似。但是,它与 Web 浏览器的工作方式不同,这意味着它不能:
  因此,如果您尝试抓取的 网站 或 Web 应用程序有很多 Javascript 内容(例如“单页应用程序”),那么 Cheerio 不是您的最佳选择,您可能不得不依赖以下讨论一些其他选项。
  为了展示 Cheerio 的强大功能,我们将尝试爬取 Reddit 中的 r/programming 论坛以获取帖子标题列表。
  首先,运行以下命令安装 Cheerio 和 axios:npm install Cheerio axios。然后创建一个名为 crawler.js 的新文件并复制/粘贴以下代码:
  1const axios = require(&#x27;axios&#x27;);
2const cheerio = require(&#x27;cheerio&#x27;);
3const getPostTitles = async () => {
4    try {
5        const { data } = await axios.get(
6            &#x27;https://old.reddit.com/r/programming/&#x27;
7        );
8        const $ = cheerio.load(data);
9        const postTitles = [];
10        $(&#x27;div > p.title > a&#x27;).each((_idx, el) => {
11            const postTitle = $(el).text()
12            postTitles.push(postTitle)
13        });
14        return postTitles;
15    } catch (error) {
16        throw error;
17    }
18};
19getPostTitles()
20.then((postTitles) => console.log(postTitles));
21
  getPostTitles() 是一个异步函数,用于抓取旧 reddit 的 r/programming 论坛。首先,使用来自 axios HTTP 客户端库的简单 HTTP GET 请求获取 网站 的 HTML,然后使用cheerio.load() 函数将 html 数据提供给 Cheerio。
  接下来,使用浏览器的开发工具,您可以获得通常可以针对所有明信片的选择器。如果您使用过 JQuery,那么 $('div &gt; p.title &gt; a') 非常熟悉。这将获取所有帖子,因为您只想获取每个帖子的标题,您必须遍历每个帖子(使用 each() 函数进行迭代)。
  要从每个标题中提取文本,必须在 Cheerio 的帮助下获取 DOM 元素(当前元素的 el)。然后在每个元素上调用 text() 以获取文本。
  现在,您可以弹出一个终端并运行 node crawler.js,您会看到一长串大约 25 或 26 个帖子标题。虽然这是一个非常简单的用例,但它显示了 Cheerio 提供的 API 是多么容易使用。
  如果您的用例需要执行 Javascript 和加载外部资源,这里有几个选项可供考虑。JSDOM:节点的 DOM
  JSDOM 是 NodeJS 中使用的文档对象模型 (DOM) 的纯 Javascript 实现。如前所述,DOM 不适用于 Node,而 JSDOM 是最接近的替代品。它或多或少地模拟了浏览器的机制。
  一旦创建了 DOM,我们就可以通过编程方式与要抓取的 Web 应用程序或 网站 进行交互,还可以完成单击按钮之类的操作。如果您熟悉 DOM 的工作原理,那么 JSDOM 也非常易于使用。
  1const { JSDOM } = require(&#x27;jsdom&#x27;)
2const { document } = new JSDOM(
3    &#x27;
4
  你好世界
  '
  )。窗户
  constheading = document.querySelector('.title')
  heading.textContent = '你好!'
  heading.classList.add('欢迎')
  标题.innerHTML
  //
  你好呀!
  如您所见,JSDOM 创建了一个 DOM,然后您可以使用与浏览器 DOM 相同的方法和属性对其进行操作。为了演示如何使用 JSDOM 与 网站 交互,我们将在 Redditr/programming 论坛上发表第一篇文章,点赞它,然后我们将验证该帖子是否已被点赞。首先运行以下命令安装jsdom和axios:
  1npm install jsdom axios
2
  1然后创建一个名为 rawler.js 的文件,并复制 / 粘贴以下代码:
2
  1const { JSDOM } = require("jsdom")
2const axios = require(&#x27;axios&#x27;)
3const upvoteFirstPost = async () => {
4  try {
5    const { data } = await axios.get("https://old.reddit.com/r/programming/";);
6    const dom = new JSDOM(data, {
7      runScripts: "dangerously",
8      resources: "usable"
9    });
10    const { document } = dom.window;
11    const firstPost = document.querySelector("div > div.midcol > div.arrow");
12    firstPost.click();
13    const isUpvoted = firstPost.classList.contains("upmod");
14    const msg = isUpvoted
15      ? "Post has been upvoted successfully!"
16      : "The post has not been upvoted!";
17    return msg;
18  } catch (error) {
19    throw error;
20  }
21};
22upvoteFirstPost().then(msg => console.log(msg));
23
  upvoteFirstPost() 是一个异步函数,它将获得 r/programming 中的第一个帖子并对其进行投票。为此,axios 发送一个 HTTP GET 请求以获取指定 URL 的 HTML。然后将先前获取的 HTML 馈送到 JSDOM 以创建新的 DOM。JSDOM 构造函数将 HTML 作为第一个参数,将选项作为第二个参数。添加的 2 个选项执行以下功能:

nodejs抓取动态网页(使用ScrapySharp快速从网页中采集数据中的采集方案介绍)

网站优化优采云 发表了文章 • 0 个评论 • 107 次浏览 • 2022-02-05 01:21 • 来自相关话题

  nodejs抓取动态网页(使用ScrapySharp快速从网页中采集数据中的采集方案介绍)
  在上一篇文章文章中,我介绍了使用ScrapySharp快速采集从网页中获取数据。该方法是通过直接发送的Http请求获取原创页面信息。对于静态网页非常有效,但是网站中也有很多页面内容并不是全部存储在原创页面中的,而且很多内容是通过javascript动态生成的,而这些数据是前面无法抓取的方法。本文简单介绍了动态网页的采集方案。
  对于这样的网页数据采集,往往会使用浏览器引擎来加载整个页面,加载后输出完整的页面,然后使用ScrapySharp等工具进行解析。常用的方法有以下几种:
  使用 WebBrowser 控件
  这种方法被认为已被大多数 .Net 开发人员使用。由于WebBrowser直接使用与操作系统集成的IE,无需下载第三方控件,比较简单快捷。但是,它只是一个演示控件,并没有提供很多接口。集成一些扩展比较麻烦。
  使用网络浏览器
  PhantomJS 是一个带有 Webkit 核心的无界面浏览器。它的一个特点就是可以很方便的集成javascript脚本,所以开发扩展更加方便,也可以用在服务器端无法使用UI控件的地方。目前网上的大部分解决方案都是这种。把我看过的几篇文章抄录到这里文章,就不做详细介绍了:
  这个程序本身是比较方便和强大的,但是在试用过程中还是存在一些问题,比如有些网页不是很规范,不能正确解析,或者有乱码。
  使用 CEF 控件
  CEF 是 Chromium Embedded Framework,谷歌提供的 Chrome 集成解决方案。相对而言,它提供了更底层的API,我们可以进行更强大的自定义(当然也需要更多的工作)。加快内容解析。
  直接解析Javascript模拟渲染
  上述方案虽然可以简单正确地获取完整的解析页面,但存在一个性能问题:速度很慢。虽然浏览器是由顶级专家开发的,但页面本身的渲染是一个非常复杂的过程。使用上述工具完全渲染一个页面仍然需要几秒钟,而且资源开销不小,无法支持大规模数据。采集。
  在大多数情况下,这不是什么大问题,但是如果你更关注性能问题,有一个比较原创的方法来解决它,那就是分析网页的JS的工作原理,并模拟浏览器只执行与内容相关的 JS。手动获取输出的内容。
  这样主要是需要一个javascript引擎,已经提供了大量的js引擎,基本没有问题。它的主要问题在于需要对网页进行自定义和分析,而这些网页的大部分JS都采用了一定的混淆策略,不易分析,而且往往需要大量的时间去调试。 查看全部

  nodejs抓取动态网页(使用ScrapySharp快速从网页中采集数据中的采集方案介绍)
  在上一篇文章文章中,我介绍了使用ScrapySharp快速采集从网页中获取数据。该方法是通过直接发送的Http请求获取原创页面信息。对于静态网页非常有效,但是网站中也有很多页面内容并不是全部存储在原创页面中的,而且很多内容是通过javascript动态生成的,而这些数据是前面无法抓取的方法。本文简单介绍了动态网页的采集方案。
  对于这样的网页数据采集,往往会使用浏览器引擎来加载整个页面,加载后输出完整的页面,然后使用ScrapySharp等工具进行解析。常用的方法有以下几种:
  使用 WebBrowser 控件
  这种方法被认为已被大多数 .Net 开发人员使用。由于WebBrowser直接使用与操作系统集成的IE,无需下载第三方控件,比较简单快捷。但是,它只是一个演示控件,并没有提供很多接口。集成一些扩展比较麻烦。
  使用网络浏览器
  PhantomJS 是一个带有 Webkit 核心的无界面浏览器。它的一个特点就是可以很方便的集成javascript脚本,所以开发扩展更加方便,也可以用在服务器端无法使用UI控件的地方。目前网上的大部分解决方案都是这种。把我看过的几篇文章抄录到这里文章,就不做详细介绍了:
  这个程序本身是比较方便和强大的,但是在试用过程中还是存在一些问题,比如有些网页不是很规范,不能正确解析,或者有乱码。
  使用 CEF 控件
  CEF 是 Chromium Embedded Framework,谷歌提供的 Chrome 集成解决方案。相对而言,它提供了更底层的API,我们可以进行更强大的自定义(当然也需要更多的工作)。加快内容解析。
  直接解析Javascript模拟渲染
  上述方案虽然可以简单正确地获取完整的解析页面,但存在一个性能问题:速度很慢。虽然浏览器是由顶级专家开发的,但页面本身的渲染是一个非常复杂的过程。使用上述工具完全渲染一个页面仍然需要几秒钟,而且资源开销不小,无法支持大规模数据。采集。
  在大多数情况下,这不是什么大问题,但是如果你更关注性能问题,有一个比较原创的方法来解决它,那就是分析网页的JS的工作原理,并模拟浏览器只执行与内容相关的 JS。手动获取输出的内容。
  这样主要是需要一个javascript引擎,已经提供了大量的js引擎,基本没有问题。它的主要问题在于需要对网页进行自定义和分析,而这些网页的大部分JS都采用了一定的混淆策略,不易分析,而且往往需要大量的时间去调试。

nodejs抓取动态网页(1.为什么需要一个前端监控系统?技术选型监控的意义)

网站优化优采云 发表了文章 • 0 个评论 • 62 次浏览 • 2022-02-05 01:20 • 来自相关话题

  nodejs抓取动态网页(1.为什么需要一个前端监控系统?技术选型监控的意义)
  1. 为什么需要前端监控系统
  通常大型web项目中会有很多监控系统,比如后端服务API监控、接口存活、调用、延迟等监控,一般用于监控后端数据级别的信息界面。而对于大型的网站系统,从后端服务到前端展示会有很多层:内网VIP、CDN等。
  但是这些监控并不能准确反映用户看到的前端页面的状态,比如页面上第三方系统数据调用失败、模块加载异常、数据不正确、天窗茫然地打开。
  这时候就需要从前端 DOM 展示的角度去分析采集用户真实看到的东西,从而检测页面是否存在异常问题。
  2. 监控系统需要解决的问题
  当页面出现以下问题时,您需要及时通过邮件或短信方式向相关人员报告,以解决问题。
  3. 技术选型
  监控的含义和测试的含义本质上是一样的,都是对已经上线的功能进行回归测试,但不同的是监控需要长期持续的、反复出现的回归测试,而测试之后只需要做一次发射,市场投入。回归测试。
  由于监控和测试的性质是相同的,我们可以用测试的方法来做监控系统。在自动化测试技术遍地开花的时代,有很多有用的自动化工具,我们只需要整合这些自动化工具供我们使用。
  节点JS
  NodeJS 是一个 JavaScript 运行环境,NodeJS 支持非阻塞 I/O 和异步、事件驱动,这对于我们构建基于 DOM 元素的监控更为重要。
  幻影JS
  PhantomJS 是一个基于 webkit 的 JavaScript API。它使用 QtWebKit 作为其核心浏览器,并使用 webkit 来编译、解释和执行 JavaScript 代码。它可以做任何你可以在基于 webkit 的浏览器中做的事情。
  它不仅是一个不可见的浏览器,提供诸如 CSS 选择器、对 Web 标准的支持、DOM 操作、JSON、HTML5、Canvas、SVG 等,它还提供处理文件 I/O 的操作,让您可以读写文件到操作系统等。PhantomJS的用途很广泛,比如网络监控、网页截图、免浏览器网页测试、页面访问自动化等等。
  为什么不使用硒
  做自动化测试的同学一定了解 Selenium。可以使用Selenium在浏览器中执行测试用例,Selenium对各种平台和常见浏览器的支持都很好,但是Selenium上手难度稍大,使用Selenium需要在服务器端安装浏览器。
  考虑到监控的主要任务是监控而不是测试。系统不需要过多考虑兼容性,监控功能也比较简单,主要针对页面的功能回归测试,所以选择了PhantomJS。
  4. 架构设计架构概述
  架构如下图所示。
  (点击放大图片)
  
  架构简介
  对于 DOM 监控服务,在应用层面做了纵向划分:
  应用层面的垂直划分,实现应用的分布式部署,提升处理能力。后期的性能优化、系统改造和扩展也可以提高简单性。
  5. 解决方案(1) 前端规则入口
  这是一个独立的Web系统。系统主要用于采集用户输入的页面信息,页面对应的规则,显示错误信息。通过调用后端页面爬取服务完成页面检测任务,通过系统可以创建三种检测页面:定期监控、高级监控、可用性监控。
  日常监测
  输入一个页面地址和几个检测规则。注意这里的检测规则,我们将一些常用的检测点抽象成一个类似测试用例的语句。每个规则用于匹配页面上的一个 DOM 元素,并使用 DOM 元素的属性来匹配预期。如果匹配失败,系统会产生错误信息,并交由报警系统处理。
  匹配类型一般有这些类型:长度、文本、HTML、属性
  处理器类似于编程语言中的运算符:大于、大于或等于、小于、小于或等于、等于、正则。
  这样做的好处是,输入规则的人只需要知道一点 DOM 选择器的知识就可以上手。在内部,我们通常将规则交给测试工程师完成输入。
  高级监控
  主要用于提供高级页面测试功能,测试用例一般由经验丰富的工程师编写。这个测试用例写起来会有一定的学习成本,但是可以模拟网页操作,比如点击、鼠标移动等事件。以实现对页面信息的准确抓取。
  可用性监控
  可用性监控侧重于即时监控页面可访问性、内容正确性等更严重的问题。通常我们只需要在程序中启动一个Worker,不断获取页面的HTML来检测和匹配结果,所以我们选择NodeJS做异步页面爬取队列来高效快速的完成这种网络密集型任务。
  (2)主动报错页面脚本执行错误监控
  页面引入监控脚本,采集页面产生的错误事件信息,并自动上报给后端服务。在系统中可以汇总所有的错误信息,以及对应的客户端浏览器版本、操作系统、IP地址等。
  活动页面报告
  该功能需要对应的前端工程师调用代码中的报错API主动提交错误信息。主要使用场景包括,页面异步服务延迟无响应、模块降级、主动通知等。监控脚本提供了几个简单的API来完成这个任务。
  // error 方法调用后立即上报错误信息并发出邮件、短信通知
errorTracker.error('错误描述')
// info 方法调用后立即上报信息,并在单位时间内仅仅产生一条邮件、短信通知
errorTracker.info('信息描述')
// log 方法调用后由报错检测是否达到设置阀值,最终确认是否报错
errorTracker.log('日志信息')
  (3)后端页面抓取服务
  由于京东的很多页面内容是异步加载的,而首页、单品等系统有很多第三方异步接口调用,后端程序抓取的页面数据是同步的,动态JavaScript渲染的数据不能获得。所以你必须使用像 PhantomJS 这样可以模拟浏览器的工具。
  对于日常监控,我们使用 PhantomJS 模拟浏览器打开页面进行爬取,然后将监控规则解析成 JavaScript 代码片段执行并采集结果。
  对于高级监控,我们使用 PhantomJS 打开页面,将 jasmine、Mocha 等前端 JavaScript 测试框架注入到页面中,然后在页面上执行相应的输入测试用例并返回结果。
  常规队列生成器
  规则队列生成器会将规则系统采集的规则转换成消息队列,然后交给长期持久化处理器进行顺序处理。
  为什么要使用类似消息队列的处理方式?
  这与 PhantomJS 的性能是分不开的。通过多次实践发现,PhantomJS 不能很好的进行并发处理。当并发量过多时,CPU会过载,导致机器宕机。
  对于本地环境的虚拟机并发测试,数据并不理想,限制基本在ab -n 100 -c 50左右。所以为了防止并发带来的问题,我们选择使用消息队列来进行避免高并发导致服务不可用。
  类似消息队列的实现
  这里我们通过调用内部分布式缓存系统来生成一个消息队列。队列的产生其实可以参考数据接口——队列。最基本的模型是在缓存中创建一个KEY,然后根据队列数据结构的schema插入和提取数据。
  当然消息队列的中间介质可以根据你的实际情况选择,当然你也可以使用原生内存实现。这可能会导致应用程序和类似消息队列的内存争用。
  长效处理器
  长期持久处理器是由消费规则队列生成器生成的消息队列。
  长效处理实施
  在长时持久化处理器的具体实现中,我们充分利用了JavaScript的setInterval方法,不断的获取疲倦消息队列的内容并发送给规则转换器,再转发给负载均衡调度器。之后统一处理返回的结果,如邮件或短信报警。
  API
  PhantomJS服务可以作为公共API提供给客户端处理测试需求,通过HTTP调用API。在 API 的处理中,需要提供 HTTP 数据到规则和 PhantomJS 的转换。因此,HTTP 数据到规则的转换器得到了发展。
  PhantomJS 服务
  PhantomJS 服务是指将 PhantomJS 与 HTTP 服务和子进程相结合的服务处理 查看全部

  nodejs抓取动态网页(1.为什么需要一个前端监控系统?技术选型监控的意义)
  1. 为什么需要前端监控系统
  通常大型web项目中会有很多监控系统,比如后端服务API监控、接口存活、调用、延迟等监控,一般用于监控后端数据级别的信息界面。而对于大型的网站系统,从后端服务到前端展示会有很多层:内网VIP、CDN等。
  但是这些监控并不能准确反映用户看到的前端页面的状态,比如页面上第三方系统数据调用失败、模块加载异常、数据不正确、天窗茫然地打开。
  这时候就需要从前端 DOM 展示的角度去分析采集用户真实看到的东西,从而检测页面是否存在异常问题。
  2. 监控系统需要解决的问题
  当页面出现以下问题时,您需要及时通过邮件或短信方式向相关人员报告,以解决问题。
  3. 技术选型
  监控的含义和测试的含义本质上是一样的,都是对已经上线的功能进行回归测试,但不同的是监控需要长期持续的、反复出现的回归测试,而测试之后只需要做一次发射,市场投入。回归测试。
  由于监控和测试的性质是相同的,我们可以用测试的方法来做监控系统。在自动化测试技术遍地开花的时代,有很多有用的自动化工具,我们只需要整合这些自动化工具供我们使用。
  节点JS
  NodeJS 是一个 JavaScript 运行环境,NodeJS 支持非阻塞 I/O 和异步、事件驱动,这对于我们构建基于 DOM 元素的监控更为重要。
  幻影JS
  PhantomJS 是一个基于 webkit 的 JavaScript API。它使用 QtWebKit 作为其核心浏览器,并使用 webkit 来编译、解释和执行 JavaScript 代码。它可以做任何你可以在基于 webkit 的浏览器中做的事情。
  它不仅是一个不可见的浏览器,提供诸如 CSS 选择器、对 Web 标准的支持、DOM 操作、JSON、HTML5、Canvas、SVG 等,它还提供处理文件 I/O 的操作,让您可以读写文件到操作系统等。PhantomJS的用途很广泛,比如网络监控、网页截图、免浏览器网页测试、页面访问自动化等等。
  为什么不使用硒
  做自动化测试的同学一定了解 Selenium。可以使用Selenium在浏览器中执行测试用例,Selenium对各种平台和常见浏览器的支持都很好,但是Selenium上手难度稍大,使用Selenium需要在服务器端安装浏览器。
  考虑到监控的主要任务是监控而不是测试。系统不需要过多考虑兼容性,监控功能也比较简单,主要针对页面的功能回归测试,所以选择了PhantomJS。
  4. 架构设计架构概述
  架构如下图所示。
  (点击放大图片)
  
  架构简介
  对于 DOM 监控服务,在应用层面做了纵向划分:
  应用层面的垂直划分,实现应用的分布式部署,提升处理能力。后期的性能优化、系统改造和扩展也可以提高简单性。
  5. 解决方案(1) 前端规则入口
  这是一个独立的Web系统。系统主要用于采集用户输入的页面信息,页面对应的规则,显示错误信息。通过调用后端页面爬取服务完成页面检测任务,通过系统可以创建三种检测页面:定期监控、高级监控、可用性监控。
  日常监测
  输入一个页面地址和几个检测规则。注意这里的检测规则,我们将一些常用的检测点抽象成一个类似测试用例的语句。每个规则用于匹配页面上的一个 DOM 元素,并使用 DOM 元素的属性来匹配预期。如果匹配失败,系统会产生错误信息,并交由报警系统处理。
  匹配类型一般有这些类型:长度、文本、HTML、属性
  处理器类似于编程语言中的运算符:大于、大于或等于、小于、小于或等于、等于、正则。
  这样做的好处是,输入规则的人只需要知道一点 DOM 选择器的知识就可以上手。在内部,我们通常将规则交给测试工程师完成输入。
  高级监控
  主要用于提供高级页面测试功能,测试用例一般由经验丰富的工程师编写。这个测试用例写起来会有一定的学习成本,但是可以模拟网页操作,比如点击、鼠标移动等事件。以实现对页面信息的准确抓取。
  可用性监控
  可用性监控侧重于即时监控页面可访问性、内容正确性等更严重的问题。通常我们只需要在程序中启动一个Worker,不断获取页面的HTML来检测和匹配结果,所以我们选择NodeJS做异步页面爬取队列来高效快速的完成这种网络密集型任务。
  (2)主动报错页面脚本执行错误监控
  页面引入监控脚本,采集页面产生的错误事件信息,并自动上报给后端服务。在系统中可以汇总所有的错误信息,以及对应的客户端浏览器版本、操作系统、IP地址等。
  活动页面报告
  该功能需要对应的前端工程师调用代码中的报错API主动提交错误信息。主要使用场景包括,页面异步服务延迟无响应、模块降级、主动通知等。监控脚本提供了几个简单的API来完成这个任务。
  // error 方法调用后立即上报错误信息并发出邮件、短信通知
errorTracker.error('错误描述')
// info 方法调用后立即上报信息,并在单位时间内仅仅产生一条邮件、短信通知
errorTracker.info('信息描述')
// log 方法调用后由报错检测是否达到设置阀值,最终确认是否报错
errorTracker.log('日志信息')
  (3)后端页面抓取服务
  由于京东的很多页面内容是异步加载的,而首页、单品等系统有很多第三方异步接口调用,后端程序抓取的页面数据是同步的,动态JavaScript渲染的数据不能获得。所以你必须使用像 PhantomJS 这样可以模拟浏览器的工具。
  对于日常监控,我们使用 PhantomJS 模拟浏览器打开页面进行爬取,然后将监控规则解析成 JavaScript 代码片段执行并采集结果。
  对于高级监控,我们使用 PhantomJS 打开页面,将 jasmine、Mocha 等前端 JavaScript 测试框架注入到页面中,然后在页面上执行相应的输入测试用例并返回结果。
  常规队列生成器
  规则队列生成器会将规则系统采集的规则转换成消息队列,然后交给长期持久化处理器进行顺序处理。
  为什么要使用类似消息队列的处理方式?
  这与 PhantomJS 的性能是分不开的。通过多次实践发现,PhantomJS 不能很好的进行并发处理。当并发量过多时,CPU会过载,导致机器宕机。
  对于本地环境的虚拟机并发测试,数据并不理想,限制基本在ab -n 100 -c 50左右。所以为了防止并发带来的问题,我们选择使用消息队列来进行避免高并发导致服务不可用。
  类似消息队列的实现
  这里我们通过调用内部分布式缓存系统来生成一个消息队列。队列的产生其实可以参考数据接口——队列。最基本的模型是在缓存中创建一个KEY,然后根据队列数据结构的schema插入和提取数据。
  当然消息队列的中间介质可以根据你的实际情况选择,当然你也可以使用原生内存实现。这可能会导致应用程序和类似消息队列的内存争用。
  长效处理器
  长期持久处理器是由消费规则队列生成器生成的消息队列。
  长效处理实施
  在长时持久化处理器的具体实现中,我们充分利用了JavaScript的setInterval方法,不断的获取疲倦消息队列的内容并发送给规则转换器,再转发给负载均衡调度器。之后统一处理返回的结果,如邮件或短信报警。
  API
  PhantomJS服务可以作为公共API提供给客户端处理测试需求,通过HTTP调用API。在 API 的处理中,需要提供 HTTP 数据到规则和 PhantomJS 的转换。因此,HTTP 数据到规则的转换器得到了发展。
  PhantomJS 服务
  PhantomJS 服务是指将 PhantomJS 与 HTTP 服务和子进程相结合的服务处理

nodejs抓取动态网页(【系列文章目录】「项目源码(GitHub)」本篇目录)

网站优化优采云 发表了文章 • 0 个评论 • 78 次浏览 • 2022-02-03 05:10 • 来自相关话题

  nodejs抓取动态网页(【系列文章目录】「项目源码(GitHub)」本篇目录)
  重要链接:
  "系列文章目录"
  “项目源代码(GitHub)”
  本目录
  前言
  不出所料,我没能按时写完文章。时至今日,我已经没有任何愧疚感了,大概就是长大了。但话说回来,我曾经是一个每月写10篇文章的人。
  这个文章的主要内容是根据用户角色动态加载后台管理页面的菜单,关键点如下:
  在正文之前,让我告诉你这两周背后的故事:
  1、在官方活动——《CSDN发力计划》的加持下,经历了一波快速增长的浪潮,阅读量翻了一番。一连几天都肿不起来,甚至想下定决心,尽快达到破万的目标。然后我在房间里沉思良久,喝了一大杯快乐水,放弃了这个危险的想法。
  2、最近,我发现我们的项目除了入门练习之外,还可以作为一些常见应用的脚手架。如图书查询管理系统、个人博客(首页)、企业门户网站等。上周,我什至基于这个项目为公司内部活动搭建了一个投票系统。虽然觉得自己很崩溃,但是骗过不懂代码的同事就够了。
  为了让这个项目看起来很严肃,我对结构做了一些调整:
  其实前端配置了路由,修改了book组件,后端取消了对book查询端口的拦截。我相信你可以自己做。一位读者指出,我在一个地方漏掉了导入组件的代码。一开始我以为我错过了,但后来我想起我没有故意复制整个组件,因为我认为没有必要。不要只是复制代码并考虑它。不可能不报错,就是直接克隆别人的仓库,会出现各种问题。如果有问题,百度就可以了。
  3、有些读者给我发了十几封邮件,上千行的错误信息贴了好几次。没有这回事,我太南了(给我狂笑.jpg)
  但是,会说话的读者越来越多。我脸皮薄,很多时候还是不忍心拒绝你。在过去的两周里,我花了很多时间回答问题。一些认真的读者发现了项目的错误并指出了如何纠正它们。我很高兴,希望上帝能给我分配更多这样的读者。随着项目越来越复杂,难免会出现各种缺陷。欢迎大家指出各方面的不足,也可以直接在GitHub上提交Issue或者PR,一起来做吧。
  一、后端实现
  实现动态加载菜单功能的第一步是完成根据当前用户查询可访问菜单信息的界面。
  1.餐桌设计
  基于前面提到的RBAC原理,我们应该设计一个角色表,并使用角色来对应菜单。同时,为了建立用户和角色、角色和菜单之间的关系,需要两个中间表。除了前面的用户表,一共需要五个表,每个表的字段如下图所示:
  
  在这里,我将 admin 前缀添加到专用于后台管理的表中。命名是一件非常重要的事情。好名字是保证代码质量的前提。不要那么随随便便地跟着我。很多公司都有自己的命名原则,大家可以学习一下。(推荐《阿里巴巴Java开发手册》)
  另外,和之前book和category的做法不同,这里没有使用外键。一般来说,决定使用外键取决于系统对数据一致性和效率的要求哪个更突出,但我认为数据一致性问题可以通过代码来解决,使用外键既麻烦又尴尬。
  这里我简单介绍一下admin_menu表的字段:
  字段说明
  ID
  唯一标识
  小路
  对应Vue路由中的路径,即地址路径
  名称
  对应Vue路由中的name属性
  name_zh
  中文名称,用于渲染导航栏(菜单)界面
  icon_cls
  元素图标类名,用于渲染菜单名前的小图标
  零件
  组件名称,用于解析路由对应的组件
  parent_id
  父节点id,用于存储导航栏层次结构
  您可以自己设计表中的数据。为了方便测试,记得多注册一个账号来配置不同的角色,为角色配置相应的菜单。如果嫌麻烦,直接执行我的sql文件就行了:
  里面有admin、test和editor三个账号,密码都是123,admin的角色是系统管理员,editor是内容管理员,test是空的。
  2.pojo
  因为我们使用JPA作为ORM,所以在创建POJO时需要注意以下几点:
  PS 其实我们过去创建的POJO应该更具体一些,叫PO(persistant object,持久对象)或者Entity(实体),使用DTO(Data Transfer Object)与客户端进行交互。教程简化了一点,源代码添加了这些类别。
  我们需要创建四个 PO,AdminUserRole、AdminRole、AdminRoleMenu 和 AdminMenu。特别的是AdminMenu。在这里我发布代码:
  package com.gm.wj.pojo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name = "admin_menu")
@JsonIgnoreProperties({"handler", "hibernateLazyInitializer"})
public class AdminMenu {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
int id;
String path;
String name;
String nameZh;
String iconCls;
String component;
int parentId;
@Transient
List children;
// getter and setter...
  与数据库不同的是 Children 属性,用于存储子节点。
  3.菜单查询界面(树结构查询)
  根据用户查询找到对应菜单的步骤为:
  为了实现这个接口,我们需要添加三个数据库访问对象,AdminUserRoleDAO、AdminRoleMenuDAO和AdminMenuDAO,并编写Service对象。一路上我们也可以写一组AdminRoles,但是还不能用。
  在AdminMenuService中,需要实现一个根据当前用户查询所有菜单项的方法:
  public List getMenusByCurrentUser() {
// 从数据库中获取当前用户
String username = SecurityUtils.getSubject().getPrincipal().toString();
User user = userService.findByUsername(username);
// 获得当前用户对应的所有角色的 id 列表
List rids = adminUserRoleService.listAllByUid(user.getId())
.stream().map(AdminUserRole::getRid).collect(Collectors.toList());
// 查询出这些角色对应的所有菜单项
List menuIds = adminRoleMenuService.findAllByRid(rids)
.stream().map(AdminRoleMenu::getMid).collect(Collectors.toList());
List menus = adminMenuDAO.findAllById(menuIds).stream().distinct().collect(Collectors.toList());
// 处理菜单项的结构
handleMenus(menus);
return menus;
}
  该方法的主体对应于上面的前三个步骤。这里我们使用stream来简化列表的处理,包括使用map()提取集合中的某个属性,以及通过distinct()对查询到的菜单项进行去重,避免在多角色的情况下出现多余的菜单等。
  接下来,需要将查询到的菜单数据列表整合成具有层次关系的菜单树,即编写handleMenus()方法。
  这里多说一点,因为导航菜单一般不会很长,所以我们使用这种一次性取出的方式。在上面的过程中,我们会一边遍历列表一边查询数据库。在前台需要尽可能避免这样的多重交互。最好一次性查询全量数据,减轻服务器负担。不过后台一般都是管理员用的,流量也不大,不用担心。
  如果数据量特别大,应该考虑按节点动态加载。即通过监听节点的展开事件,将节点id作为参数发送到后端,在前端动态查询并渲染所有子节点。该方法的实现将在后面详细讨论。
  对查询到的菜单数据进行整合的思路如下:
  整合方法如下:
  public void handleMenus(List menus) {
for (AdminMenu menu : menus) {
List children = getAllByParentId(menu.getId());
menu.setChildren(children);
}

Iterator iterator = menus.iterator();
while (iterator.hasNext()) {
AdminMenu menu = iterator.next();
if (menu.getParentId() != 0) {
iterator.remove();
}
}
}
  首先,让我解释一下,查询树结构的方法有很多种。看到有的逐级查询,有的根据实际情况直接写死。我用的这个方法的好处是不管多少层都可以正确查询,虽然执行效率会低一些,但是反正是在后台使用的,问题不大。
  有的同学可能对这个方法有疑问。似乎一次应该只有两个级别的遍历。另外,菜单已经被查询过了,每次遍历的时候还是会调用查询方法。会不会频繁访问数据库,创建额外的对象,增加不必要的开销?虽然是背景,但这样写也太离谱了吧?
  (感谢@m0_46435907仔细分析后提供了这个问题的答案)
  这里可以放心,JPA为我们提供了持久化上下文(Persistence Context,是实体的集合),用来保证同一个持久化对象只有一个实例,不会再访问数据库当有对应的实例时(详见“JPA Persistence Context”)。因此,我们查询到的子列表中的每个 AdminMenu 对象实例都复用了 Menus 列表中的 AdminMenu 对象。
  同时,Java 中的对象都是引用类型。假设我们将b放入a的孩子中,将c放入b的孩子中,然后将c放入a的孩子的孩子中。因此,经过一次遍历,就可以得到正确的层次关系。
  下面的remove方法实际上是把对象名指向了null,对象本身还是存在的。所以虽然我们不能再通过b和c获取到原来的对象,但是a中的信息不会改变。
  为什么在移除子节点时使用 iterator.remove() 而不是 List 的 remove 方法?这是因为在使用List遍历的时候,如果删除了一个元素,就会添加后面的元素,也就是说后面的元素的索引和列表长度会发生变化。循环继续,循环次数还是列表的原创长度,不仅会漏掉一些元素,还会导致下标溢出,运行时性能会报ConcurrentModificationException。iterator.remove() 做了一些封装,将当前索引和循环次数减1,从而避免了这个问题。
  JDK 8+ 可以使用 lambda 表达式:
  public void handleMenus(List menus) {
menus.forEach(m -> {
List children = getAllByParentId(m.getId());
m.setChildren(children);
});
menus.removeIf(m -> m.getParentId() != 0);
}
  在 MenuController 中,根据请求调用查询逻辑。代码如下:
   @GetMapping("/api/menu")
public List menu() {
return adminMenuService.getMenusByCurrentUser();
}
  完成后就可以测试菜单界面了。在此之前,请确保系统处于登录状态,否则无法查询信息。
  
  二、前端实现
  前端要做的就是处理来自后端的数据,并将其传递给路由和导航菜单进行动态渲染。
  1.背景页面设计
  我目前有以下组件:
  主要是实现页面的基本设计,方便测试的动态加载,暂时还没有这个功能。效果大致是这样的:
  
  可以参考 GitHub 上的源码,也可以自己设计。开发完组件后,别忘了添加后台首页的路由。其他菜单对应的路由可以动态加载。如果没有事先写好,您将无法进入该页面。
  2.数据处理
  在我们设计AdminMenu表之前,它实际上收录了前端路由器和导航菜单所需的信息。从后台传来的数据需要整理成路由器能识别的格式。导航菜单无所谓,分配给相应的属性即可。
  格式转换方法如下:
  const formatRoutes = (routes) => {
let fmtRoutes = []
routes.forEach(route => {
if (route.children) {
route.children = formatRoutes(route.children)
}
let fmtRoute = {
path: route.path,
component: resolve => {
require([&#39;./components/admin/&#39; + route.component + &#39;.vue&#39;], resolve)
},
name: route.name,
nameZh: route.nameZh,
iconCls: route.iconCls,
children: route.children
}
fmtRoutes.push(fmtRoute)
})
return fmtRoutes
}
  这里传入的参数routes代表我们从后端获取的菜单列表。遍历这个列表,首先判断一个菜单项是否收录子项,如果是,则进行递归处理。
  下面的语句是将路由的属性与菜单项的属性对应起来。其余的都好说,主要是组件属性是一个对象,所以需要根据名字来解析(也就是获取对象引用)。同时,我们需要导入组件,所以我们可以使用Vue的异步组件加载机制(也称为延迟加载)在解析的同时完成导入。
  我们数据库中存储的是组件相对于@components/admin的路径,所以在解析的时候要根据js文件所在的位置加上对应的前缀。
  3.添加路由和渲染菜单
  首先我们需要考虑什么时候需要请求界面和渲染菜单。加载您访问的每个页面有点太浪费了。如果只在后台渲染主页面时加载一次,则不能在子页面中进行刷新操作。所以我们可以继续使用路由全局守卫在用户登录时请求菜单信息,访问以/admin开头的路径。完整代码如下
  router.beforeEach((to, from, next) => {
if (store.state.user.username && to.path.startsWith(&#39;/admin&#39;)) {
initAdminMenu(router, store)
}
// 已登录状态下访问 login 页面直接跳转到后台首页
if (store.state.username && to.path.startsWith(&#39;/login&#39;)) {
next({
path: &#39;admin/dashboard&#39;
})
}
if (to.meta.requireAuth) {
if (store.state.user.username) {
axios.get(&#39;/authentication&#39;).then(resp => {
if (resp) next()
})
} else {
next({
path: &#39;login&#39;,
query: {redirect: to.fullPath}
})
}
} else {
next()
}
}
)
  为了确保用户确实登录,仍然需要向后台发送认证请求。
  initAdminMenu 用于执行请求,调用格式化方法并向路由表添加信息,代码如下:
  const initAdminMenu = (router, store) => {
if (store.state.adminMenus.length > 0) {
return
}
axios.get(&#39;/menu&#39;).then(resp => {
if (resp && resp.status === 200) {
var fmtRoutes = formatRoutes(resp.data)
router.addRoutes(fmtRoutes)
store.commit(&#39;initAdminMenu&#39;, fmtRoutes)
}
})
}
  首先判断店铺是否有菜单数据。如果有说明,则为正常跳转,无需重新加载。(第一次进入或刷新时需要重新加载)
  记得在store.state中添加变量adminMenu: [],在mutations中添加如下方法:
   initAdminMenu (state, menus) {
state.adminMenus = menus
}
  这个菜单就是上面的 fmtRoutes。当然,你也可以将数据放入localStorage,只要记得注销时清空即可。
  最后,让我们编写菜单组件 AdminMenu.vue:
  








{{item.nameZh}}



{{ child.nameZh }}





export default {
name: &#39;AdminMenu&#39;,
computed: {
adminMenus () {
return this.$store.state.adminMenus
}
}
}
  这里我们使用 element 的导航栏组件来执行一个两层循环来渲染我们需要的菜单。表示带有子菜单的菜单项,并表示单个菜单项。命名似乎有问题,而且似乎没有错。. .
  如果有三个级别,则设置然后设置,以此类推。
  终于完成了。我们试试用admin账号登录,就是上面的效果,菜单满了:
  
  可以点击用户信息菜单跳转到对应的路由并加载组件:
  
  使用编辑器账号登录,只会显示内容管理
  
  下一步
  这个页面很匆忙。接下来,我们计划根据之前的设计对各个模块进行改进,包括:
  下一篇文章的重点是功能级权限的实现。其他方面会顺带提一下,但我不会过多赘述,因为都是已经讨论过的知识点。
  终于写完了,但是感觉这个文章还是有一些地方需要打磨的。如果大家有什么不明白的地方可以随便提,但我就直接忽略代码为什么不能运行的问题。
  另外,本文内容参考《_江南小雨》,是松哥的实现思路。我首先从松哥的项目中学习了 Vue。我们项目里很多前端代码都是模仿松哥的“微”写的“人力资源”项目,现在微人有11.6k星,大家可以学习一下,比我做了什么。
  上一篇:Vue+Spring Boot项目实战(十四):用户认证方案和完善的访问拦截
  下一篇:Vue+Spring Boot项目实战(十六):功能级访问控制的实现 查看全部

  nodejs抓取动态网页(【系列文章目录】「项目源码(GitHub)」本篇目录)
  重要链接:
  "系列文章目录"
  “项目源代码(GitHub)”
  本目录
  前言
  不出所料,我没能按时写完文章。时至今日,我已经没有任何愧疚感了,大概就是长大了。但话说回来,我曾经是一个每月写10篇文章的人。
  这个文章的主要内容是根据用户角色动态加载后台管理页面的菜单,关键点如下:
  在正文之前,让我告诉你这两周背后的故事:
  1、在官方活动——《CSDN发力计划》的加持下,经历了一波快速增长的浪潮,阅读量翻了一番。一连几天都肿不起来,甚至想下定决心,尽快达到破万的目标。然后我在房间里沉思良久,喝了一大杯快乐水,放弃了这个危险的想法。
  2、最近,我发现我们的项目除了入门练习之外,还可以作为一些常见应用的脚手架。如图书查询管理系统、个人博客(首页)、企业门户网站等。上周,我什至基于这个项目为公司内部活动搭建了一个投票系统。虽然觉得自己很崩溃,但是骗过不懂代码的同事就够了。
  为了让这个项目看起来很严肃,我对结构做了一些调整:
  其实前端配置了路由,修改了book组件,后端取消了对book查询端口的拦截。我相信你可以自己做。一位读者指出,我在一个地方漏掉了导入组件的代码。一开始我以为我错过了,但后来我想起我没有故意复制整个组件,因为我认为没有必要。不要只是复制代码并考虑它。不可能不报错,就是直接克隆别人的仓库,会出现各种问题。如果有问题,百度就可以了。
  3、有些读者给我发了十几封邮件,上千行的错误信息贴了好几次。没有这回事,我太南了(给我狂笑.jpg)
  但是,会说话的读者越来越多。我脸皮薄,很多时候还是不忍心拒绝你。在过去的两周里,我花了很多时间回答问题。一些认真的读者发现了项目的错误并指出了如何纠正它们。我很高兴,希望上帝能给我分配更多这样的读者。随着项目越来越复杂,难免会出现各种缺陷。欢迎大家指出各方面的不足,也可以直接在GitHub上提交Issue或者PR,一起来做吧。
  一、后端实现
  实现动态加载菜单功能的第一步是完成根据当前用户查询可访问菜单信息的界面。
  1.餐桌设计
  基于前面提到的RBAC原理,我们应该设计一个角色表,并使用角色来对应菜单。同时,为了建立用户和角色、角色和菜单之间的关系,需要两个中间表。除了前面的用户表,一共需要五个表,每个表的字段如下图所示:
  
  在这里,我将 admin 前缀添加到专用于后台管理的表中。命名是一件非常重要的事情。好名字是保证代码质量的前提。不要那么随随便便地跟着我。很多公司都有自己的命名原则,大家可以学习一下。(推荐《阿里巴巴Java开发手册》)
  另外,和之前book和category的做法不同,这里没有使用外键。一般来说,决定使用外键取决于系统对数据一致性和效率的要求哪个更突出,但我认为数据一致性问题可以通过代码来解决,使用外键既麻烦又尴尬。
  这里我简单介绍一下admin_menu表的字段:
  字段说明
  ID
  唯一标识
  小路
  对应Vue路由中的路径,即地址路径
  名称
  对应Vue路由中的name属性
  name_zh
  中文名称,用于渲染导航栏(菜单)界面
  icon_cls
  元素图标类名,用于渲染菜单名前的小图标
  零件
  组件名称,用于解析路由对应的组件
  parent_id
  父节点id,用于存储导航栏层次结构
  您可以自己设计表中的数据。为了方便测试,记得多注册一个账号来配置不同的角色,为角色配置相应的菜单。如果嫌麻烦,直接执行我的sql文件就行了:
  里面有admin、test和editor三个账号,密码都是123,admin的角色是系统管理员,editor是内容管理员,test是空的。
  2.pojo
  因为我们使用JPA作为ORM,所以在创建POJO时需要注意以下几点:
  PS 其实我们过去创建的POJO应该更具体一些,叫PO(persistant object,持久对象)或者Entity(实体),使用DTO(Data Transfer Object)与客户端进行交互。教程简化了一点,源代码添加了这些类别。
  我们需要创建四个 PO,AdminUserRole、AdminRole、AdminRoleMenu 和 AdminMenu。特别的是AdminMenu。在这里我发布代码:
  package com.gm.wj.pojo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name = "admin_menu")
@JsonIgnoreProperties({"handler", "hibernateLazyInitializer"})
public class AdminMenu {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
int id;
String path;
String name;
String nameZh;
String iconCls;
String component;
int parentId;
@Transient
List children;
// getter and setter...
  与数据库不同的是 Children 属性,用于存储子节点。
  3.菜单查询界面(树结构查询)
  根据用户查询找到对应菜单的步骤为:
  为了实现这个接口,我们需要添加三个数据库访问对象,AdminUserRoleDAO、AdminRoleMenuDAO和AdminMenuDAO,并编写Service对象。一路上我们也可以写一组AdminRoles,但是还不能用。
  在AdminMenuService中,需要实现一个根据当前用户查询所有菜单项的方法:
  public List getMenusByCurrentUser() {
// 从数据库中获取当前用户
String username = SecurityUtils.getSubject().getPrincipal().toString();
User user = userService.findByUsername(username);
// 获得当前用户对应的所有角色的 id 列表
List rids = adminUserRoleService.listAllByUid(user.getId())
.stream().map(AdminUserRole::getRid).collect(Collectors.toList());
// 查询出这些角色对应的所有菜单项
List menuIds = adminRoleMenuService.findAllByRid(rids)
.stream().map(AdminRoleMenu::getMid).collect(Collectors.toList());
List menus = adminMenuDAO.findAllById(menuIds).stream().distinct().collect(Collectors.toList());
// 处理菜单项的结构
handleMenus(menus);
return menus;
}
  该方法的主体对应于上面的前三个步骤。这里我们使用stream来简化列表的处理,包括使用map()提取集合中的某个属性,以及通过distinct()对查询到的菜单项进行去重,避免在多角色的情况下出现多余的菜单等。
  接下来,需要将查询到的菜单数据列表整合成具有层次关系的菜单树,即编写handleMenus()方法。
  这里多说一点,因为导航菜单一般不会很长,所以我们使用这种一次性取出的方式。在上面的过程中,我们会一边遍历列表一边查询数据库。在前台需要尽可能避免这样的多重交互。最好一次性查询全量数据,减轻服务器负担。不过后台一般都是管理员用的,流量也不大,不用担心。
  如果数据量特别大,应该考虑按节点动态加载。即通过监听节点的展开事件,将节点id作为参数发送到后端,在前端动态查询并渲染所有子节点。该方法的实现将在后面详细讨论。
  对查询到的菜单数据进行整合的思路如下:
  整合方法如下:
  public void handleMenus(List menus) {
for (AdminMenu menu : menus) {
List children = getAllByParentId(menu.getId());
menu.setChildren(children);
}

Iterator iterator = menus.iterator();
while (iterator.hasNext()) {
AdminMenu menu = iterator.next();
if (menu.getParentId() != 0) {
iterator.remove();
}
}
}
  首先,让我解释一下,查询树结构的方法有很多种。看到有的逐级查询,有的根据实际情况直接写死。我用的这个方法的好处是不管多少层都可以正确查询,虽然执行效率会低一些,但是反正是在后台使用的,问题不大。
  有的同学可能对这个方法有疑问。似乎一次应该只有两个级别的遍历。另外,菜单已经被查询过了,每次遍历的时候还是会调用查询方法。会不会频繁访问数据库,创建额外的对象,增加不必要的开销?虽然是背景,但这样写也太离谱了吧?
  (感谢@m0_46435907仔细分析后提供了这个问题的答案)
  这里可以放心,JPA为我们提供了持久化上下文(Persistence Context,是实体的集合),用来保证同一个持久化对象只有一个实例,不会再访问数据库当有对应的实例时(详见“JPA Persistence Context”)。因此,我们查询到的子列表中的每个 AdminMenu 对象实例都复用了 Menus 列表中的 AdminMenu 对象。
  同时,Java 中的对象都是引用类型。假设我们将b放入a的孩子中,将c放入b的孩子中,然后将c放入a的孩子的孩子中。因此,经过一次遍历,就可以得到正确的层次关系。
  下面的remove方法实际上是把对象名指向了null,对象本身还是存在的。所以虽然我们不能再通过b和c获取到原来的对象,但是a中的信息不会改变。
  为什么在移除子节点时使用 iterator.remove() 而不是 List 的 remove 方法?这是因为在使用List遍历的时候,如果删除了一个元素,就会添加后面的元素,也就是说后面的元素的索引和列表长度会发生变化。循环继续,循环次数还是列表的原创长度,不仅会漏掉一些元素,还会导致下标溢出,运行时性能会报ConcurrentModificationException。iterator.remove() 做了一些封装,将当前索引和循环次数减1,从而避免了这个问题。
  JDK 8+ 可以使用 lambda 表达式:
  public void handleMenus(List menus) {
menus.forEach(m -> {
List children = getAllByParentId(m.getId());
m.setChildren(children);
});
menus.removeIf(m -> m.getParentId() != 0);
}
  在 MenuController 中,根据请求调用查询逻辑。代码如下:
   @GetMapping("/api/menu")
public List menu() {
return adminMenuService.getMenusByCurrentUser();
}
  完成后就可以测试菜单界面了。在此之前,请确保系统处于登录状态,否则无法查询信息。
  
  二、前端实现
  前端要做的就是处理来自后端的数据,并将其传递给路由和导航菜单进行动态渲染。
  1.背景页面设计
  我目前有以下组件:
  主要是实现页面的基本设计,方便测试的动态加载,暂时还没有这个功能。效果大致是这样的:
  
  可以参考 GitHub 上的源码,也可以自己设计。开发完组件后,别忘了添加后台首页的路由。其他菜单对应的路由可以动态加载。如果没有事先写好,您将无法进入该页面。
  2.数据处理
  在我们设计AdminMenu表之前,它实际上收录了前端路由器和导航菜单所需的信息。从后台传来的数据需要整理成路由器能识别的格式。导航菜单无所谓,分配给相应的属性即可。
  格式转换方法如下:
  const formatRoutes = (routes) => {
let fmtRoutes = []
routes.forEach(route => {
if (route.children) {
route.children = formatRoutes(route.children)
}
let fmtRoute = {
path: route.path,
component: resolve => {
require([&#39;./components/admin/&#39; + route.component + &#39;.vue&#39;], resolve)
},
name: route.name,
nameZh: route.nameZh,
iconCls: route.iconCls,
children: route.children
}
fmtRoutes.push(fmtRoute)
})
return fmtRoutes
}
  这里传入的参数routes代表我们从后端获取的菜单列表。遍历这个列表,首先判断一个菜单项是否收录子项,如果是,则进行递归处理。
  下面的语句是将路由的属性与菜单项的属性对应起来。其余的都好说,主要是组件属性是一个对象,所以需要根据名字来解析(也就是获取对象引用)。同时,我们需要导入组件,所以我们可以使用Vue的异步组件加载机制(也称为延迟加载)在解析的同时完成导入。
  我们数据库中存储的是组件相对于@components/admin的路径,所以在解析的时候要根据js文件所在的位置加上对应的前缀。
  3.添加路由和渲染菜单
  首先我们需要考虑什么时候需要请求界面和渲染菜单。加载您访问的每个页面有点太浪费了。如果只在后台渲染主页面时加载一次,则不能在子页面中进行刷新操作。所以我们可以继续使用路由全局守卫在用户登录时请求菜单信息,访问以/admin开头的路径。完整代码如下
  router.beforeEach((to, from, next) => {
if (store.state.user.username && to.path.startsWith(&#39;/admin&#39;)) {
initAdminMenu(router, store)
}
// 已登录状态下访问 login 页面直接跳转到后台首页
if (store.state.username && to.path.startsWith(&#39;/login&#39;)) {
next({
path: &#39;admin/dashboard&#39;
})
}
if (to.meta.requireAuth) {
if (store.state.user.username) {
axios.get(&#39;/authentication&#39;).then(resp => {
if (resp) next()
})
} else {
next({
path: &#39;login&#39;,
query: {redirect: to.fullPath}
})
}
} else {
next()
}
}
)
  为了确保用户确实登录,仍然需要向后台发送认证请求。
  initAdminMenu 用于执行请求,调用格式化方法并向路由表添加信息,代码如下:
  const initAdminMenu = (router, store) => {
if (store.state.adminMenus.length > 0) {
return
}
axios.get(&#39;/menu&#39;).then(resp => {
if (resp && resp.status === 200) {
var fmtRoutes = formatRoutes(resp.data)
router.addRoutes(fmtRoutes)
store.commit(&#39;initAdminMenu&#39;, fmtRoutes)
}
})
}
  首先判断店铺是否有菜单数据。如果有说明,则为正常跳转,无需重新加载。(第一次进入或刷新时需要重新加载)
  记得在store.state中添加变量adminMenu: [],在mutations中添加如下方法:
   initAdminMenu (state, menus) {
state.adminMenus = menus
}
  这个菜单就是上面的 fmtRoutes。当然,你也可以将数据放入localStorage,只要记得注销时清空即可。
  最后,让我们编写菜单组件 AdminMenu.vue:
  








{{item.nameZh}}



{{ child.nameZh }}





export default {
name: &#39;AdminMenu&#39;,
computed: {
adminMenus () {
return this.$store.state.adminMenus
}
}
}
  这里我们使用 element 的导航栏组件来执行一个两层循环来渲染我们需要的菜单。表示带有子菜单的菜单项,并表示单个菜单项。命名似乎有问题,而且似乎没有错。. .
  如果有三个级别,则设置然后设置,以此类推。
  终于完成了。我们试试用admin账号登录,就是上面的效果,菜单满了:
  
  可以点击用户信息菜单跳转到对应的路由并加载组件:
  
  使用编辑器账号登录,只会显示内容管理
  
  下一步
  这个页面很匆忙。接下来,我们计划根据之前的设计对各个模块进行改进,包括:
  下一篇文章的重点是功能级权限的实现。其他方面会顺带提一下,但我不会过多赘述,因为都是已经讨论过的知识点。
  终于写完了,但是感觉这个文章还是有一些地方需要打磨的。如果大家有什么不明白的地方可以随便提,但我就直接忽略代码为什么不能运行的问题。
  另外,本文内容参考《_江南小雨》,是松哥的实现思路。我首先从松哥的项目中学习了 Vue。我们项目里很多前端代码都是模仿松哥的“微”写的“人力资源”项目,现在微人有11.6k星,大家可以学习一下,比我做了什么。
  上一篇:Vue+Spring Boot项目实战(十四):用户认证方案和完善的访问拦截
  下一篇:Vue+Spring Boot项目实战(十六):功能级访问控制的实现

nodejs抓取动态网页(Google和Baidu的爬虫技术是不公开的,算是商业机密了)

网站优化优采云 发表了文章 • 0 个评论 • 80 次浏览 • 2022-02-02 03:12 • 来自相关话题

  nodejs抓取动态网页(Google和Baidu的爬虫技术是不公开的,算是商业机密了)
  之前玩千里的时候,有这样一个问题,如下:谷歌、百度的两大技术:爬虫和搜索。爬虫负责爬取整个互联网的内容,搜索负责生成索引供用户检索。所以爬虫是这两家公司的看家本领。但是,谷歌和百度的爬虫技术并不公开,属于商业机密。所以,360搜索刚起步的时候,第一件事就是挖了百度核心搜索爬虫群的程序员,基本是3倍的工资。虽然没有办法知道这两家公司的爬虫技术,但是我们可以向开源项目学习。例如,Scrapy 是一个非常好的开源爬虫框架。如果你对爬虫感兴趣,
  简单来说,爬虫分为两步:
  这里有一个很简单的任务,让大家感受一下爬虫:豆瓣电影Top250收录迄今为止最喜欢的250部电影。该列表显示了每部电影的基本信息,例如评级、年份等。这个问题的简单答案是该列表中排名前 166 部电影的评分总和。例子:目前第一名《肖申克的救赎》是9.6分,第二名《这个杀手不太冷》是9.4分,第三名《阿甘正传》是9.4 分。那么前3部电影的总分是9.6+9.4+9.4=28.4。
  下面是具体的实现代码:
<p>var url = &#39;https://movie.douban.com/top250?start=&#39;;
//需要统计166部,每页25条
var superagent = require(&#39;superagent&#39;);
var cheerio = require(&#39;cheerio&#39;);
var numArr = [];
var getNext = function(path,start){
var tempPath = path+start;
superagent.get(tempPath).set({
&#39;cookie&#39;:&#39;bid=xEbtZyJpLBs; _pk_ref.100001.4cf6=%5B%22%22%2C%22%22%2C1474114942%2C%22http%3A%2F%2Fwww.qlcoder.com%2Ftask%2F7560%22%5D; _pk_id.100001.4cf6=ebdb1fdadde80a53.1474114942.1.1474115010.1474114942.; _pk_ses.100001.4cf6=*; __utma=30149280.834196820.1474114942.1474114942.1474114942.1; __utmb=30149280.0.10.1474114942; __utmc=30149280; __utmz=30149280.1474114942.1.1.utmcsr=qlcoder.com|utmccn=(referral)|utmcmd=referral|utmcct=/task/7560; __utma=223695111.500893696.1474114942.1474114942.1474114942.1; __utmb=223695111.0.10.1474114942; __utmc=223695111; __utmz=223695111.1474114942.1.1.utmcsr=qlcoder.com|utmccn=(referral)|utmcmd=referral|utmcct=/task/7560&#39;
}).end(function(err,res){
var text = res.text;
var $ = cheerio.load(text);
var flag = false;
$(&#39;.rating_num&#39;).each(function(index,ele){
var num = $(ele).html();
num = parseFloat(num);
if(numArr.length == 166){
flag = true;
countNum ();
}
if(!flag){
numArr.push(num);
}
});
if(!flag){
getNext(path,start+25);
}
});
};
var countNum = function(){
var total = 0 ;
for(var i=0,max=numArr.length;i 查看全部

  nodejs抓取动态网页(Google和Baidu的爬虫技术是不公开的,算是商业机密了)
  之前玩千里的时候,有这样一个问题,如下:谷歌、百度的两大技术:爬虫和搜索。爬虫负责爬取整个互联网的内容,搜索负责生成索引供用户检索。所以爬虫是这两家公司的看家本领。但是,谷歌和百度的爬虫技术并不公开,属于商业机密。所以,360搜索刚起步的时候,第一件事就是挖了百度核心搜索爬虫群的程序员,基本是3倍的工资。虽然没有办法知道这两家公司的爬虫技术,但是我们可以向开源项目学习。例如,Scrapy 是一个非常好的开源爬虫框架。如果你对爬虫感兴趣,
  简单来说,爬虫分为两步:
  这里有一个很简单的任务,让大家感受一下爬虫:豆瓣电影Top250收录迄今为止最喜欢的250部电影。该列表显示了每部电影的基本信息,例如评级、年份等。这个问题的简单答案是该列表中排名前 166 部电影的评分总和。例子:目前第一名《肖申克的救赎》是9.6分,第二名《这个杀手不太冷》是9.4分,第三名《阿甘正传》是9.4 分。那么前3部电影的总分是9.6+9.4+9.4=28.4。
  下面是具体的实现代码:
<p>var url = &#39;https://movie.douban.com/top250?start=&#39;;
//需要统计166部,每页25条
var superagent = require(&#39;superagent&#39;);
var cheerio = require(&#39;cheerio&#39;);
var numArr = [];
var getNext = function(path,start){
var tempPath = path+start;
superagent.get(tempPath).set({
&#39;cookie&#39;:&#39;bid=xEbtZyJpLBs; _pk_ref.100001.4cf6=%5B%22%22%2C%22%22%2C1474114942%2C%22http%3A%2F%2Fwww.qlcoder.com%2Ftask%2F7560%22%5D; _pk_id.100001.4cf6=ebdb1fdadde80a53.1474114942.1.1474115010.1474114942.; _pk_ses.100001.4cf6=*; __utma=30149280.834196820.1474114942.1474114942.1474114942.1; __utmb=30149280.0.10.1474114942; __utmc=30149280; __utmz=30149280.1474114942.1.1.utmcsr=qlcoder.com|utmccn=(referral)|utmcmd=referral|utmcct=/task/7560; __utma=223695111.500893696.1474114942.1474114942.1474114942.1; __utmb=223695111.0.10.1474114942; __utmc=223695111; __utmz=223695111.1474114942.1.1.utmcsr=qlcoder.com|utmccn=(referral)|utmcmd=referral|utmcct=/task/7560&#39;
}).end(function(err,res){
var text = res.text;
var $ = cheerio.load(text);
var flag = false;
$(&#39;.rating_num&#39;).each(function(index,ele){
var num = $(ele).html();
num = parseFloat(num);
if(numArr.length == 166){
flag = true;
countNum ();
}
if(!flag){
numArr.push(num);
}
});
if(!flag){
getNext(path,start+25);
}
});
};
var countNum = function(){
var total = 0 ;
for(var i=0,max=numArr.length;i

nodejs抓取动态网页(华为云开发者社区搭建python爬虫管理平台的相关内容吗)

网站优化优采云 发表了文章 • 0 个评论 • 49 次浏览 • 2022-01-29 23:11 • 来自相关话题

  nodejs抓取动态网页(华为云开发者社区搭建python爬虫管理平台的相关内容吗)
  想知道快速搭建python爬虫管理平台的相关内容吗?在这篇文章中,华为云开发者社区将为大家讲解搭建python爬虫平台的相关知识和一些代码示例。欢迎阅读和指正。我们先把重点放在:python、爬虫、爬虫、构建,一起来学习。
  爬行动物有多重要
  对于搜索引擎来说,爬虫是必不可少的;对于舆论公司来说,爬虫是基础;对于 NLP,爬虫可以获得语料库;对于初创公司,爬虫可以获取初始内容。但是爬虫技术比较复杂,不同类型的爬取场景会用到不同的技术。比如一个简单的静态页面可以直接用HTTP请求+HTML解析器完成;动态页面需要使用 Puppeteer 或 Selenium 等自动化测试工具;反爬网站需要使用代理、编码等技术;等等。这时就需要一个成熟的爬虫管理平台来帮助企业或个人处理大量的爬虫类别。
  了解爬虫管理平台的定义是什么
  爬虫管理平台是集爬虫部署、任务调度、任务监控、结果展示等模块于一体的一站式管理系统。它通常配备可视化的 UI 界面,通过与 Web 端的 UI 界面交互,可以有效地管理爬虫。爬虫管理平台一般支持多机分布式协同运行。
  当然,上述定义是狭隘的,通常针对技术人员或开发人员或技术管理人员。企业一般会自行开发内部爬虫管理系统,以应对复杂的爬虫管理需求。这样的系统就是上面定义的狭义的爬虫管理平台。
  通用爬虫管理平台
  什么是通用爬虫管理平台?您可能听说过 优采云(后来转换为 优采云采集器)和 优采云。前者是基于云服务的爬虫管理平台,可以在线编写、运行和监控爬虫。是一般爬虫平台中最接近狭义的爬虫管理平台;后者是一种流行的商业爬虫爬取工具,允许新手用户通过拖放来编写和运行爬虫导出数据。您可能还见过各种 API 聚合服务提供商,例如 Aggregate Data。这是一个可以直接调用网站接口获取数据的平台。这实际上是爬虫平台的一个变种,但它可以帮助您完成爬虫编写过程。过程。而在两者之间,国外有个叫Kimonolab的公司,开发了一个Chrome插件叫Kimono,可以让用户直观的点击页面上的元素,生成抓取规则,放到网站爬虫生成后,用户提交任务,数据可以在网站上自动捕获。Kimono是一款很棒的爬虫应用,可惜Kimonolab已经被大数据公司Plantir收购,现在无法体验。
  在本文中,我们主要关注狭义的爬虫管理平台,因此后面提到的爬虫管理平台指的是狭义的。
  爬虫管理平台模块
  以下是一个典型的爬虫管理平台所涉及的模块。
  
  爬虫管理平台架构
  一个典型的爬虫管理平台的模块主要包括以下几个:
  当然,有些爬虫管理平台的模块可能不止这些。它可能包括其他更实用的功能,如可配置爬虫规则、可视化配置爬虫规则、代理池、cookie池、异常监控等。
  为什么需要爬虫管理平台
  有了爬虫管理平台,开发者,尤其是爬虫工程师,可以很方便的添加爬虫,执行任务,查看结果,不用在命令行之间来回切换,非常容易出错。一个常见的场景是爬虫工程师在最初的技术选型中使用了scrapy和crontab来管理爬虫任务。他必须仔细选择计划任务的时间间隔,以免填满服务器 CPU 或内存;比较棘手的问题是,他还需要将scrapy生成的日志保存在一个文件中。爬虫一旦出错,就得使用shell命令一一查看日志,定位出错原因。如果严重的话,需要一整天的时间;还有一个严重的问题。爬虫工程师可能会发现公司的业务量 当他需要编写数百个爬虫来满足公司的业务需求时,使用 scrapy 和 crontab 进行管理是一场彻头彻尾的噩梦。可怜的爬虫工程师其实可以选择一个合适的爬虫管理平台来解决他的问题。
  如何选择合适的爬虫管理平台
  当您愿意解决上述爬虫工程师遇到的难题,而是想选择合适的爬虫管理平台时。
  您应该回答的第一个问题是:我们是否需要从头开始开发系统(Start from scratch)?要回答这个问题,您应该首先回答以下问题:
  1.我们的需求是否复杂到可以完全定制一个新系统(例如,需要复杂的权限管理)?
  2.我们的团队是否有足够的技术力量来开发这个系统(例如经验丰富的前端和后端开发工程师)?
  3.我们的时间资源是否足以让我们开发系统(例如一年的项目规划周期)?
  如果以上三个问题的任何一个问题的答案都是“否”,您应该考虑使用市面上的开源爬虫管理平台来满足您的需求。
  以下是市面上的开源爬虫管理平台:
  
  总的来说,SpiderKeeper可能是最早的爬虫管理平台,但功能相对有限;Gerapy 有一个功能齐全且美观的界面,但有许多 bug 需要处理。建议有需要的用户等待2.0版本;Scrapydweb是一个比较完善的爬虫管理平台,基于scrapyd,所以只能运行scrapy爬虫;而 Crawlab 是一个非常灵活的爬虫管理平台,可以运行用 Python、Nodejs、Java、PHP 和 Go 编写的爬虫。比较全,但是部署起来比前三个麻烦一些,但是对于Docker用户来说可以一体部署(后面会讲)。
  所以严重依赖scrapy爬虫不想折腾的开发者可以考虑Scrapydweb;而对于具有各类复杂技术结构的爬虫开发者,应优先考虑更灵活的Crawlab。当然,并不是说 Crawlab 对 scrapy 支持不友好。Crawlab 也可以很好的集成scrapy。稍后会介绍。
  作为Crawlab的作者,我不希望王破卖瓜吹牛。笔者只是想把最好的技术选型推荐给开发者,让开发者可以根据自己的需求决定使用哪个爬虫管理平台。
  爬虫管理平台Crawlab介绍
  Crawlab是一个基于Golang的分布式爬虫管理平台,支持Python、NodeJS、Java、Go、PHP等多种编程语言和爬虫框架。
  Crawlab自今年3月上线以来,深受爬虫爱好者和开发者的好评。不少用户也表示会使用Crawlab搭建公司的爬虫平台。经过几个月的迭代,Crawlab先后推出了定时任务、数据分析、网站信息、可配置爬虫、自动字段提取、下载结果、上传爬虫等功能,使平台更加实用和全面,可以真正帮助用户解决爬虫管理的难题。现在 Github 上有近 1k 的明星相关社区,四分之一的用户表示已经将 Crawlab 应用到企业爬虫管理中。可见Crawlab受到开发者的关注和喜爱。
  解决这个问题
  Crawlab主要解决大量爬虫管理困难的问题,比如需要监控数百个网站混合的scrapy和selenium项目。同时不容易管理,命令行管理成本很高,容易出错。Crawlab 支持任意语言、任意框架的任务调度和任务监控,轻松有效监控和管理大型爬虫项目。
  接口和使用
  下面是 Crawlab 爬虫列表页面的截图。
  
  Crawlab爬虫列表
  用户只需要将爬虫上传到Crawlab配置执行命令,点击“运行”按钮即可执行爬虫任务。爬虫任务可以在任何节点上运行。从上图可以看出,Crawlab有节点管理、爬虫管理、任务管理、定时任务、用户管理等模块。
  整体结构
  以下是Crawlab的整体架构图,由五部分组成:
  1.Master Node:负责任务调度、API、爬虫部署等;
  2.Worker Node:负责执行爬虫任务;
  3.MongoDB数据库:存储节点、爬虫、任务等日常运行数据;
  4.Redis数据库:存储任务消息队列、节点心跳等信息。
  5.前端客户端:Vue应用负责前端交互,向后端请求数据。
  
  Github地址和Demo
  查看演示 DemoGithub:
  使用 Docker 部署安装 CrawlabDocker 映像
  Docker 是部署 Crawlab 最方便、最简洁的方式。其他部署方式包括直接部署,但不推荐给想要快速搭建平台的开发者。Crawlab 已经在 Dockerhub 上注册了相关镜像。开发者只需执行 docker pull tikazyq/crawlab 命令即可下载 Crawlab 镜像。
  读者可以去Dockerhub看看,Crawlab的镜像只有不到300Mb。地址:
  
  Dockerhub 页面
  安装 Docker
  要使用 Docker 部署 Crawlab,您必须首先确保已安装 Docker。请参考以下文档进行安装。
  
  安装 Docker Compose
  Docker Compose 是一个运行 Docker 集群的简单工具。非常轻巧。我们将使用 Docker Compose 一键部署 Crawlab。
  Docker 官方 网站 已经有安装 Docker Compose 的教程,点击链接查看。这里有一个简单的介绍。
  
  对于 Linux 用户,请使用以下命令进行安装。
  
# 下载 docker-compose
sudo curl -L "https://github.com/docker/comp ... pose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose`
# 将 docker-compose 变成执行文件
sudo chmod +x /usr/local/bin/docker-compose
  拉图像
  拉取镜像前需要配置镜像源。因为在国内使用原创镜像源的速度不是很快,所以需要使用DockerHub的国产加速器。请创建 /etc/docker/daemon.json 文件并输入以下内容。
  
{
"registry-mirrors": ["https://registry.docker-cn.com"]
}
  然后拉图像会快得多。当然,您也可以使用其他镜像源在线搜索。运行以下命令拉取 Crawlab 镜像。
  
docker pull tikazyq/crawlab:latest
  下图是拉取镜像时的命令行界面。
  
  码头工人拉
  开始爬行实验室
  我们将使用 Docker Compose 启动 Crawlab 及其依赖的数据库 MongoDB 和 Redis。首先我们需要修改Docker Compose的yaml配置文件docker-compose.yml。该配置文件定义了需要启动的容器服务和网络配置。这里我们使用 Crawlab 自带的 docker-compose.yml。
  
version: '3.3' # Docker Compose 的版本号(请看后续说明)
services: # 服务
master: # 服务名称
image: tikazyq/crawlab:latest # 服务对应的镜像名称
container_name: master # 服务对应的容器名称
environment: # 这里定义传入的环境变量
CRAWLAB_API_ADDRESS: "localhost:8000" # 前端调用的 API 地址默认为 localhost:8000
CRAWLAB_SERVER_MASTER: "Y" # 是否为主节点Y/N
CRAWLAB_MONGO_HOST: "mongo" # MongoDB host由于在 Docker Compose 里可以引用服务名称
CRAWLAB_REDIS_ADDRESS: "redis" # Redis host由于在 Docker Compose 里可以引用服务名称
ports: # 映射的端口
- "8080:8080" # 前端端口
- "8000:8000" # 后端端口
depends_on: # 依赖的服务
- mongo # MongoDB
- redis # Redis
worker: # 工作节点与主节点配置类似不重复写了
image: tikazyq/crawlab:latest
container_name: worker
environment:
CRAWLAB_SERVER_MASTER: "N"
CRAWLAB_MONGO_HOST: "mongo"
CRAWLAB_REDIS_ADDRESS: "redis"
depends_on:
- mongo
- redis
mongo: # MongoDB 服务名称
image: mongo:latest # MongoDB 镜像名称
restart: always # 重启策略为“总是”
ports: # 映射端口
- "27017:27017"
redis: # Redis 服务名称
image: redis:latest # Redis 镜像名称
restart: always # 重启策略为“总是”
ports: # 映射端口
- "6379:6379"
  读者可以根据自己的需求配置docker-compose.yml。特别要注意环境变量 CRAWLAB_API_ADDRESS。很多新手用户因为变量配置不正确而无法登录。在大多数情况下,您不需要进行任何配置更改。请参阅常见问题解答和详细的环境变量配置文档,以帮助根据您的环境配置 Crawlab。
  然后运行以下命令启动 Crawlab。您可以添加 -d 参数以使 Docker Compose 在后台运行。
  
docker-compose up
  运行上述命令后,Docker Compose 将拉取 MongoDB 和 Redis 映像,这可能需要几分钟时间。拉取完成后,四个服务会依次启动。您将在命令行上看到以下内容。
  
  码头工人撰写
  正常情况下应该可以看到四个服务全部启动成功,并且可以顺利打印日志。
  如果在本地机器上启动Docker Compose,可以在浏览器中输入:8080,可以看到登录界面;如果在另一台机器上启动Docker Compose,需要在浏览器中输入:8080才能看到登录界面是其他机器的IP地址(请确保本机8080端口对外开放)。
  
  登录
  初始登录用户名和密码为 admin/admin,您可以使用此用户名和密码登录。如果您的环境变量 CRAWLAB_API_ADDRESS 设置不正确,您可能会看到登录按钮在单击登录后一直在旋转而没有任何提示。此时请在 docker-compose.yml 中设置正确的 CRAWLAB_API_ADDRESS(将 localhost 替换为)并重启 docker-compose up。然后在浏览器中输入:8080。
  登录后,您将看到 Crawlab 主页。
  
  家
  本文文章主要介绍如何搭建爬虫管理平台Crawlab,所以不会详细介绍如何使用Crawlab(可以另写一篇文章文章详细介绍,有兴趣的可以付费注意力)。如果您感到困惑,请查看相关文档以了解如何使用它。
  如何将 Scrapy 等爬虫集成到 Crawlab 中
  众所周知,Scrapy 是一个非常流行的爬虫框架。其灵活的框架设计、高并发、易用性和可扩展性已被众多开发者和企业广泛采用。市面上几乎所有爬虫管理平台都支持Scrapy爬虫Crawlab也不例外,但Crawlab可以运行puppeteer、selenium等其他爬虫。下面将介绍如何在Crawlab中运行scrapy爬虫。
  Crawlab是执行爬虫的基本原理
  Crawlab的爬虫原理很简单。它实际上是一个 shell 命令。用户在爬虫中输入shell命令执行爬虫,比如scrapy crawl some_spiderCrawlab执行器会读取这个命令,直接在shell中执行。因此,每次运行爬虫任务时,都要执行一个shell命令(当然实际情况比这复杂得多,有兴趣的可以参考官方文档)。Crawlab 支持显示和导出爬虫结果,但这需要更多的工作。
  编写管道
  集成scrapy爬虫无非是将爬虫爬取的数据存储在Crawlab的数据库中,并与任务ID相关联。每次执行爬虫任务时,都会通过环境变量将任务ID传递给爬虫程序,所以我们只需要将任务ID加上结果存入数据库即可(Crawlab目前只支持MongoDB,后续会开发) MySQL、SQL Server、Postgres 等关系类型未来需要数据库的用户可以关注)。
  在 Scrapy 中,我们需要编写存储逻辑。原理图代码如下:
  
# 引入相关的库pymongo 是标准连接 MongoDB 的库
import os
from pymongo import MongoClient
# MongoDB 配置参数
MONGO_HOST = '192.168.99.100'
MONGO_PORT = 27017
MONGO_DB = 'crawlab_test'
class JuejinPipeline(object):
mongo = MongoClient(host=MONGO_HOST, port=MONGO_PORT) # mongo 连接实例
db = mongo[MONGO_DB] # 数据库实例
col_name = os.environ.get('CRAWLAB_COLLECTION') # 集合名称通过环境变量 CRAWLAB_COLLECTION 传过来
# 如果 CRAWLAB_COLLECTION 不存在则默认集合名称为 test
if not col_name:
col_name = 'test'

col = db[col_name] # 集合实例
# 每一个传入 item 会调用的函数参数分别为 item 和 spider
def process_item(self, item, spider):
item['task_id'] = os.environ.get('CRAWLAB_TASK_ID') # 将 task_id 设置为环境变量传过来的任务 ID
self.col.save(item) # 保存 item 在数据库中
return item
  同时,还需要在 items.py 中添加 task_id 字段,确保可以赋值(这个很重要)。
  上传并配置爬虫
  在运行爬虫之前,您需要将爬虫文件上传到主节点。进行如下操作:
  1.将爬虫文件打包成zip(请确保直接打包在根目录下);
  2.点击侧边栏的“爬虫”导航到爬虫列表,点击“添加爬虫”按钮选择“自定义爬虫”;
  3.点击“上传”按钮选择刚刚打包好的zip文件
  4.上传成功后,新添加的自定义爬虫会出现在爬虫列表中,所以上传成功。
  您可以点击爬虫详情中的“文件”选项卡选择一个文件来编辑文件中的代码。
  
  接下来,需要在“概览”选项卡的“执行命令”栏中输入爬虫的shell执行命令。Crawlab的Docker镜像内置了scrapy,可以直接运行scrapy爬虫。命令是scrapy crawl。点击“保存”按钮保存爬虫配置。
  运行爬虫任务
  然后是时候运行爬虫任务了。其实很简单,点击“概览”选项卡中的“运行”按钮即可启动爬虫任务。如果日志提示找不到scrapy命令,可以将scrapy改成绝对路径/usr/local/bin/scrapy,运行成功。
  任务的运行状态会显示在“任务”页面或爬虫的“概览”中,每5秒更新一次。你可以在这里查看。并且在爬虫“结果”选项卡中,您可以预览结果的详细信息并将数据导出到 CSV 文件。
  构建持续集成 (CI) 工作流
  软件开发通常是企业的自动化过程。它将经历需求、开发、部署、测试和启动的步骤。而且这个过程一般是迭代的,需要不断的更新和发布。
  以爬虫为例。您已经启动了一个爬虫。此爬虫将定期获取 网站 数据。但突然有一天,你发现无法捕获数据。你快速定位原因,发现网站版本已经修改。你需要改变爬虫的爬取规则来应对网站的修改。无论如何,您需要发布代码更新。最快的做法是直接在线更改代码。但这很危险:第一,不能测试自己更新的代码,只能通过不断调整在线代码来测试爬取是否成功;其次,如果出现问题,您以后无法记录更改,您很可能会忽略它。此更改导致了错误。您需要做的就是使用版本管理工具来管理您的爬虫代码。
  当我们更新代码时,我们需要将更新后的代码发布到在线服务器。这时候就需要自己编写部署脚本或者更方便的使用Jenkins作为持续集成管理平台。Jenkins 是一个持续集成平台,可以通过获取存储库来更新部署代码。它是一个非常实用的工具,被许多企业使用。下图是如何将 Crawlab 爬虫应用于持续集成工作流的示例。
  
  词
  在 Crawlab 中有两种创建或更新爬虫的方法:
  1.上传打包好的zip文件;
  2.通过更改主节点CRAWLAB_SPIDER_PATH目录下的爬虫文件。
  我们针对第二种方式进行持续集成。进行如下操作:
  1.使用Gitlab或者其他平台搭建代码仓库;
  2.在Jenkins中创建一个项目,并将代码源指向项目中之前创建的仓库;
  3.在Jenkins项目中编写工作流,并将发布地址指向Crawlab的CRAWLAB_SPIDER_PATH。如果是Docker,注意挂载地址到宿主机文件系统;
  4.Jenkins项目的工作可以直接写也可以使用Jenkinsfile查看相关信息;
  5.这样每次代码更新提交到代码仓库后,Jenkins都会将更新后的代码发布到Crawlab。Crawlab主节点会将爬虫代码同步到worker节点进行爬取。
  总结
  本篇文章主要介绍爬虫管理平台的定义,如何选择爬虫管理平台,重点介绍如何搭建开源的爬虫管理平台Crawlab。它还讨论了如何集成scrapy爬虫以及如何创建持续集成工作流。这篇文章文章并没有涉及很多内容,包括如何Crawlab的原理和架构细节,如何使用Crawlab,如何编写大型爬虫,如何使用Jenkins等等。这些内容可能会在其他文章上发表,感兴趣的读者请留意。此外,Crawlab 还有一些需要改进的地方,比如异常监控(零值、空值)、可配置爬虫、可视化爬取、集中日志采集等。
  相关文章 查看全部

  nodejs抓取动态网页(华为云开发者社区搭建python爬虫管理平台的相关内容吗)
  想知道快速搭建python爬虫管理平台的相关内容吗?在这篇文章中,华为云开发者社区将为大家讲解搭建python爬虫平台的相关知识和一些代码示例。欢迎阅读和指正。我们先把重点放在:python、爬虫、爬虫、构建,一起来学习。
  爬行动物有多重要
  对于搜索引擎来说,爬虫是必不可少的;对于舆论公司来说,爬虫是基础;对于 NLP,爬虫可以获得语料库;对于初创公司,爬虫可以获取初始内容。但是爬虫技术比较复杂,不同类型的爬取场景会用到不同的技术。比如一个简单的静态页面可以直接用HTTP请求+HTML解析器完成;动态页面需要使用 Puppeteer 或 Selenium 等自动化测试工具;反爬网站需要使用代理、编码等技术;等等。这时就需要一个成熟的爬虫管理平台来帮助企业或个人处理大量的爬虫类别。
  了解爬虫管理平台的定义是什么
  爬虫管理平台是集爬虫部署、任务调度、任务监控、结果展示等模块于一体的一站式管理系统。它通常配备可视化的 UI 界面,通过与 Web 端的 UI 界面交互,可以有效地管理爬虫。爬虫管理平台一般支持多机分布式协同运行。
  当然,上述定义是狭隘的,通常针对技术人员或开发人员或技术管理人员。企业一般会自行开发内部爬虫管理系统,以应对复杂的爬虫管理需求。这样的系统就是上面定义的狭义的爬虫管理平台。
  通用爬虫管理平台
  什么是通用爬虫管理平台?您可能听说过 优采云(后来转换为 优采云采集器)和 优采云。前者是基于云服务的爬虫管理平台,可以在线编写、运行和监控爬虫。是一般爬虫平台中最接近狭义的爬虫管理平台;后者是一种流行的商业爬虫爬取工具,允许新手用户通过拖放来编写和运行爬虫导出数据。您可能还见过各种 API 聚合服务提供商,例如 Aggregate Data。这是一个可以直接调用网站接口获取数据的平台。这实际上是爬虫平台的一个变种,但它可以帮助您完成爬虫编写过程。过程。而在两者之间,国外有个叫Kimonolab的公司,开发了一个Chrome插件叫Kimono,可以让用户直观的点击页面上的元素,生成抓取规则,放到网站爬虫生成后,用户提交任务,数据可以在网站上自动捕获。Kimono是一款很棒的爬虫应用,可惜Kimonolab已经被大数据公司Plantir收购,现在无法体验。
  在本文中,我们主要关注狭义的爬虫管理平台,因此后面提到的爬虫管理平台指的是狭义的。
  爬虫管理平台模块
  以下是一个典型的爬虫管理平台所涉及的模块。
  
  爬虫管理平台架构
  一个典型的爬虫管理平台的模块主要包括以下几个:
  当然,有些爬虫管理平台的模块可能不止这些。它可能包括其他更实用的功能,如可配置爬虫规则、可视化配置爬虫规则、代理池、cookie池、异常监控等。
  为什么需要爬虫管理平台
  有了爬虫管理平台,开发者,尤其是爬虫工程师,可以很方便的添加爬虫,执行任务,查看结果,不用在命令行之间来回切换,非常容易出错。一个常见的场景是爬虫工程师在最初的技术选型中使用了scrapy和crontab来管理爬虫任务。他必须仔细选择计划任务的时间间隔,以免填满服务器 CPU 或内存;比较棘手的问题是,他还需要将scrapy生成的日志保存在一个文件中。爬虫一旦出错,就得使用shell命令一一查看日志,定位出错原因。如果严重的话,需要一整天的时间;还有一个严重的问题。爬虫工程师可能会发现公司的业务量 当他需要编写数百个爬虫来满足公司的业务需求时,使用 scrapy 和 crontab 进行管理是一场彻头彻尾的噩梦。可怜的爬虫工程师其实可以选择一个合适的爬虫管理平台来解决他的问题。
  如何选择合适的爬虫管理平台
  当您愿意解决上述爬虫工程师遇到的难题,而是想选择合适的爬虫管理平台时。
  您应该回答的第一个问题是:我们是否需要从头开始开发系统(Start from scratch)?要回答这个问题,您应该首先回答以下问题:
  1.我们的需求是否复杂到可以完全定制一个新系统(例如,需要复杂的权限管理)?
  2.我们的团队是否有足够的技术力量来开发这个系统(例如经验丰富的前端和后端开发工程师)?
  3.我们的时间资源是否足以让我们开发系统(例如一年的项目规划周期)?
  如果以上三个问题的任何一个问题的答案都是“否”,您应该考虑使用市面上的开源爬虫管理平台来满足您的需求。
  以下是市面上的开源爬虫管理平台:
  
  总的来说,SpiderKeeper可能是最早的爬虫管理平台,但功能相对有限;Gerapy 有一个功能齐全且美观的界面,但有许多 bug 需要处理。建议有需要的用户等待2.0版本;Scrapydweb是一个比较完善的爬虫管理平台,基于scrapyd,所以只能运行scrapy爬虫;而 Crawlab 是一个非常灵活的爬虫管理平台,可以运行用 Python、Nodejs、Java、PHP 和 Go 编写的爬虫。比较全,但是部署起来比前三个麻烦一些,但是对于Docker用户来说可以一体部署(后面会讲)。
  所以严重依赖scrapy爬虫不想折腾的开发者可以考虑Scrapydweb;而对于具有各类复杂技术结构的爬虫开发者,应优先考虑更灵活的Crawlab。当然,并不是说 Crawlab 对 scrapy 支持不友好。Crawlab 也可以很好的集成scrapy。稍后会介绍。
  作为Crawlab的作者,我不希望王破卖瓜吹牛。笔者只是想把最好的技术选型推荐给开发者,让开发者可以根据自己的需求决定使用哪个爬虫管理平台。
  爬虫管理平台Crawlab介绍
  Crawlab是一个基于Golang的分布式爬虫管理平台,支持Python、NodeJS、Java、Go、PHP等多种编程语言和爬虫框架。
  Crawlab自今年3月上线以来,深受爬虫爱好者和开发者的好评。不少用户也表示会使用Crawlab搭建公司的爬虫平台。经过几个月的迭代,Crawlab先后推出了定时任务、数据分析、网站信息、可配置爬虫、自动字段提取、下载结果、上传爬虫等功能,使平台更加实用和全面,可以真正帮助用户解决爬虫管理的难题。现在 Github 上有近 1k 的明星相关社区,四分之一的用户表示已经将 Crawlab 应用到企业爬虫管理中。可见Crawlab受到开发者的关注和喜爱。
  解决这个问题
  Crawlab主要解决大量爬虫管理困难的问题,比如需要监控数百个网站混合的scrapy和selenium项目。同时不容易管理,命令行管理成本很高,容易出错。Crawlab 支持任意语言、任意框架的任务调度和任务监控,轻松有效监控和管理大型爬虫项目。
  接口和使用
  下面是 Crawlab 爬虫列表页面的截图。
  
  Crawlab爬虫列表
  用户只需要将爬虫上传到Crawlab配置执行命令,点击“运行”按钮即可执行爬虫任务。爬虫任务可以在任何节点上运行。从上图可以看出,Crawlab有节点管理、爬虫管理、任务管理、定时任务、用户管理等模块。
  整体结构
  以下是Crawlab的整体架构图,由五部分组成:
  1.Master Node:负责任务调度、API、爬虫部署等;
  2.Worker Node:负责执行爬虫任务;
  3.MongoDB数据库:存储节点、爬虫、任务等日常运行数据;
  4.Redis数据库:存储任务消息队列、节点心跳等信息。
  5.前端客户端:Vue应用负责前端交互,向后端请求数据。
  
  Github地址和Demo
  查看演示 DemoGithub:
  使用 Docker 部署安装 CrawlabDocker 映像
  Docker 是部署 Crawlab 最方便、最简洁的方式。其他部署方式包括直接部署,但不推荐给想要快速搭建平台的开发者。Crawlab 已经在 Dockerhub 上注册了相关镜像。开发者只需执行 docker pull tikazyq/crawlab 命令即可下载 Crawlab 镜像。
  读者可以去Dockerhub看看,Crawlab的镜像只有不到300Mb。地址:
  
  Dockerhub 页面
  安装 Docker
  要使用 Docker 部署 Crawlab,您必须首先确保已安装 Docker。请参考以下文档进行安装。
  
  安装 Docker Compose
  Docker Compose 是一个运行 Docker 集群的简单工具。非常轻巧。我们将使用 Docker Compose 一键部署 Crawlab。
  Docker 官方 网站 已经有安装 Docker Compose 的教程,点击链接查看。这里有一个简单的介绍。
  
  对于 Linux 用户,请使用以下命令进行安装。
  
# 下载 docker-compose
sudo curl -L "https://github.com/docker/comp ... pose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose`
# 将 docker-compose 变成执行文件
sudo chmod +x /usr/local/bin/docker-compose
  拉图像
  拉取镜像前需要配置镜像源。因为在国内使用原创镜像源的速度不是很快,所以需要使用DockerHub的国产加速器。请创建 /etc/docker/daemon.json 文件并输入以下内容。
  
{
"registry-mirrors": ["https://registry.docker-cn.com"]
}
  然后拉图像会快得多。当然,您也可以使用其他镜像源在线搜索。运行以下命令拉取 Crawlab 镜像。
  
docker pull tikazyq/crawlab:latest
  下图是拉取镜像时的命令行界面。
  
  码头工人拉
  开始爬行实验室
  我们将使用 Docker Compose 启动 Crawlab 及其依赖的数据库 MongoDB 和 Redis。首先我们需要修改Docker Compose的yaml配置文件docker-compose.yml。该配置文件定义了需要启动的容器服务和网络配置。这里我们使用 Crawlab 自带的 docker-compose.yml。
  
version: '3.3' # Docker Compose 的版本号(请看后续说明)
services: # 服务
master: # 服务名称
image: tikazyq/crawlab:latest # 服务对应的镜像名称
container_name: master # 服务对应的容器名称
environment: # 这里定义传入的环境变量
CRAWLAB_API_ADDRESS: "localhost:8000" # 前端调用的 API 地址默认为 localhost:8000
CRAWLAB_SERVER_MASTER: "Y" # 是否为主节点Y/N
CRAWLAB_MONGO_HOST: "mongo" # MongoDB host由于在 Docker Compose 里可以引用服务名称
CRAWLAB_REDIS_ADDRESS: "redis" # Redis host由于在 Docker Compose 里可以引用服务名称
ports: # 映射的端口
- "8080:8080" # 前端端口
- "8000:8000" # 后端端口
depends_on: # 依赖的服务
- mongo # MongoDB
- redis # Redis
worker: # 工作节点与主节点配置类似不重复写了
image: tikazyq/crawlab:latest
container_name: worker
environment:
CRAWLAB_SERVER_MASTER: "N"
CRAWLAB_MONGO_HOST: "mongo"
CRAWLAB_REDIS_ADDRESS: "redis"
depends_on:
- mongo
- redis
mongo: # MongoDB 服务名称
image: mongo:latest # MongoDB 镜像名称
restart: always # 重启策略为“总是”
ports: # 映射端口
- "27017:27017"
redis: # Redis 服务名称
image: redis:latest # Redis 镜像名称
restart: always # 重启策略为“总是”
ports: # 映射端口
- "6379:6379"
  读者可以根据自己的需求配置docker-compose.yml。特别要注意环境变量 CRAWLAB_API_ADDRESS。很多新手用户因为变量配置不正确而无法登录。在大多数情况下,您不需要进行任何配置更改。请参阅常见问题解答和详细的环境变量配置文档,以帮助根据您的环境配置 Crawlab。
  然后运行以下命令启动 Crawlab。您可以添加 -d 参数以使 Docker Compose 在后台运行。
  
docker-compose up
  运行上述命令后,Docker Compose 将拉取 MongoDB 和 Redis 映像,这可能需要几分钟时间。拉取完成后,四个服务会依次启动。您将在命令行上看到以下内容。
  
  码头工人撰写
  正常情况下应该可以看到四个服务全部启动成功,并且可以顺利打印日志。
  如果在本地机器上启动Docker Compose,可以在浏览器中输入:8080,可以看到登录界面;如果在另一台机器上启动Docker Compose,需要在浏览器中输入:8080才能看到登录界面是其他机器的IP地址(请确保本机8080端口对外开放)。
  
  登录
  初始登录用户名和密码为 admin/admin,您可以使用此用户名和密码登录。如果您的环境变量 CRAWLAB_API_ADDRESS 设置不正确,您可能会看到登录按钮在单击登录后一直在旋转而没有任何提示。此时请在 docker-compose.yml 中设置正确的 CRAWLAB_API_ADDRESS(将 localhost 替换为)并重启 docker-compose up。然后在浏览器中输入:8080。
  登录后,您将看到 Crawlab 主页。
  
  家
  本文文章主要介绍如何搭建爬虫管理平台Crawlab,所以不会详细介绍如何使用Crawlab(可以另写一篇文章文章详细介绍,有兴趣的可以付费注意力)。如果您感到困惑,请查看相关文档以了解如何使用它。
  如何将 Scrapy 等爬虫集成到 Crawlab 中
  众所周知,Scrapy 是一个非常流行的爬虫框架。其灵活的框架设计、高并发、易用性和可扩展性已被众多开发者和企业广泛采用。市面上几乎所有爬虫管理平台都支持Scrapy爬虫Crawlab也不例外,但Crawlab可以运行puppeteer、selenium等其他爬虫。下面将介绍如何在Crawlab中运行scrapy爬虫。
  Crawlab是执行爬虫的基本原理
  Crawlab的爬虫原理很简单。它实际上是一个 shell 命令。用户在爬虫中输入shell命令执行爬虫,比如scrapy crawl some_spiderCrawlab执行器会读取这个命令,直接在shell中执行。因此,每次运行爬虫任务时,都要执行一个shell命令(当然实际情况比这复杂得多,有兴趣的可以参考官方文档)。Crawlab 支持显示和导出爬虫结果,但这需要更多的工作。
  编写管道
  集成scrapy爬虫无非是将爬虫爬取的数据存储在Crawlab的数据库中,并与任务ID相关联。每次执行爬虫任务时,都会通过环境变量将任务ID传递给爬虫程序,所以我们只需要将任务ID加上结果存入数据库即可(Crawlab目前只支持MongoDB,后续会开发) MySQL、SQL Server、Postgres 等关系类型未来需要数据库的用户可以关注)。
  在 Scrapy 中,我们需要编写存储逻辑。原理图代码如下:
  
# 引入相关的库pymongo 是标准连接 MongoDB 的库
import os
from pymongo import MongoClient
# MongoDB 配置参数
MONGO_HOST = '192.168.99.100'
MONGO_PORT = 27017
MONGO_DB = 'crawlab_test'
class JuejinPipeline(object):
mongo = MongoClient(host=MONGO_HOST, port=MONGO_PORT) # mongo 连接实例
db = mongo[MONGO_DB] # 数据库实例
col_name = os.environ.get('CRAWLAB_COLLECTION') # 集合名称通过环境变量 CRAWLAB_COLLECTION 传过来
# 如果 CRAWLAB_COLLECTION 不存在则默认集合名称为 test
if not col_name:
col_name = 'test'

col = db[col_name] # 集合实例
# 每一个传入 item 会调用的函数参数分别为 item 和 spider
def process_item(self, item, spider):
item['task_id'] = os.environ.get('CRAWLAB_TASK_ID') # 将 task_id 设置为环境变量传过来的任务 ID
self.col.save(item) # 保存 item 在数据库中
return item
  同时,还需要在 items.py 中添加 task_id 字段,确保可以赋值(这个很重要)。
  上传并配置爬虫
  在运行爬虫之前,您需要将爬虫文件上传到主节点。进行如下操作:
  1.将爬虫文件打包成zip(请确保直接打包在根目录下);
  2.点击侧边栏的“爬虫”导航到爬虫列表,点击“添加爬虫”按钮选择“自定义爬虫”;
  3.点击“上传”按钮选择刚刚打包好的zip文件
  4.上传成功后,新添加的自定义爬虫会出现在爬虫列表中,所以上传成功。
  您可以点击爬虫详情中的“文件”选项卡选择一个文件来编辑文件中的代码。
  
  接下来,需要在“概览”选项卡的“执行命令”栏中输入爬虫的shell执行命令。Crawlab的Docker镜像内置了scrapy,可以直接运行scrapy爬虫。命令是scrapy crawl。点击“保存”按钮保存爬虫配置。
  运行爬虫任务
  然后是时候运行爬虫任务了。其实很简单,点击“概览”选项卡中的“运行”按钮即可启动爬虫任务。如果日志提示找不到scrapy命令,可以将scrapy改成绝对路径/usr/local/bin/scrapy,运行成功。
  任务的运行状态会显示在“任务”页面或爬虫的“概览”中,每5秒更新一次。你可以在这里查看。并且在爬虫“结果”选项卡中,您可以预览结果的详细信息并将数据导出到 CSV 文件。
  构建持续集成 (CI) 工作流
  软件开发通常是企业的自动化过程。它将经历需求、开发、部署、测试和启动的步骤。而且这个过程一般是迭代的,需要不断的更新和发布。
  以爬虫为例。您已经启动了一个爬虫。此爬虫将定期获取 网站 数据。但突然有一天,你发现无法捕获数据。你快速定位原因,发现网站版本已经修改。你需要改变爬虫的爬取规则来应对网站的修改。无论如何,您需要发布代码更新。最快的做法是直接在线更改代码。但这很危险:第一,不能测试自己更新的代码,只能通过不断调整在线代码来测试爬取是否成功;其次,如果出现问题,您以后无法记录更改,您很可能会忽略它。此更改导致了错误。您需要做的就是使用版本管理工具来管理您的爬虫代码。
  当我们更新代码时,我们需要将更新后的代码发布到在线服务器。这时候就需要自己编写部署脚本或者更方便的使用Jenkins作为持续集成管理平台。Jenkins 是一个持续集成平台,可以通过获取存储库来更新部署代码。它是一个非常实用的工具,被许多企业使用。下图是如何将 Crawlab 爬虫应用于持续集成工作流的示例。
  
  词
  在 Crawlab 中有两种创建或更新爬虫的方法:
  1.上传打包好的zip文件;
  2.通过更改主节点CRAWLAB_SPIDER_PATH目录下的爬虫文件。
  我们针对第二种方式进行持续集成。进行如下操作:
  1.使用Gitlab或者其他平台搭建代码仓库;
  2.在Jenkins中创建一个项目,并将代码源指向项目中之前创建的仓库;
  3.在Jenkins项目中编写工作流,并将发布地址指向Crawlab的CRAWLAB_SPIDER_PATH。如果是Docker,注意挂载地址到宿主机文件系统;
  4.Jenkins项目的工作可以直接写也可以使用Jenkinsfile查看相关信息;
  5.这样每次代码更新提交到代码仓库后,Jenkins都会将更新后的代码发布到Crawlab。Crawlab主节点会将爬虫代码同步到worker节点进行爬取。
  总结
  本篇文章主要介绍爬虫管理平台的定义,如何选择爬虫管理平台,重点介绍如何搭建开源的爬虫管理平台Crawlab。它还讨论了如何集成scrapy爬虫以及如何创建持续集成工作流。这篇文章文章并没有涉及很多内容,包括如何Crawlab的原理和架构细节,如何使用Crawlab,如何编写大型爬虫,如何使用Jenkins等等。这些内容可能会在其他文章上发表,感兴趣的读者请留意。此外,Crawlab 还有一些需要改进的地方,比如异常监控(零值、空值)、可配置爬虫、可视化爬取、集中日志采集等。
  相关文章

nodejs抓取动态网页(谷歌发布的一款Chromium无头的基本使用(s)模块)

网站优化优采云 发表了文章 • 0 个评论 • 57 次浏览 • 2022-01-25 10:04 • 来自相关话题

  nodejs抓取动态网页(谷歌发布的一款Chromium无头的基本使用(s)模块)
  我们可以使用 nodejs 的 http(s) 模块来请求目标 URL,也可以使用 axios 之类的第三方库来请求网页。拿到html后,使用html解析库解析成方便的js对象,然后抓取目标内容。这里推荐解析库cheerio。Cheerio 提供了类似 jQuery 的操作方式,可以轻松抓取我们想要的内容。
  // demo1
const cheerio = require('cheerio')
const axios = require('axios')
async function main() {
const resp = await axios.get('https://www.jianshu.com/')
console.log(resp.data)
}
main()
  通过节点demo1执行demo1,我们可以得到短书首页的html。
  
  图像.png
  现在让我们改进demo1的代码,使用cheerio来解析html。
  // demo1
const cheerio = require('cheerio')
const axios = require('axios')
async function main() {
const resp = await axios.get('https://www.jianshu.com/')
const $ = cheerio.load(resp.data)
const articleList = []
$('#list-container li').each((i, el) => {
articleList.push({
id: $(el).attr('id').replace('', ''),
title: unescape($(el).find('.title').html().replace(/&#x/g, '%u').replace(/;/g, '')),
href: $(el).find('.title').attr('href'),
})
})
console.log(articleList)
}
main()
  
  图像.png
  这样我们就得到了文章的id、title和link。
  采用这种方式捕获数据的优点是效率高,占用的硬件资源极少。但是这种方法也有一些缺点。例如,延迟加载页面很难爬取。因为懒加载页面的内容是由浏览器js动态加载的,这样对于我们请求的页面,由于js还没有渲染页面上的内容,我们无法正确获取页面内容,而我们的请求也未能提供页面js所需的运行环境。这时候,我们将介绍另一种爬虫。
  无头浏览器
  无头浏览器是一种在后台运行的网络浏览器程序,没有操作界面。常用的无头浏览器有PhantomJS、Selenium、Puppeteer等(还有很多很多,有兴趣的可以去看看)。
  无头浏览器常用于自动化测试、爬虫等技术。它提供了浏览器js所需的运行环境,我们可以利用它来处理延迟加载的页面。
  Puppeteer 是谷歌发布的 Chromium 无头浏览器。这里我们以 Puppeteer 为例,编写一些小 Demo。
  // demo2
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch({
// 是否为无头,无头模式下没有用户操作界面
headless: false,
defaultViewport: null
})
const page = await browser.newPage();
await page.goto('https://www.jianshu.com/u/04d11dd2f924');
}
main()
  让我们使用 node demo2 来执行:
  
  图像.png
  我们现在已经用代码打开了一个浏览器窗口。接下来我们会继续完善demo2来爬取这个页面的内容。
  // demo2
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch({
// 是否为无头,无头模式下没有用户操作界面
headless: false,
defaultViewport: null,
devtools: true,
})
const page = await browser.newPage();
await page.goto('https://www.jianshu.com/u/04d11dd2f924');
const articleList = await page.$$eval('#list-container li', els =>
els.map(el => {
const id = el.dataset.noteId
const title = el.querySelector('.title').innerText
const url = el.querySelector('.title').href
return {id, title, url}
})
)
console.log(articleList)
}
main()
  执行这段代码,我们得到以下结果:
  
  图像.png
  这是无头浏览器的基本用法。它还提供了很多方便的API,比如page.waitFor(selector, pageFun)方法,它可以在执行pageFun方法之前等待一个元素出现在页面上。还有page.addScriptTag(options)方法,可以直接在页面上使用。注入你自己的js脚本。
  在这里,再次请大家不要滥用爬虫采集的资源去攻击别人的服务器。
  两种方式的比较 查看全部

  nodejs抓取动态网页(谷歌发布的一款Chromium无头的基本使用(s)模块)
  我们可以使用 nodejs 的 http(s) 模块来请求目标 URL,也可以使用 axios 之类的第三方库来请求网页。拿到html后,使用html解析库解析成方便的js对象,然后抓取目标内容。这里推荐解析库cheerio。Cheerio 提供了类似 jQuery 的操作方式,可以轻松抓取我们想要的内容。
  // demo1
const cheerio = require('cheerio')
const axios = require('axios')
async function main() {
const resp = await axios.get('https://www.jianshu.com/')
console.log(resp.data)
}
main()
  通过节点demo1执行demo1,我们可以得到短书首页的html。
  
  图像.png
  现在让我们改进demo1的代码,使用cheerio来解析html。
  // demo1
const cheerio = require('cheerio')
const axios = require('axios')
async function main() {
const resp = await axios.get('https://www.jianshu.com/')
const $ = cheerio.load(resp.data)
const articleList = []
$('#list-container li').each((i, el) => {
articleList.push({
id: $(el).attr('id').replace('', ''),
title: unescape($(el).find('.title').html().replace(/&#x/g, '%u').replace(/;/g, '')),
href: $(el).find('.title').attr('href'),
})
})
console.log(articleList)
}
main()
  
  图像.png
  这样我们就得到了文章的id、title和link。
  采用这种方式捕获数据的优点是效率高,占用的硬件资源极少。但是这种方法也有一些缺点。例如,延迟加载页面很难爬取。因为懒加载页面的内容是由浏览器js动态加载的,这样对于我们请求的页面,由于js还没有渲染页面上的内容,我们无法正确获取页面内容,而我们的请求也未能提供页面js所需的运行环境。这时候,我们将介绍另一种爬虫。
  无头浏览器
  无头浏览器是一种在后台运行的网络浏览器程序,没有操作界面。常用的无头浏览器有PhantomJS、Selenium、Puppeteer等(还有很多很多,有兴趣的可以去看看)。
  无头浏览器常用于自动化测试、爬虫等技术。它提供了浏览器js所需的运行环境,我们可以利用它来处理延迟加载的页面。
  Puppeteer 是谷歌发布的 Chromium 无头浏览器。这里我们以 Puppeteer 为例,编写一些小 Demo。
  // demo2
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch({
// 是否为无头,无头模式下没有用户操作界面
headless: false,
defaultViewport: null
})
const page = await browser.newPage();
await page.goto('https://www.jianshu.com/u/04d11dd2f924');
}
main()
  让我们使用 node demo2 来执行:
  
  图像.png
  我们现在已经用代码打开了一个浏览器窗口。接下来我们会继续完善demo2来爬取这个页面的内容。
  // demo2
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch({
// 是否为无头,无头模式下没有用户操作界面
headless: false,
defaultViewport: null,
devtools: true,
})
const page = await browser.newPage();
await page.goto('https://www.jianshu.com/u/04d11dd2f924');
const articleList = await page.$$eval('#list-container li', els =>
els.map(el => {
const id = el.dataset.noteId
const title = el.querySelector('.title').innerText
const url = el.querySelector('.title').href
return {id, title, url}
})
)
console.log(articleList)
}
main()
  执行这段代码,我们得到以下结果:
  
  图像.png
  这是无头浏览器的基本用法。它还提供了很多方便的API,比如page.waitFor(selector, pageFun)方法,它可以在执行pageFun方法之前等待一个元素出现在页面上。还有page.addScriptTag(options)方法,可以直接在页面上使用。注入你自己的js脚本。
  在这里,再次请大家不要滥用爬虫采集的资源去攻击别人的服务器。
  两种方式的比较

nodejs抓取动态网页(爬虫爬取的流程和最终如何展示数据的地址?)

网站优化优采云 发表了文章 • 0 个评论 • 81 次浏览 • 2022-01-25 10:03 • 来自相关话题

  nodejs抓取动态网页(爬虫爬取的流程和最终如何展示数据的地址?)
  其实很早以前我做过即时理财的销售统计,不过是前端js写的。需要在首页的控制台调试面板中粘贴一段代码来执行,点这里。主要是通过定期爬取异步接口来获取数据。然后,通过一定的排序算法得到最终的数据。但这样做有以下缺点:
  0. 代码只能在浏览器窗口下运行,关闭浏览器或电脑无效
  0. 只能爬取一个页面的数据,不能整合其他页面的数据
  0. 爬取的数据无法存储在本地
  0. 上面的异步接口数据会被部分过滤,导致我们的重载算法失败
  由于最近学习了节点爬虫,所以可以在后台模拟请求,爬取页面数据。而且我已经开通了阿里云服务器,可以把代码放到云端运行。这样,1、2、3就可以解决了。4是因为我不知道这个ajax接口每三分钟更新一次,所以我们可以按照这个来安排权重,保证数据不会重复。说到爬虫,大部分人都会想到python。确实,python有Scrapy等成熟的框架,可以实现强大的爬取功能。但是,节点也有自己的优势。凭借其强大的异步特性,可以轻松实现高效的异步并发请求,节省CPU开销。其实节点爬虫比较简单。让'
  在线地址
  一、爬虫进程
  我们的最终目标是爬取利马财经的每日销售额,并了解哪些产品被销售,哪些用户在什么时间购买了每个产品。首先介绍爬取的主要步骤:
  1. 结构分析
  如果我们要爬取页面的数据,第一步当然是分析页面的结构,要爬哪些页面,页面的结构是什么,是否登录;是否有ajax接口,返回什么样的数据等。
  2. 数据抓取
  分析好爬取哪些页面和ajax后,就需要爬取数据了。现在网页的数据大致分为同步页面和ajax接口。要获取同步页面数据,我们需要先分析网页的结构。Python取数据一般通过正则表达式匹配来获取需要的数据;node有一个cheerio工具,可以将获取到的页面内容转换成jquery对象,然后可以使用jquery强大的dom API来获取node相关的数据。其实看源码的话,这些API的本质就是正则匹配。Ajax接口数据一般为json格式,处理起来比较简单。
  3. 数据存储
  采集到数据后,会进行简单的筛选,然后将需要的数据保存下来,供后续分析处理。当然我们可以将数据存储在 MySQL 和 Mongodb 等数据库中。这里,为了方便,我们直接使用文件存储。
  4. 数据分析
  因为我们最终要展示数据,所以需要对原创数据按照一定的维度进行处理和分析,然后返回给客户端。这个过程可以在存储的时候进行处理,也可以在显示的时候,前端发送请求,后台取回存储的数据进行处理。这取决于我们希望如何显示数据。
  5. 结果显示
  做了这么多工作,一点显示输出都没有,怎么舍得?这又回到了我们的老业务,前端展示页面大家应该都很熟悉了。数据展示更加直观,方便我们进行统计分析。
  二、爬虫常用库介绍1. Superagent
  Superagent 是一个轻量级的 http 库。是nodejs中一个非常方便的客户端请求代理模块。当我们需要进行get、post、head等网络请求的时候,试试吧。
  2. 谢里奥
  Cheerio 可以理解为 jquery 的 Node.js 版本,用于从带有 css 选择器的网页中获取数据,方式与 jquery 完全相同。
  3. 异步
  Async 是一个流程控制工具包,提供了直接且强大的异步函数 mapLimit(arr, limit, iterator, callback)。我们主要使用这种方法。您可以在官方网站上查看API。
  4. arr-del
  arr-del 是我自己写的一个删除数组元素方法的工具。可以通过传入一个由要删除的数组元素的索引组成的数组来执行一次性删除。
  5. arr-排序
  arr-sort 是我自己编写的用于对数组进行排序的工具。可以按一个或多个属性排序,支持嵌套属性。并且可以指定每个条件下的排序方向,并支持传入比较函数。
  三、页面结构分析
  让我们重复我们首先抓取的想法。利马理财在线的产品主要是普通的利马银行(光大银行最新的理财产品,手续复杂,前期投入大,基本没人买,这里不计)。定期我们可以爬取财务页面的ajax接口:. (更新:近期定期缺货,可能看不到数据)数据如下图所示:
  
  这包括在线销售的所有常规产品。ajax数据只有产品本身相关的信息,比如产品id、募集金额、当前销售额、年化收益率、投资天数等,并没有哪些用户购买了产品的信息。. 所以我们需要到它的商品详情页面去爬取id参数,比如Lima Jucai-December issue HLB01239511。详情页有一栏投资记录,里面收录了我们需要的信息,如下图所示:
  
  但是详情页需要我们登录才能查看,这就需要我们带着cookie来访问,而且cookie的有效期是有限的。如何让我们的cookie保持登录状态?请看下文。
  其实Lima Vault也有类似的ajax接口:,但是里面的相关数据都写死了,没有意义。而且,金库的详情页没有投资记录信息。这就需要我们爬取开头提到的首页的ajax接口:. 但是后来发现这个接口每三分钟更新一次,也就是说后台每三分钟向服务器请求一次数据。并且一次有10条数据,所以如果三分钟内购买产品的记录数超过10条,数据将被省略。没有办法绕过它,所以金库统计数据会立即低于真实数据。
  四、爬虫代码分析1.获取登录cookie
  因为商品详情页面需要登录,所以我们需要先获取登录cookie。getCookie 方法如下:
  function getCookie() {
superagent.post('https://www.lmlc.com/user/s/web/logon')
.type('form')
.send({
phone: phone,
password: password,
productCode: "LMLC",
origin: "PC"
})
.end(function(err, res) {
if (err) {
handleErr(err.message);
return;
}
cookie = res.header['set-cookie']; //从response中得到cookie
emitter.emit("setCookeie");
})
}
  手机和密码参数是命令行传入的,就是你用手机号登录的账号和密码。我们使用superagent模拟即时财务管理登录界面的请求:. 传入相应的参数,在回调中我们获取到header的set-cookie信息,并发出setCookieie事件。因为我们设置了监听事件:emitter.on("setCookie", requestData),一旦获取到cookie,我们就会执行requestData方法。
  2. 财务页面ajax的爬取
  requestData 方法的代码如下:
<p>function requestData() {
superagent.get('https://www.lmlc.com/web/product/product_list?pageSize=100&pageNo=1&type=0')
.end(function(err,pres){
// 常规的错误处理
if (err) {
handleErr(err.message);
return;
}
// 在这里清空数据,避免一个文件被同时写入
if(clearProd){
fs.writeFileSync('data/prod.json', JSON.stringify([]));
clearProd = false;
}
let addData = JSON.parse(pres.text).data;
let formatedAddData = formatData(addData.result);
let pageUrls = [];
if(addData.totalPage > 1){
handleErr('产品个数超过100个!');
return;
}
for(let i=0,len=addData.result.length; i 查看全部

  nodejs抓取动态网页(爬虫爬取的流程和最终如何展示数据的地址?)
  其实很早以前我做过即时理财的销售统计,不过是前端js写的。需要在首页的控制台调试面板中粘贴一段代码来执行,点这里。主要是通过定期爬取异步接口来获取数据。然后,通过一定的排序算法得到最终的数据。但这样做有以下缺点:
  0. 代码只能在浏览器窗口下运行,关闭浏览器或电脑无效
  0. 只能爬取一个页面的数据,不能整合其他页面的数据
  0. 爬取的数据无法存储在本地
  0. 上面的异步接口数据会被部分过滤,导致我们的重载算法失败
  由于最近学习了节点爬虫,所以可以在后台模拟请求,爬取页面数据。而且我已经开通了阿里云服务器,可以把代码放到云端运行。这样,1、2、3就可以解决了。4是因为我不知道这个ajax接口每三分钟更新一次,所以我们可以按照这个来安排权重,保证数据不会重复。说到爬虫,大部分人都会想到python。确实,python有Scrapy等成熟的框架,可以实现强大的爬取功能。但是,节点也有自己的优势。凭借其强大的异步特性,可以轻松实现高效的异步并发请求,节省CPU开销。其实节点爬虫比较简单。让'
  在线地址
  一、爬虫进程
  我们的最终目标是爬取利马财经的每日销售额,并了解哪些产品被销售,哪些用户在什么时间购买了每个产品。首先介绍爬取的主要步骤:
  1. 结构分析
  如果我们要爬取页面的数据,第一步当然是分析页面的结构,要爬哪些页面,页面的结构是什么,是否登录;是否有ajax接口,返回什么样的数据等。
  2. 数据抓取
  分析好爬取哪些页面和ajax后,就需要爬取数据了。现在网页的数据大致分为同步页面和ajax接口。要获取同步页面数据,我们需要先分析网页的结构。Python取数据一般通过正则表达式匹配来获取需要的数据;node有一个cheerio工具,可以将获取到的页面内容转换成jquery对象,然后可以使用jquery强大的dom API来获取node相关的数据。其实看源码的话,这些API的本质就是正则匹配。Ajax接口数据一般为json格式,处理起来比较简单。
  3. 数据存储
  采集到数据后,会进行简单的筛选,然后将需要的数据保存下来,供后续分析处理。当然我们可以将数据存储在 MySQL 和 Mongodb 等数据库中。这里,为了方便,我们直接使用文件存储。
  4. 数据分析
  因为我们最终要展示数据,所以需要对原创数据按照一定的维度进行处理和分析,然后返回给客户端。这个过程可以在存储的时候进行处理,也可以在显示的时候,前端发送请求,后台取回存储的数据进行处理。这取决于我们希望如何显示数据。
  5. 结果显示
  做了这么多工作,一点显示输出都没有,怎么舍得?这又回到了我们的老业务,前端展示页面大家应该都很熟悉了。数据展示更加直观,方便我们进行统计分析。
  二、爬虫常用库介绍1. Superagent
  Superagent 是一个轻量级的 http 库。是nodejs中一个非常方便的客户端请求代理模块。当我们需要进行get、post、head等网络请求的时候,试试吧。
  2. 谢里奥
  Cheerio 可以理解为 jquery 的 Node.js 版本,用于从带有 css 选择器的网页中获取数据,方式与 jquery 完全相同。
  3. 异步
  Async 是一个流程控制工具包,提供了直接且强大的异步函数 mapLimit(arr, limit, iterator, callback)。我们主要使用这种方法。您可以在官方网站上查看API。
  4. arr-del
  arr-del 是我自己写的一个删除数组元素方法的工具。可以通过传入一个由要删除的数组元素的索引组成的数组来执行一次性删除。
  5. arr-排序
  arr-sort 是我自己编写的用于对数组进行排序的工具。可以按一个或多个属性排序,支持嵌套属性。并且可以指定每个条件下的排序方向,并支持传入比较函数。
  三、页面结构分析
  让我们重复我们首先抓取的想法。利马理财在线的产品主要是普通的利马银行(光大银行最新的理财产品,手续复杂,前期投入大,基本没人买,这里不计)。定期我们可以爬取财务页面的ajax接口:. (更新:近期定期缺货,可能看不到数据)数据如下图所示:
  
  这包括在线销售的所有常规产品。ajax数据只有产品本身相关的信息,比如产品id、募集金额、当前销售额、年化收益率、投资天数等,并没有哪些用户购买了产品的信息。. 所以我们需要到它的商品详情页面去爬取id参数,比如Lima Jucai-December issue HLB01239511。详情页有一栏投资记录,里面收录了我们需要的信息,如下图所示:
  
  但是详情页需要我们登录才能查看,这就需要我们带着cookie来访问,而且cookie的有效期是有限的。如何让我们的cookie保持登录状态?请看下文。
  其实Lima Vault也有类似的ajax接口:,但是里面的相关数据都写死了,没有意义。而且,金库的详情页没有投资记录信息。这就需要我们爬取开头提到的首页的ajax接口:. 但是后来发现这个接口每三分钟更新一次,也就是说后台每三分钟向服务器请求一次数据。并且一次有10条数据,所以如果三分钟内购买产品的记录数超过10条,数据将被省略。没有办法绕过它,所以金库统计数据会立即低于真实数据。
  四、爬虫代码分析1.获取登录cookie
  因为商品详情页面需要登录,所以我们需要先获取登录cookie。getCookie 方法如下:
  function getCookie() {
superagent.post('https://www.lmlc.com/user/s/web/logon')
.type('form')
.send({
phone: phone,
password: password,
productCode: "LMLC",
origin: "PC"
})
.end(function(err, res) {
if (err) {
handleErr(err.message);
return;
}
cookie = res.header['set-cookie']; //从response中得到cookie
emitter.emit("setCookeie");
})
}
  手机和密码参数是命令行传入的,就是你用手机号登录的账号和密码。我们使用superagent模拟即时财务管理登录界面的请求:. 传入相应的参数,在回调中我们获取到header的set-cookie信息,并发出setCookieie事件。因为我们设置了监听事件:emitter.on("setCookie", requestData),一旦获取到cookie,我们就会执行requestData方法。
  2. 财务页面ajax的爬取
  requestData 方法的代码如下:
<p>function requestData() {
superagent.get('https://www.lmlc.com/web/product/product_list?pageSize=100&pageNo=1&type=0')
.end(function(err,pres){
// 常规的错误处理
if (err) {
handleErr(err.message);
return;
}
// 在这里清空数据,避免一个文件被同时写入
if(clearProd){
fs.writeFileSync('data/prod.json', JSON.stringify([]));
clearProd = false;
}
let addData = JSON.parse(pres.text).data;
let formatedAddData = formatData(addData.result);
let pageUrls = [];
if(addData.totalPage > 1){
handleErr('产品个数超过100个!');
return;
}
for(let i=0,len=addData.result.length; i

nodejs抓取动态网页( 响应设计分为数据结构数据结构设计和响应码设计(组图) )

网站优化优采云 发表了文章 • 0 个评论 • 72 次浏览 • 2022-02-27 23:17 • 来自相关话题

  nodejs抓取动态网页(
响应设计分为数据结构数据结构设计和响应码设计(组图)
)
   分页获取部分博客
GET /api/blogs?page=1&size=15&sort=time

- 按层次组织资源:

// 获取某个博客下面的所有评论

GET /api/blogs/143/comments

层次化的设计有可能会让URL太长,不便于阅读,比如:

// 获取某个博客下面的某个评论的某个回复

GET /api/blogs/123/comments/12343/replys/2231

所以,我们也可以使用扁平化的方式设计所有的资源:

// 获取某个博客的所有评论,使用查询参数来限定条件

GET /api/comments?blogId=123
// 获取某个评论的所有回复

GET /api/replys?commentId=123
  响应式设计
  响应式设计分为数据结构设计和响应代码设计。
  前后分离
  在以前的时代,当用户请求一个网页时,服务器通过JSP技术或者其他模板引擎技术动态渲染,然后返回给用户。它们看起来像这样:
  
  这样做的缺点是后端和前端是一体的,即使前端代码不是后端人员编写的,双方仍然需要进行必要的交流和沟通,减少了开发效率;并且前后端一起部署,灵活性差;而静态资源的后台处理性能较差,不应该处理静态资源压缩、缓存等问题,应该交给代理服务器,比如Nginx。
  前后端分离最极端的方式是:前后端独立编写,独立部署,通过API接口交互。它们看起来像这样:
  
  项目实战:电商管理后台
  目标是为商家和管理员实现一个收录账户模块、权限模块、商品分类模块和订单模块的后台系统。
  项目采用前后端分离的开发方式。它只实现 API 功能,不提供接口。
  项目结构搭建及实施过程
  在之前的TODO项目的基础上,增加了一个中间件包和一个测试包。前者用于存放中间件包,因为权限管理需要通过中间件来实现;后者是一个与测试相关的包。
  各个模块的实现顺序为:模型层--&gt;服务层--&gt;路由器层。单元测试:服务层编写脚本测试;路由器层使用邮递员测试。配置文件的环境切换
  开发环境和生产环境的配置一般是不同的,比如端口配置和数据库配置。一般我们通过环境变量NODE_ENV来区分。为了动态切换配置,需要根据NODE_ENV的当前值加载不同的配置对象。
  这样做的方法是:
  创建config目录,创建dev.js和prod.js存放对应的配置信息,编写index.js实现动态配置切换的逻辑。
  目录结构:
  
  写入入口文件
  添加依赖项:
  npm i body-parser express express-async-errors mongoose morgan
  编写 app.js 和 db.js 文件。
  应用程序.js
   //引入dib
require(&#39;./db&#39;)

const config = require(&#39;./config&#39;);
const morgan = require(&#39;morgan&#39;)
const bodyParser = require(&#39;body-parser&#39;);
const express = require(&#39;express&#39;)
// 引入异常捕获处理
require(&#39;express-async-errors&#39;);

const app = express();

//注册中间件
// log中间件
app.use(morgan(&#39;combined&#39;));

//注册自定义的中间件

// 注册body-parser中间件
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));
// parse application/json
app.use(bodyParser.json());

// 注册路由
app.use("/users", require(&#39;./router/account&#39;));

// 注册异常处理中间件
app.use((err, req, res, next)=>{
res.fail(err.toString())
});

//启动
app.listen(config.PORT);
  数据库.js
   const config = require(&#39;./config&#39;);
const mongoose = require(&#39;mongoose&#39;);
mongoose.connect(`mongodb://127.0.0.1/${config.DB}`);

const db = mongoose.connection;

db.on(&#39;error&#39;, (err)=>{
console.log(err);
});

db.on("open", ()=>{
console.log("mongodb connect successfully!");
});

  账户模块
  先写模型,再写服务,最后写路由器;最后,测试服务和路由器。
  REST 中间件
  为了方便返回 REST 结果,我们编写了一个 res_md.js 中间件,用于为每个 res 对象安装 2 个方法。注意中间件的注册顺序要放在前面。代码显示如下:
   module.exports = function (req, res, next) {
res.success = (data = null) =>{
res.send({
code: 0,
msg: "操作成功",
data: data
})
};
res.fail = (msg)=>{
res.send({
code: -1,
msg: msg
})
};

next();
};
  账户模型
   const mongoose = require(&#39;mongoose&#39;)
const schema = new mongoose.Schema({
username: {
type: String,
required: [true, "用户名不能缺少"]
},
password: {
type: String,
required: [true, "密码不能缺少"]
},
age: {
type: Number,
min: [0, "年龄不能小于0"],
max: [120, "年龄不能大于120"]
},
role: {
type: Number,
default: 0 // 0是商家, 10086是管理员
},
created:{
type: Date,
default: Date.now()
}
});

module.exports = mongoose.model(&#39;user&#39;, schema);
  账户服务
   const User = require(&#39;../model/user&#39;);
const config = require(&#39;../config&#39;)
const crypto = require(&#39;lxj-crypto&#39;)

/**
* 根据用户名获取某个用户
* @param username
* @returns {Promise}
*/
async function getUserByUsername(username) {
return await User.findOne({username: username}).select("-__v")
}


async function checkCanInsertUser(user) {
//检查密码是否为空
if(!user.password || user.password.length===0){
throw Error("密码不能为空")
}
//检查用户是否已经存在
let res = await getUserByUsername(user.username);
if(res){
throw Error("用户名已经存在")
}
}

/**
* 添加普通用户
* @param user
* @returns {Promise}
*/
async function addUser(user) {
await checkCanInsertUser(user);

user.role = 0;
user.created = Date.now();

//对密码进行加密,加密的方式:使用username作为随机数对password进行哈希
user.password = crypto.md5Hmac(user.password, user.username)
await User.create(user)
}

async function deleteUser(id) {
let res = await User.deleteOne({_id:id});
if(!res || res.n===0){
throw Error("删除用户失败")
}
}

/**
* 登录的方法
* @param user
* @returns token
*/
async function login(user) {
// 1. 对密码进行加密
user.password = crypto.md5Hmac(user.password, user.username)
// 2. 进行查询
let res = await User.findOne({username:user.username, password: user.password});
if(!res){
throw Error("用户名或者密码错误")
}

// 说明用户名和密码验证成功,需要生产token返回给客户端,以后客户端的header中带上这个token
// token 生产方法:用aes进行对指定的data加密
let tokenData = {
username: user.username,
expire: Date.now() + config.TokenDuration
};
let token = crypto.aesEncrypt(JSON.stringify(tokenData), config.TokenKey);
return token
}

module.exports = {
getUserByUsername,
addUser,
deleteUser,
login
};

  帐号路由器
   let router = require("express").Router();
let accountService = require(&#39;../service/accout&#39;)


router.get("/:username", async (req, res)=>{
let user = await accountService.getUserByUsername(req.params.username);
res.success(user);
});

// 登录
router.post("/login", async (req, res)=>{
let token = await accountService.login(req.body);
res.success({token});
});

// 注册
router.post("/register", async (req, res)=>{
await accountService.register(req.body)
res.success()
});

router.delete("/:id", async (req, res)=>{
await accountService.deleteUser(req.params.id)
res.success()
});

module.exports = router;


``` 查看全部

  nodejs抓取动态网页(
响应设计分为数据结构数据结构设计和响应码设计(组图)
)
   分页获取部分博客
GET /api/blogs?page=1&size=15&sort=time

- 按层次组织资源:

// 获取某个博客下面的所有评论

GET /api/blogs/143/comments

层次化的设计有可能会让URL太长,不便于阅读,比如:

// 获取某个博客下面的某个评论的某个回复

GET /api/blogs/123/comments/12343/replys/2231

所以,我们也可以使用扁平化的方式设计所有的资源:

// 获取某个博客的所有评论,使用查询参数来限定条件

GET /api/comments?blogId=123
// 获取某个评论的所有回复

GET /api/replys?commentId=123
  响应式设计
  响应式设计分为数据结构设计和响应代码设计。
  前后分离
  在以前的时代,当用户请求一个网页时,服务器通过JSP技术或者其他模板引擎技术动态渲染,然后返回给用户。它们看起来像这样:
  
  这样做的缺点是后端和前端是一体的,即使前端代码不是后端人员编写的,双方仍然需要进行必要的交流和沟通,减少了开发效率;并且前后端一起部署,灵活性差;而静态资源的后台处理性能较差,不应该处理静态资源压缩、缓存等问题,应该交给代理服务器,比如Nginx。
  前后端分离最极端的方式是:前后端独立编写,独立部署,通过API接口交互。它们看起来像这样:
  
  项目实战:电商管理后台
  目标是为商家和管理员实现一个收录账户模块、权限模块、商品分类模块和订单模块的后台系统。
  项目采用前后端分离的开发方式。它只实现 API 功能,不提供接口。
  项目结构搭建及实施过程
  在之前的TODO项目的基础上,增加了一个中间件包和一个测试包。前者用于存放中间件包,因为权限管理需要通过中间件来实现;后者是一个与测试相关的包。
  各个模块的实现顺序为:模型层--&gt;服务层--&gt;路由器层。单元测试:服务层编写脚本测试;路由器层使用邮递员测试。配置文件的环境切换
  开发环境和生产环境的配置一般是不同的,比如端口配置和数据库配置。一般我们通过环境变量NODE_ENV来区分。为了动态切换配置,需要根据NODE_ENV的当前值加载不同的配置对象。
  这样做的方法是:
  创建config目录,创建dev.js和prod.js存放对应的配置信息,编写index.js实现动态配置切换的逻辑。
  目录结构:
  
  写入入口文件
  添加依赖项:
  npm i body-parser express express-async-errors mongoose morgan
  编写 app.js 和 db.js 文件。
  应用程序.js
   //引入dib
require(&#39;./db&#39;)

const config = require(&#39;./config&#39;);
const morgan = require(&#39;morgan&#39;)
const bodyParser = require(&#39;body-parser&#39;);
const express = require(&#39;express&#39;)
// 引入异常捕获处理
require(&#39;express-async-errors&#39;);

const app = express();

//注册中间件
// log中间件
app.use(morgan(&#39;combined&#39;));

//注册自定义的中间件

// 注册body-parser中间件
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));
// parse application/json
app.use(bodyParser.json());

// 注册路由
app.use("/users", require(&#39;./router/account&#39;));

// 注册异常处理中间件
app.use((err, req, res, next)=>{
res.fail(err.toString())
});

//启动
app.listen(config.PORT);
  数据库.js
   const config = require(&#39;./config&#39;);
const mongoose = require(&#39;mongoose&#39;);
mongoose.connect(`mongodb://127.0.0.1/${config.DB}`);

const db = mongoose.connection;

db.on(&#39;error&#39;, (err)=>{
console.log(err);
});

db.on("open", ()=>{
console.log("mongodb connect successfully!");
});

  账户模块
  先写模型,再写服务,最后写路由器;最后,测试服务和路由器。
  REST 中间件
  为了方便返回 REST 结果,我们编写了一个 res_md.js 中间件,用于为每个 res 对象安装 2 个方法。注意中间件的注册顺序要放在前面。代码显示如下:
   module.exports = function (req, res, next) {
res.success = (data = null) =>{
res.send({
code: 0,
msg: "操作成功",
data: data
})
};
res.fail = (msg)=>{
res.send({
code: -1,
msg: msg
})
};

next();
};
  账户模型
   const mongoose = require(&#39;mongoose&#39;)
const schema = new mongoose.Schema({
username: {
type: String,
required: [true, "用户名不能缺少"]
},
password: {
type: String,
required: [true, "密码不能缺少"]
},
age: {
type: Number,
min: [0, "年龄不能小于0"],
max: [120, "年龄不能大于120"]
},
role: {
type: Number,
default: 0 // 0是商家, 10086是管理员
},
created:{
type: Date,
default: Date.now()
}
});

module.exports = mongoose.model(&#39;user&#39;, schema);
  账户服务
   const User = require(&#39;../model/user&#39;);
const config = require(&#39;../config&#39;)
const crypto = require(&#39;lxj-crypto&#39;)

/**
* 根据用户名获取某个用户
* @param username
* @returns {Promise}
*/
async function getUserByUsername(username) {
return await User.findOne({username: username}).select("-__v")
}


async function checkCanInsertUser(user) {
//检查密码是否为空
if(!user.password || user.password.length===0){
throw Error("密码不能为空")
}
//检查用户是否已经存在
let res = await getUserByUsername(user.username);
if(res){
throw Error("用户名已经存在")
}
}

/**
* 添加普通用户
* @param user
* @returns {Promise}
*/
async function addUser(user) {
await checkCanInsertUser(user);

user.role = 0;
user.created = Date.now();

//对密码进行加密,加密的方式:使用username作为随机数对password进行哈希
user.password = crypto.md5Hmac(user.password, user.username)
await User.create(user)
}

async function deleteUser(id) {
let res = await User.deleteOne({_id:id});
if(!res || res.n===0){
throw Error("删除用户失败")
}
}

/**
* 登录的方法
* @param user
* @returns token
*/
async function login(user) {
// 1. 对密码进行加密
user.password = crypto.md5Hmac(user.password, user.username)
// 2. 进行查询
let res = await User.findOne({username:user.username, password: user.password});
if(!res){
throw Error("用户名或者密码错误")
}

// 说明用户名和密码验证成功,需要生产token返回给客户端,以后客户端的header中带上这个token
// token 生产方法:用aes进行对指定的data加密
let tokenData = {
username: user.username,
expire: Date.now() + config.TokenDuration
};
let token = crypto.aesEncrypt(JSON.stringify(tokenData), config.TokenKey);
return token
}

module.exports = {
getUserByUsername,
addUser,
deleteUser,
login
};

  帐号路由器
   let router = require("express").Router();
let accountService = require(&#39;../service/accout&#39;)


router.get("/:username", async (req, res)=>{
let user = await accountService.getUserByUsername(req.params.username);
res.success(user);
});

// 登录
router.post("/login", async (req, res)=>{
let token = await accountService.login(req.body);
res.success({token});
});

// 注册
router.post("/register", async (req, res)=>{
await accountService.register(req.body)
res.success()
});

router.delete("/:id", async (req, res)=>{
await accountService.deleteUser(req.params.id)
res.success()
});

module.exports = router;


```

nodejs抓取动态网页(Nodejs将JavaScript语言带到了服务器端(图)网络爬虫)

网站优化优采云 发表了文章 • 0 个评论 • 60 次浏览 • 2022-02-22 10:08 • 来自相关话题

  nodejs抓取动态网页(Nodejs将JavaScript语言带到了服务器端(图)网络爬虫)
  Nodejs 将 JavaScript 语言带到了服务器端。Nodejs作为js主要用户的前端,已经获得了服务器端的开发能力,但是除了用express搭建博客,还能做哪些好玩的项目呢?你为什么不只是一个网络爬虫。据说互联网上90%以上的流量是由爬虫贡献的。不知道是真是假,但至少证明了爬虫被广泛使用,尤其是在电商比价领域。它做的是爬虫中的弱鸡,只实现基本功能,见底部代码。
  运行结果:
  
  下面简要介绍实现过程。
  先选择爬取对象,这里也是门槛比较低的新闻站,因为我每天早上都要去逛街,比较熟悉,为什么新闻站比较简单,因为通常是这种类型的网站不需要用户登录,所有爬虫请求都很容易伪造。
  爬虫的基本思路是获取页面 -&gt; 构造信息选择器 -&gt; 分析页面链接 -&gt; 获取相邻页面 -&gt; 循环的第一步,对于新闻站来说,我们想要得到什么就是新闻内容,向页面请求html代码后,只要从页面中找到内容容器元素,就很容易得到新闻内容。下一步是获取相邻页面的地址。cnBeta新闻有上一页和下一页的链接,但仔细看会发现其实是用的。js是动态生成的。这里我们需要了解一下js是如何获取链接的。得到链接后,我们会继续获取下一条新闻的html代码,而且会一次又一次的开始。
  期间肯定不会一帆风顺。比如我遇到了301跳转。幸运的是,从请求头中很容易找到跳转目标。找到301跳转后,立即放弃请求,重新请求跳转地址。
  另外,爬虫不能无限爬取,否则很容易被IP屏蔽,需要设置爬取次数限制。最好再设置一个爬取间隔,不过这个只是用于学习Nodejs,不会有爬取,所以不需要再设置一个爬取间隔。
  详细流程分析见原文地址:%E7%88%AC%E8%99%AB%E5%AE%9E%E8%B7%B5%E5%B0%8F%E8%AE%B0/。
  项目源代码:Github。 查看全部

  nodejs抓取动态网页(Nodejs将JavaScript语言带到了服务器端(图)网络爬虫)
  Nodejs 将 JavaScript 语言带到了服务器端。Nodejs作为js主要用户的前端,已经获得了服务器端的开发能力,但是除了用express搭建博客,还能做哪些好玩的项目呢?你为什么不只是一个网络爬虫。据说互联网上90%以上的流量是由爬虫贡献的。不知道是真是假,但至少证明了爬虫被广泛使用,尤其是在电商比价领域。它做的是爬虫中的弱鸡,只实现基本功能,见底部代码。
  运行结果:
  
  下面简要介绍实现过程。
  先选择爬取对象,这里也是门槛比较低的新闻站,因为我每天早上都要去逛街,比较熟悉,为什么新闻站比较简单,因为通常是这种类型的网站不需要用户登录,所有爬虫请求都很容易伪造。
  爬虫的基本思路是获取页面 -&gt; 构造信息选择器 -&gt; 分析页面链接 -&gt; 获取相邻页面 -&gt; 循环的第一步,对于新闻站来说,我们想要得到什么就是新闻内容,向页面请求html代码后,只要从页面中找到内容容器元素,就很容易得到新闻内容。下一步是获取相邻页面的地址。cnBeta新闻有上一页和下一页的链接,但仔细看会发现其实是用的。js是动态生成的。这里我们需要了解一下js是如何获取链接的。得到链接后,我们会继续获取下一条新闻的html代码,而且会一次又一次的开始。
  期间肯定不会一帆风顺。比如我遇到了301跳转。幸运的是,从请求头中很容易找到跳转目标。找到301跳转后,立即放弃请求,重新请求跳转地址。
  另外,爬虫不能无限爬取,否则很容易被IP屏蔽,需要设置爬取次数限制。最好再设置一个爬取间隔,不过这个只是用于学习Nodejs,不会有爬取,所以不需要再设置一个爬取间隔。
  详细流程分析见原文地址:%E7%88%AC%E8%99%AB%E5%AE%9E%E8%B7%B5%E5%B0%8F%E8%AE%B0/。
  项目源代码:Github。

nodejs抓取动态网页(1.在项目文件夹安装两个必须的依赖包npminstallsupe是什么)

网站优化优采云 发表了文章 • 0 个评论 • 73 次浏览 • 2022-02-16 02:24 • 来自相关话题

  nodejs抓取动态网页(1.在项目文件夹安装两个必须的依赖包npminstallsupe是什么)
  在开始之前,请确保您已经安装了 Node.js 环境。如果没有安装,可以到Somei Technology下载安装。
  1.在项目文件夹中安装两个需要的依赖
  npminstallsupe
  在开始之前,请确保您已经安装了 Node.js 环境。如果没有安装,可以到Somei Technology下载安装。
  1.在项目文件夹中安装两个需要的依赖
  
npm install superagent --save-dev
  superagent是一个轻量级的渐进式ajax api,可读性好,学习曲线低,内部依赖nodejs原生请求api,适用于nodejs环境
  
npm install cheerio --save-dev
  Cheerio 是一个用于 nodejs 的爬虫页面模块。它是专门为服务器定制的一个快速、灵活且可实现的 jQuery 核心实现。适用于各种网络爬虫程序。等效于 node.js 中的 jQuery
  2.新建一个 crawler.js 文件
  
//导入依赖包
const http = require("http");
const path = require("path");
const url = require("url");
const fs = require("fs");
const superagent = require("superagent");
const cheerio = require("cheerio");
  3.获取 Boos 直接就业数据
  
superagent
.get("https://www.zhipin.com/job_det ... 6quot;)
.end((error,response)=>{
//获取页面文档数据
var content = response.text;
//cheerio也就是nodejs下的jQuery 将整个文档包装成一个集合,定义一个变量$接收
var $ = cheerio.load(content);
//定义一个空数组,用来接收数据
var result=[];
//分析文档结构 先获取每个li 再遍历里面的内容(此时每个li里面就存放着我们想要获取的数据)
$(".job-list li .job-primary").each((index,value)=>{
//地址和类型为一行显示,需要用到字符串截取
//地址
let address=$(value).find(".info-primary").children().eq(1).html();
//类型
let type=$(value).find(".info-company p").html();
//解码
address=unescape(address.replace(/&#x/g,'%u').replace(/;/g,''));
type=unescape(type.replace(/&#x/g,'%u').replace(/;/g,''))
//字符串截取
let addressArr=address.split('');
let typeArr=type.split('');
//将获取的数据以对象的形式添加到数组中
result.push({
title:$(value).find(".name .job-title").text(),
money:$(value).find(".name .red").text(),
address:addressArr,
company:$(value).find(".info-company a").text(),
type:typeArr,
position:$(value).find(".info-publis .name").text(),
txImg:$(value).find(".info-publis img").attr("src"),
time:$(value).find(".info-publis p").text()
});
// console.log(typeof $(value).find(".info-primary").children().eq(1).html());
});
//将数组转换成字符串
result=JSON.stringify(result);
//将数组输出到json文件里 刷新目录 即可看到当前文件夹多出一个boss.json文件(打开boss.json文件,ctrl+A全选之后 ctrl+K,再Ctrl+F即可将json文件自动排版)
fs.writeFile("boss.json",result,"utf-8",(error)=>{
//监听错误,如正常输出,则打印null
if(error==null){
console.log("恭喜您,数据爬取成功!请打开json文件,先Ctrl+A,再Ctrl+K,最后Ctrl+F格式化后查看json文件(仅限Visual Studio Code编辑器)");
}
});
});  
  总结
  以上就是小编介绍的爬虫爬取数据的Nodejs实现。我希望它对你有帮助。有任何问题请给我留言,小编会及时回复你的。还要感谢大家对染美科技的支持网站! 查看全部

  nodejs抓取动态网页(1.在项目文件夹安装两个必须的依赖包npminstallsupe是什么)
  在开始之前,请确保您已经安装了 Node.js 环境。如果没有安装,可以到Somei Technology下载安装。
  1.在项目文件夹中安装两个需要的依赖
  npminstallsupe
  在开始之前,请确保您已经安装了 Node.js 环境。如果没有安装,可以到Somei Technology下载安装。
  1.在项目文件夹中安装两个需要的依赖
  
npm install superagent --save-dev
  superagent是一个轻量级的渐进式ajax api,可读性好,学习曲线低,内部依赖nodejs原生请求api,适用于nodejs环境
  
npm install cheerio --save-dev
  Cheerio 是一个用于 nodejs 的爬虫页面模块。它是专门为服务器定制的一个快速、灵活且可实现的 jQuery 核心实现。适用于各种网络爬虫程序。等效于 node.js 中的 jQuery
  2.新建一个 crawler.js 文件
  
//导入依赖包
const http = require("http");
const path = require("path");
const url = require("url");
const fs = require("fs");
const superagent = require("superagent");
const cheerio = require("cheerio");
  3.获取 Boos 直接就业数据
  
superagent
.get("https://www.zhipin.com/job_det ... 6quot;)
.end((error,response)=>{
//获取页面文档数据
var content = response.text;
//cheerio也就是nodejs下的jQuery 将整个文档包装成一个集合,定义一个变量$接收
var $ = cheerio.load(content);
//定义一个空数组,用来接收数据
var result=[];
//分析文档结构 先获取每个li 再遍历里面的内容(此时每个li里面就存放着我们想要获取的数据)
$(".job-list li .job-primary").each((index,value)=>{
//地址和类型为一行显示,需要用到字符串截取
//地址
let address=$(value).find(".info-primary").children().eq(1).html();
//类型
let type=$(value).find(".info-company p").html();
//解码
address=unescape(address.replace(/&#x/g,'%u').replace(/;/g,''));
type=unescape(type.replace(/&#x/g,'%u').replace(/;/g,''))
//字符串截取
let addressArr=address.split('');
let typeArr=type.split('');
//将获取的数据以对象的形式添加到数组中
result.push({
title:$(value).find(".name .job-title").text(),
money:$(value).find(".name .red").text(),
address:addressArr,
company:$(value).find(".info-company a").text(),
type:typeArr,
position:$(value).find(".info-publis .name").text(),
txImg:$(value).find(".info-publis img").attr("src"),
time:$(value).find(".info-publis p").text()
});
// console.log(typeof $(value).find(".info-primary").children().eq(1).html());
});
//将数组转换成字符串
result=JSON.stringify(result);
//将数组输出到json文件里 刷新目录 即可看到当前文件夹多出一个boss.json文件(打开boss.json文件,ctrl+A全选之后 ctrl+K,再Ctrl+F即可将json文件自动排版)
fs.writeFile("boss.json",result,"utf-8",(error)=>{
//监听错误,如正常输出,则打印null
if(error==null){
console.log("恭喜您,数据爬取成功!请打开json文件,先Ctrl+A,再Ctrl+K,最后Ctrl+F格式化后查看json文件(仅限Visual Studio Code编辑器)");
}
});
});  
  总结
  以上就是小编介绍的爬虫爬取数据的Nodejs实现。我希望它对你有帮助。有任何问题请给我留言,小编会及时回复你的。还要感谢大家对染美科技的支持网站!

nodejs抓取动态网页(网络爬虫(webcrawler)、增量式网络机器人(incremental) )

网站优化优采云 发表了文章 • 0 个评论 • 90 次浏览 • 2022-02-16 02:22 • 来自相关话题

  nodejs抓取动态网页(网络爬虫(webcrawler)、增量式网络机器人(incremental)
)
  前言
  ?? 网络爬虫(又称网络蜘蛛、网络机器人,在foaf社区,更常称为网页追逐者),是一种按照一定的规则自动爬取万维网上信息的程序或脚本。其他不太常用的名称是 ant、autoindex、emulator 或 worm。
  ?? 我们可以使用网络爬虫来自动采集的数据信息,比如搜索引擎中的站点收录的爬取,对数据采集的数据分析和挖掘,用于财务分析进行采集在金融数据上,此外,网络爬虫还可用于舆情监测分析、目标客户数据采集等各个领域。
  1、网络爬虫分类
  根据系统结构和实现技术,网络爬虫大致可以分为以下几种:通用网络爬虫、聚焦网络爬虫、增量网络爬虫、深度网络爬虫。实际的网络爬虫系统通常是通过几种爬虫技术的组合来实现的。下面简单介绍一下这几种爬虫。
  1.1、万能网络爬虫
  又称可伸缩网络爬虫,爬取对象从一些种子URL扩展到整个网络,主要针对门户网站搜索引擎和大型网络服务商采集数据。
  1.2、关注网络爬虫
  话题爬虫也称为话题爬虫,是指选择性地爬取与预定义话题相关的页面的网络爬虫。与一般的网络爬虫相比,专注爬虫只需要爬取主题相关的页面,大大节省了硬件和网络资源,而且由于页面少,保存的页面更新也很快。信息需求。
  1.3、增量网络爬虫
  指增量更新下载的网页的爬虫,只爬取新生成或更改的网页。可以在一定程度上保证爬取的页面尽可能的新。.
  1.4、深网爬虫
  网页按存在方式可分为表层网页(surface web)和深层网页(deep web,也称为隐形网页或隐藏网页)。表面网页是指可以被传统搜索引擎检索到的页面,以及主要由可以通过超链接到达的静态网页组成的网页。深网是那些大部分内容无法通过静态链接访问的网页,隐藏在搜索表单后面,只有在用户提交一些 关键词 时才可用。
  2、创建一个简单的爬虫应用
  ?? 简单了解以上爬虫后,我们来实现一个简单的爬虫应用。
  2.1、实现目标
  说到爬虫,大概率会想到大数据,然后会想到python。百度之后,python的爬虫确实多了。由于我主要做前端开发,所以相对来说javascript更熟练,也更简单。实现一个小目标,然后用nodejs爬取首页文章列表(自己常用的一个开发者网站),然后写入本地json文件。
  2.2、环境建设
  nodejs安装好后,打开命令行,使用node -v查看nodejs是否安装成功,使用npm -v查看nodejs是否安装成功,如果安装成功应该打印如下信息(不同版本不同):
  
  2.3、具体实现
  2.3.1、安装依赖
  在目录下执行 npm install superagentcheerio --save-dev 安装superagent和cheerio。创建一个 crawler.js 文件。
  // 导入依赖包
const http = require("http");
const path = require("path");
const url = require("url");
const fs = require("fs");
const superagent = require("superagent");
const cheerio = require("cheerio");
  2.3.2、爬取数据
  然后获取请求页面,获取页面内容后,根据你想要的数据解析返回的DOM,最后将处理后的结果json转成字符串保存在本地。
  //爬取页面地址
const pageurl="https://www.cnblogs.com/";
// 解码字符串
function unescapestring(str){
if(!str){
return &#39;&#39;
}else{
return unescape(str.replace(/&#x/g,&#39;%u&#39;).replace(/;/g,&#39;&#39;));
}
}
// 抓取数据
function fetchdata(){
console.log(&#39;爬取数据时间节点:&#39;,new date());
superagent.get(pageurl).end((error,response)=>{
// 页面文档数据
let content=response.text;
if(content){
console.log(&#39;获取数据成功&#39;);
}
// 定义一个空数组来接收数据
let result=[];
let $=cheerio.load(content);
let postlist=$("#main #post_list .post_item");
postlist.each((index,value)=>{
let titlelnk=$(value).find(&#39;a.titlelnk&#39;);
let itemfoot=$(value).find(&#39;.post_item_foot&#39;);
let title=titlelnk.html(); //标题
let //链接
let author=itemfoot.find(&#39;a.lightblue&#39;).html(); //作者
let headlogo=$(value).find(&#39;.post_item_summary a img&#39;).attr(&#39;src&#39;); //头像
let summary=$(value).find(&#39;.post_item_summary&#39;).text(); //简介
let postedtime=itemfoot.text().split(&#39;发布于 &#39;)[1].substr(0,16); //发布时间
let readnum=itemfoot.text().split(&#39;阅读&#39;)[1]; //阅读量
readnum=readnum.substr(1,readnum.length-1);
title=unescapestring(title);

author=unescapestring(author);
headlogo=unescapestring(headlogo);
summary=unescapestring(summary);
postedtime=unescapestring(postedtime);
readnum=unescapestring(readnum);
result.push({
index,
title,
href,
author,
headlogo,
summary,
postedtime,
readnum
});
});
// 数组转换为字符串
result=json.stringify(result);
// 写入本地cnblogs.json文件中
fs.writefile("cnblogs.json",result,"utf-8",(err)=>{
// 监听错误,如正常输出,则打印null
if(!err){
console.log(&#39;写入数据成功&#39;);
}
});
});
}
fetchdata();
  3、执行优化
  3.1、生成结果
  在项目目录下打开命令行,输入node crawler.js,
  
  你会发现目录下会创建一个cnblogs.json文件,打开文件如下:
   查看全部

  nodejs抓取动态网页(网络爬虫(webcrawler)、增量式网络机器人(incremental)
)
  前言
  ?? 网络爬虫(又称网络蜘蛛、网络机器人,在foaf社区,更常称为网页追逐者),是一种按照一定的规则自动爬取万维网上信息的程序或脚本。其他不太常用的名称是 ant、autoindex、emulator 或 worm。
  ?? 我们可以使用网络爬虫来自动采集的数据信息,比如搜索引擎中的站点收录的爬取,对数据采集的数据分析和挖掘,用于财务分析进行采集在金融数据上,此外,网络爬虫还可用于舆情监测分析、目标客户数据采集等各个领域。
  1、网络爬虫分类
  根据系统结构和实现技术,网络爬虫大致可以分为以下几种:通用网络爬虫、聚焦网络爬虫、增量网络爬虫、深度网络爬虫。实际的网络爬虫系统通常是通过几种爬虫技术的组合来实现的。下面简单介绍一下这几种爬虫。
  1.1、万能网络爬虫
  又称可伸缩网络爬虫,爬取对象从一些种子URL扩展到整个网络,主要针对门户网站搜索引擎和大型网络服务商采集数据。
  1.2、关注网络爬虫
  话题爬虫也称为话题爬虫,是指选择性地爬取与预定义话题相关的页面的网络爬虫。与一般的网络爬虫相比,专注爬虫只需要爬取主题相关的页面,大大节省了硬件和网络资源,而且由于页面少,保存的页面更新也很快。信息需求。
  1.3、增量网络爬虫
  指增量更新下载的网页的爬虫,只爬取新生成或更改的网页。可以在一定程度上保证爬取的页面尽可能的新。.
  1.4、深网爬虫
  网页按存在方式可分为表层网页(surface web)和深层网页(deep web,也称为隐形网页或隐藏网页)。表面网页是指可以被传统搜索引擎检索到的页面,以及主要由可以通过超链接到达的静态网页组成的网页。深网是那些大部分内容无法通过静态链接访问的网页,隐藏在搜索表单后面,只有在用户提交一些 关键词 时才可用。
  2、创建一个简单的爬虫应用
  ?? 简单了解以上爬虫后,我们来实现一个简单的爬虫应用。
  2.1、实现目标
  说到爬虫,大概率会想到大数据,然后会想到python。百度之后,python的爬虫确实多了。由于我主要做前端开发,所以相对来说javascript更熟练,也更简单。实现一个小目标,然后用nodejs爬取首页文章列表(自己常用的一个开发者网站),然后写入本地json文件。
  2.2、环境建设
  nodejs安装好后,打开命令行,使用node -v查看nodejs是否安装成功,使用npm -v查看nodejs是否安装成功,如果安装成功应该打印如下信息(不同版本不同):
  
  2.3、具体实现
  2.3.1、安装依赖
  在目录下执行 npm install superagentcheerio --save-dev 安装superagent和cheerio。创建一个 crawler.js 文件。
  // 导入依赖包
const http = require("http");
const path = require("path");
const url = require("url");
const fs = require("fs");
const superagent = require("superagent");
const cheerio = require("cheerio");
  2.3.2、爬取数据
  然后获取请求页面,获取页面内容后,根据你想要的数据解析返回的DOM,最后将处理后的结果json转成字符串保存在本地。
  //爬取页面地址
const pageurl="https://www.cnblogs.com/";
// 解码字符串
function unescapestring(str){
if(!str){
return &#39;&#39;
}else{
return unescape(str.replace(/&#x/g,&#39;%u&#39;).replace(/;/g,&#39;&#39;));
}
}
// 抓取数据
function fetchdata(){
console.log(&#39;爬取数据时间节点:&#39;,new date());
superagent.get(pageurl).end((error,response)=>{
// 页面文档数据
let content=response.text;
if(content){
console.log(&#39;获取数据成功&#39;);
}
// 定义一个空数组来接收数据
let result=[];
let $=cheerio.load(content);
let postlist=$("#main #post_list .post_item");
postlist.each((index,value)=>{
let titlelnk=$(value).find(&#39;a.titlelnk&#39;);
let itemfoot=$(value).find(&#39;.post_item_foot&#39;);
let title=titlelnk.html(); //标题
let //链接
let author=itemfoot.find(&#39;a.lightblue&#39;).html(); //作者
let headlogo=$(value).find(&#39;.post_item_summary a img&#39;).attr(&#39;src&#39;); //头像
let summary=$(value).find(&#39;.post_item_summary&#39;).text(); //简介
let postedtime=itemfoot.text().split(&#39;发布于 &#39;)[1].substr(0,16); //发布时间
let readnum=itemfoot.text().split(&#39;阅读&#39;)[1]; //阅读量
readnum=readnum.substr(1,readnum.length-1);
title=unescapestring(title);

author=unescapestring(author);
headlogo=unescapestring(headlogo);
summary=unescapestring(summary);
postedtime=unescapestring(postedtime);
readnum=unescapestring(readnum);
result.push({
index,
title,
href,
author,
headlogo,
summary,
postedtime,
readnum
});
});
// 数组转换为字符串
result=json.stringify(result);
// 写入本地cnblogs.json文件中
fs.writefile("cnblogs.json",result,"utf-8",(err)=>{
// 监听错误,如正常输出,则打印null
if(!err){
console.log(&#39;写入数据成功&#39;);
}
});
});
}
fetchdata();
  3、执行优化
  3.1、生成结果
  在项目目录下打开命令行,输入node crawler.js,
  
  你会发现目录下会创建一个cnblogs.json文件,打开文件如下:
  

nodejs抓取动态网页(第二篇自动爬虫入门(二)爬取动态页面(puppeteer))

网站优化优采云 发表了文章 • 0 个评论 • 58 次浏览 • 2022-02-14 15:14 • 来自相关话题

  nodejs抓取动态网页(第二篇自动爬虫入门(二)爬取动态页面(puppeteer))
  上次写第二篇爬虫教程Node.js爬虫介绍(二)爬取动态页面(puppeteer)讲解使用puppeteer打开无头浏览器进行动态数据爬取,这是一年前的事了,现在的原因这么久没更新是因为开始学爬虫的目的是为了搭建一个自动爬虫系统,去年我开发了陌陌热榜,实现之后就停止研究爬虫了。
  
  陌陌热榜的开发涉及到很多功能模块,包括界面设计和前端开发,后端高度自定义爬虫功能和登录模块,数据表设计,自动化部署等,学到了很多,这次我将使用自动爬虫我们来谈谈这个功能。这次我们来说说定时爬取功能的实现:
  先打开你要爬取的网站看看是动态网页还是静态网页。重点就在这里。如果是静态网页,则很容易处理。使用cheerio进行dom爬取请参考第一讲。; 如果是ajax动态加载的话,除了第二讲,其实还有另一种方式,用F12打开开发者工具,找到网络请求,找到你要爬取的请求地址。
  
  以百度热榜为例,我每分钟抓取一次百度热榜的标题数据,拼接标题和搜索量,打印出来。它是一个动态加载的网页。按照上面的方法,找到请求地址为/mobile_v2/buzz/hotspot,然后尝试爬取:
  const request = require(&#39;request&#39;)
request(&#39;http://top.baidu.com/mobile_v2 ... 39%3B, (err, res) => {
if (err) {
console.log(err.code)
return false;
}
let data = JSON.parse(res.body).result.topwords
console.log(`${data[0].keyword} - ${data[0].searches}次搜索`)
})
// 运行结果 -> 湖北十堰爆炸致11死 省长赶往现场 - 4974558次搜索
  爬行没有问题。实现定时爬取,使用调度库实现简单的定时任务。使用 schedule.scheduleJob() 方法启动计划任务。第一个参数是定时器,即你想多久执行一次;第二个参数是执行函数。
  定时器建议使用 Cron 样式,如:'* * *' 6 个占位符代表秒、分、小时、天、月、星期几,星号代表完全匹配。- 在每分钟的第 30 秒触发:'30 * *' - 在每小时的 1 分 30 秒触发:'30 1 * *' - 在每天凌晨 1:1:30 触发:'30 1 1* ' - 每天 每月 1 日 1:1:30 触发:'30 1 1 1*' - 2016 年 1 月 1 日 1:1:30 触发:'30 1 1 1 2016' - 1 点 1:1每周的分 30 秒触发:'30 1 1 * 1'
  现在想每分钟抓取一次百度热搜头条的数据,那么
  const schedule = require(&#39;node-schedule&#39;)
const request = require(&#39;request&#39;)
schedule.scheduleJob(&#39;0 * * * * *&#39;, () => {
request(&#39;http://top.baidu.com/mobile_v2 ... 39%3B, (err, res) => {
if (err) {
console.log(err.code)
return false;
}
let data = JSON.parse(res.body).result.topwords
console.log(`${data[0].keyword} - ${data[0].searches}次搜索`)
})
})
  运行结果是:
  
  做吧~ 查看全部

  nodejs抓取动态网页(第二篇自动爬虫入门(二)爬取动态页面(puppeteer))
  上次写第二篇爬虫教程Node.js爬虫介绍(二)爬取动态页面(puppeteer)讲解使用puppeteer打开无头浏览器进行动态数据爬取,这是一年前的事了,现在的原因这么久没更新是因为开始学爬虫的目的是为了搭建一个自动爬虫系统,去年我开发了陌陌热榜,实现之后就停止研究爬虫了。
  
  陌陌热榜的开发涉及到很多功能模块,包括界面设计和前端开发,后端高度自定义爬虫功能和登录模块,数据表设计,自动化部署等,学到了很多,这次我将使用自动爬虫我们来谈谈这个功能。这次我们来说说定时爬取功能的实现:
  先打开你要爬取的网站看看是动态网页还是静态网页。重点就在这里。如果是静态网页,则很容易处理。使用cheerio进行dom爬取请参考第一讲。; 如果是ajax动态加载的话,除了第二讲,其实还有另一种方式,用F12打开开发者工具,找到网络请求,找到你要爬取的请求地址。
  
  以百度热榜为例,我每分钟抓取一次百度热榜的标题数据,拼接标题和搜索量,打印出来。它是一个动态加载的网页。按照上面的方法,找到请求地址为/mobile_v2/buzz/hotspot,然后尝试爬取:
  const request = require(&#39;request&#39;)
request(&#39;http://top.baidu.com/mobile_v2 ... 39%3B, (err, res) => {
if (err) {
console.log(err.code)
return false;
}
let data = JSON.parse(res.body).result.topwords
console.log(`${data[0].keyword} - ${data[0].searches}次搜索`)
})
// 运行结果 -> 湖北十堰爆炸致11死 省长赶往现场 - 4974558次搜索
  爬行没有问题。实现定时爬取,使用调度库实现简单的定时任务。使用 schedule.scheduleJob() 方法启动计划任务。第一个参数是定时器,即你想多久执行一次;第二个参数是执行函数。
  定时器建议使用 Cron 样式,如:'* * *' 6 个占位符代表秒、分、小时、天、月、星期几,星号代表完全匹配。- 在每分钟的第 30 秒触发:'30 * *' - 在每小时的 1 分 30 秒触发:'30 1 * *' - 在每天凌晨 1:1:30 触发:'30 1 1* ' - 每天 每月 1 日 1:1:30 触发:'30 1 1 1*' - 2016 年 1 月 1 日 1:1:30 触发:'30 1 1 1 2016' - 1 点 1:1每周的分 30 秒触发:'30 1 1 * 1'
  现在想每分钟抓取一次百度热搜头条的数据,那么
  const schedule = require(&#39;node-schedule&#39;)
const request = require(&#39;request&#39;)
schedule.scheduleJob(&#39;0 * * * * *&#39;, () => {
request(&#39;http://top.baidu.com/mobile_v2 ... 39%3B, (err, res) => {
if (err) {
console.log(err.code)
return false;
}
let data = JSON.parse(res.body).result.topwords
console.log(`${data[0].keyword} - ${data[0].searches}次搜索`)
})
})
  运行结果是:
  
  做吧~

nodejs抓取动态网页(/module/cvsresovle/js)

网站优化优采云 发表了文章 • 0 个评论 • 77 次浏览 • 2022-02-13 18:13 • 来自相关话题

  nodejs抓取动态网页(/module/cvsresovle/js)
  有这样一个需求,先从cvs文件中读取要解析的url数据,然后使用puppeteer和puppeteer-har获取浏览器的HAR数据。在调试的过程中发现for循环中怎么操作是异步的,终于找到了解决办法,这里也记录一下。
  1、创建解析csv文件的代码(ultra-harlog/module/cvsresovle.js)
  const fs = require("fs");
const path = require("path");
const csv =require('csv');
const parse = require('csv-parse/lib/sync')
const iconv = require('iconv-lite');
/*
npm install iconv-lite
*/
function readUrlRecord(csvpath){
console.log('开始解析文件:' + csvpath) ;
//读取文件
const input = fs.readFileSync(csvpath,'utf8') ;
/*
解析文件,生成JSON格式
{ ' ': '142',
AREA_NAME: '湖北',
SITE_LINK: 'www.banggo.com',
BEARING_MODE: '移动接入',
SITE_NAME: '邦购',
MENU_TYPE: '二级' }
*/
const records = parse(input, {
columns: true,
skip_empty_lines: true,
delimiter: ',',
}) ;
return records ;
}
//readUrlRecord('../top300.csv') ;
exports.readUrlRecord = readUrlRecord;
  2、创建抓取的主代码(ultra-harlog/module/puppeteerhar-event.js)
  const fs = require('fs');
const { promisify } = require('util');
const path = require("path");
const puppeteer = require('puppeteer');
const { harFromMessages } = require('chrome-har');
const logger=require("./log");
const log = logger.getPuppeteerHarEventRecordLogger() ;
//https://michaljanaszek.com/blo ... eteer
//https://www.npmjs.com/package/chrome-har
// 设置要监控的事件
const observe = [
'Page.loadEventFired',
'Page.domContentEventFired',
'Page.frameStartedLoading',
'Page.frameAttached',
'Network.requestWillBeSent',
'Network.requestServedFromCache',
'Network.dataReceived',
'Network.responseReceived',
'Network.resourceChangedPriority',
'Network.loadingFinished',
'Network.loadingFailed',
];
/*
启动浏览器
*/
async function launchBrowser(){
//启动浏览器实例 [puppeteer.createBrowserFetcher([options])]
let browser = await puppeteer.launch({
// 若是手动下载的chromium需要指定chromium地址, 默认引用地址为 /项目目录/node_modules/puppeteer/.local-chromium/
//executablePath: '/Users/huqiyang/Documents/project/z/chromium/Chromium.app/Contents/MacOS/Chromium',
//如果是访问https页面 此属性会忽略https错误
ignoreHTTPSErrors: true,
// 关闭headless模式, 不会打开浏览器
headless: true,
//浏览器启动参数 https://peter.sh/experiments/c ... ches/ --timeout
args:["--disk-cache-size=0","--disable-cache",'--disable-infobars','--window-size=800,600','--ignore-certificate-errors','--enable-feaures'],
//是否为每个选项卡自动打开DevTools面板。 如果此选项为true,则headless选项将设置为false。
devtools: false,
//Defaults to 30000 (30 seconds). Pass 0 to disable timeout.
timeout: 0
//放慢puppeteer执行的动作,方便调试
//slowMo: 250
});
return browser ;
}
async function saveHarlog(url,dirPath,filename){
let homesite = url ;
//保存的文件路径
let harFilePath = path.join(dirPath,filename) ;
//处理URL
if(!(url.startsWith('http://') || url.startsWith('https://'))){
url = "http://" + url ;
}
//打开浏览器
let browser = await launchBrowser() ;
//创建一个新页面
//let page = await browser.newPage();
let page = (await browser.pages())[0];
// 注册事件监听器
const client = await page.target().createCDPSession();
await client.send('Page.enable');
await client.send('Network.enable');
//用于保存用于转为为HAR数据的事件
const events = [];
observe.forEach(method => {
client.on(method, params => {
events.push({ method, params });
});
});
try{
// 执行跳转,访问制定的资源
await page.goto(url,{
timeout:0
});
}catch(error){
log.info('resovle error :' + url + "; error message:" + error) ;
}finally{
if(browser){
await browser.close();
}
}
const har = harFromMessages(events);
//resovleHar(har) ;
//log.info(JSON.stringify(har));
await promisify(fs.writeFile)(harFilePath, JSON.stringify(har));
}
exports.launchBrowser = launchBrowser;
exports.saveHarlog = saveHarlog;
  3、创建启动文件(ultra-harlog/puppeteerhar-event-app.js) 查看全部

  nodejs抓取动态网页(/module/cvsresovle/js)
  有这样一个需求,先从cvs文件中读取要解析的url数据,然后使用puppeteer和puppeteer-har获取浏览器的HAR数据。在调试的过程中发现for循环中怎么操作是异步的,终于找到了解决办法,这里也记录一下。
  1、创建解析csv文件的代码(ultra-harlog/module/cvsresovle.js)
  const fs = require("fs");
const path = require("path");
const csv =require('csv');
const parse = require('csv-parse/lib/sync')
const iconv = require('iconv-lite');
/*
npm install iconv-lite
*/
function readUrlRecord(csvpath){
console.log('开始解析文件:' + csvpath) ;
//读取文件
const input = fs.readFileSync(csvpath,'utf8') ;
/*
解析文件,生成JSON格式
{ ' ': '142',
AREA_NAME: '湖北',
SITE_LINK: 'www.banggo.com',
BEARING_MODE: '移动接入',
SITE_NAME: '邦购',
MENU_TYPE: '二级' }
*/
const records = parse(input, {
columns: true,
skip_empty_lines: true,
delimiter: ',',
}) ;
return records ;
}
//readUrlRecord('../top300.csv') ;
exports.readUrlRecord = readUrlRecord;
  2、创建抓取的主代码(ultra-harlog/module/puppeteerhar-event.js)
  const fs = require('fs');
const { promisify } = require('util');
const path = require("path");
const puppeteer = require('puppeteer');
const { harFromMessages } = require('chrome-har');
const logger=require("./log");
const log = logger.getPuppeteerHarEventRecordLogger() ;
//https://michaljanaszek.com/blo ... eteer
//https://www.npmjs.com/package/chrome-har
// 设置要监控的事件
const observe = [
'Page.loadEventFired',
'Page.domContentEventFired',
'Page.frameStartedLoading',
'Page.frameAttached',
'Network.requestWillBeSent',
'Network.requestServedFromCache',
'Network.dataReceived',
'Network.responseReceived',
'Network.resourceChangedPriority',
'Network.loadingFinished',
'Network.loadingFailed',
];
/*
启动浏览器
*/
async function launchBrowser(){
//启动浏览器实例 [puppeteer.createBrowserFetcher([options])]
let browser = await puppeteer.launch({
// 若是手动下载的chromium需要指定chromium地址, 默认引用地址为 /项目目录/node_modules/puppeteer/.local-chromium/
//executablePath: '/Users/huqiyang/Documents/project/z/chromium/Chromium.app/Contents/MacOS/Chromium',
//如果是访问https页面 此属性会忽略https错误
ignoreHTTPSErrors: true,
// 关闭headless模式, 不会打开浏览器
headless: true,
//浏览器启动参数 https://peter.sh/experiments/c ... ches/ --timeout
args:["--disk-cache-size=0","--disable-cache",'--disable-infobars','--window-size=800,600','--ignore-certificate-errors','--enable-feaures'],
//是否为每个选项卡自动打开DevTools面板。 如果此选项为true,则headless选项将设置为false。
devtools: false,
//Defaults to 30000 (30 seconds). Pass 0 to disable timeout.
timeout: 0
//放慢puppeteer执行的动作,方便调试
//slowMo: 250
});
return browser ;
}
async function saveHarlog(url,dirPath,filename){
let homesite = url ;
//保存的文件路径
let harFilePath = path.join(dirPath,filename) ;
//处理URL
if(!(url.startsWith('http://') || url.startsWith('https://'))){
url = "http://" + url ;
}
//打开浏览器
let browser = await launchBrowser() ;
//创建一个新页面
//let page = await browser.newPage();
let page = (await browser.pages())[0];
// 注册事件监听器
const client = await page.target().createCDPSession();
await client.send('Page.enable');
await client.send('Network.enable');
//用于保存用于转为为HAR数据的事件
const events = [];
observe.forEach(method => {
client.on(method, params => {
events.push({ method, params });
});
});
try{
// 执行跳转,访问制定的资源
await page.goto(url,{
timeout:0
});
}catch(error){
log.info('resovle error :' + url + "; error message:" + error) ;
}finally{
if(browser){
await browser.close();
}
}
const har = harFromMessages(events);
//resovleHar(har) ;
//log.info(JSON.stringify(har));
await promisify(fs.writeFile)(harFilePath, JSON.stringify(har));
}
exports.launchBrowser = launchBrowser;
exports.saveHarlog = saveHarlog;
  3、创建启动文件(ultra-harlog/puppeteerhar-event-app.js)

nodejs抓取动态网页(【技术创作101训练营】用NodeJS来入门image.png第二页)

网站优化优采云 发表了文章 • 0 个评论 • 59 次浏览 • 2022-02-13 03:10 • 来自相关话题

  nodejs抓取动态网页(【技术创作101训练营】用NodeJS来入门image.png第二页)
  【科技创造101训练营】使用NodeJS上手爬虫
  image.png 演讲第一页:大家好,今天主要给大家分享使用NodeJS上手爬虫 image.png 演讲第二页:现在简单介绍一下我讲的一些内容今天,首先,什么是爬行动物?还有为什么会有爬虫,然后怎么做爬虫,最后是扩展和总结 image.png 演讲第三页:什么是爬虫,嗯,我们写了一个爬虫程序,然后会是这样的一个爬虫,然后就会在网上层出不穷,自动模仿人,image.png第四页演讲:那为什么要有爬虫呢,嗯,比如说搜索引擎可以用爬虫爬一些关键词,一些内容,然后方便我们搜索;那么它也可以聚合信息,比如一些内容&lt; @网站,比如头条等,可以利用爬虫整合其他平台的信息并下载HTML源代码,然后去获取数据;还有一种情况,当我们发现HTML源代码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 他们可以使用爬虫整合其他平台的信息并下载HTML源代码,然后去获取数据;还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 他们可以使用爬虫整合其他平台的信息并下载HTML源代码,然后去获取数据;还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png Lecture第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题
  346 查看全部

  nodejs抓取动态网页(【技术创作101训练营】用NodeJS来入门image.png第二页)
  【科技创造101训练营】使用NodeJS上手爬虫
  image.png 演讲第一页:大家好,今天主要给大家分享使用NodeJS上手爬虫 image.png 演讲第二页:现在简单介绍一下我讲的一些内容今天,首先,什么是爬行动物?还有为什么会有爬虫,然后怎么做爬虫,最后是扩展和总结 image.png 演讲第三页:什么是爬虫,嗯,我们写了一个爬虫程序,然后会是这样的一个爬虫,然后就会在网上层出不穷,自动模仿人,image.png第四页演讲:那为什么要有爬虫呢,嗯,比如说搜索引擎可以用爬虫爬一些关键词,一些内容,然后方便我们搜索;那么它也可以聚合信息,比如一些内容&lt; @网站,比如头条等,可以利用爬虫整合其他平台的信息并下载HTML源代码,然后去获取数据;还有一种情况,当我们发现HTML源代码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 他们可以使用爬虫整合其他平台的信息并下载HTML源代码,然后去获取数据;还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 他们可以使用爬虫整合其他平台的信息并下载HTML源代码,然后去获取数据;还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png Lecture第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题 还有一种情况,当我们发现HTML源码中没有想要的数据时,他可能通过接口请求,通过JS渲染,这样我们就可以通过抓包查看它的接口,然后通过界面。接口获取想要的数据嗯,这两个库主要是使用真实的浏览器访问页面,等待页面请求数据并渲染,然后使用选择器获取DOM获取指定数据 image.png 讲座第10页手稿:接下来我们可以看个例子,然后他主要爬取掘金列表的文章的标题
  346

nodejs抓取动态网页(前端渲染和后端渲染的优势是怎样的??)

网站优化优采云 发表了文章 • 0 个评论 • 48 次浏览 • 2022-02-11 08:06 • 来自相关话题

  nodejs抓取动态网页(前端渲染和后端渲染的优势是怎样的??)
  同构渲染是指在前后端使用js。第一次渲染时,使用 nodejs 来加载我们的 HTML 文件。用小白的话来说,同构渲染既不是后端渲染,也不是前端渲染,是两者之间的共同部分。同构渲染的实现其实是SSR+CSH,都是通过代码实现的。
  
  这里说的是前端渲染和后端渲染,各有各的优势。前端渲染的优点包括:可以部分刷新,可以实现懒加载技术,可以用js实现各种炫酷效果,可以通过cdn服务器访问资源,前后端开发都可以分离,学习成本相对较低。后端的主要优点是:可以解决首屏加载问题,有利于seo的优化。
  随着技术的发展,像Vue、React这样的框架已经能够实现很好的同构渲染,也就是我们常说的SSR,比如nuxt.js和next.js。在前端渲染非常普遍的今天,为什么还要有同构渲染的技术呢?
  如前所述,同构渲染其实可以解决首屏加载慢和SEO优化的问题。
  传统的搜索引擎从 HTML 文件中爬取我们需要的数据,所以前端渲染的页面无法爬取。开发过vue和react项目的开发者都知道,我们经常使用的SPA会将所有JS打包成一个整体。一个不容忽视的问题是文件太大,导致渲染前的等待时间过长。尤其是网速不好的时候,等待白屏结束对用户来说体验不是很好。
  服务端渲染可以先将使用过的数据渲染成最终的HTML直接展示,理想的避免了白屏问题。但没有什么是绝对的。如果一个页面的数据量太大,难免会出现等待的情况。
  说到PHP和JSP的渲染方式,都是前端将页面写到后端后渲染出来的HTML页面。其实pass中的SSR原理是一样的,只是多了一层CSH。其实现需要服务器提供初始化数据。 查看全部

  nodejs抓取动态网页(前端渲染和后端渲染的优势是怎样的??)
  同构渲染是指在前后端使用js。第一次渲染时,使用 nodejs 来加载我们的 HTML 文件。用小白的话来说,同构渲染既不是后端渲染,也不是前端渲染,是两者之间的共同部分。同构渲染的实现其实是SSR+CSH,都是通过代码实现的。
  
  这里说的是前端渲染和后端渲染,各有各的优势。前端渲染的优点包括:可以部分刷新,可以实现懒加载技术,可以用js实现各种炫酷效果,可以通过cdn服务器访问资源,前后端开发都可以分离,学习成本相对较低。后端的主要优点是:可以解决首屏加载问题,有利于seo的优化。
  随着技术的发展,像Vue、React这样的框架已经能够实现很好的同构渲染,也就是我们常说的SSR,比如nuxt.js和next.js。在前端渲染非常普遍的今天,为什么还要有同构渲染的技术呢?
  如前所述,同构渲染其实可以解决首屏加载慢和SEO优化的问题。
  传统的搜索引擎从 HTML 文件中爬取我们需要的数据,所以前端渲染的页面无法爬取。开发过vue和react项目的开发者都知道,我们经常使用的SPA会将所有JS打包成一个整体。一个不容忽视的问题是文件太大,导致渲染前的等待时间过长。尤其是网速不好的时候,等待白屏结束对用户来说体验不是很好。
  服务端渲染可以先将使用过的数据渲染成最终的HTML直接展示,理想的避免了白屏问题。但没有什么是绝对的。如果一个页面的数据量太大,难免会出现等待的情况。
  说到PHP和JSP的渲染方式,都是前端将页面写到后端后渲染出来的HTML页面。其实pass中的SSR原理是一样的,只是多了一层CSH。其实现需要服务器提供初始化数据。

nodejs抓取动态网页(ES6ES6模块的只读代码和javascript的区别?模块)

网站优化优采云 发表了文章 • 0 个评论 • 54 次浏览 • 2022-02-10 10:12 • 来自相关话题

  nodejs抓取动态网页(ES6ES6模块的只读代码和javascript的区别?模块)
  安装node,百度查,node -v查询版本
  node.js 和 javascript 之间的区别
  es
定义了语法,js和nodejs都要遵守
不能操作dom,不能监听click,不能发送ajax
js
使用es语法,外加webAPI,可以操作dom,事件绑定,ajax等
nodejs
使用es语法,外加nodejsAPI,处理htpp,处理文件等
  commonjs 模块化,node.js 使用了这种模块化
  一个.js
  function add(a, b) {
return a + b
}
function mul(a, b) {
return a * b
}
module.exports = {
add,
mul
}
  b.js
  const { add, mul } = require('./a')
const _ = require('lodash')
const sum = add(10, 20)
const result = mul(100, 200)
console.log(sum)
console.log(result)
const arr = _.concat([1, 2], 3)
console.log('arr...', arr)
  npm init 初始化,运行,节点 b.js
  CommonJS 模块和 ES6 模块 CommonJS 的区别在于基本数据类型的复制。将被模块缓存。同时,该模块输出的变量可以在另一个模块中重新赋值。对于复杂的数据类型,它是一个浅拷贝。由于两个模块引用的对象都指向同一个内存空间,所以改变这个模块的值会影响到另一个模块。当使用 require 命令加载模块时,会运行整个模块的代码。当使用 require 命令加载同一个模块时,该模块不会被执行,而是会检索缓存中的值。也就是说,CommonJS 模块无论加载多少次,第一次加载时只会运行一次。如果稍后加载,它将返回第一次运行的结果,除非手动清除系统缓存。当循环被加载时,它属于加载时执行。也就是当需要脚本代码的时候,都会全部执行。一旦一个模块被“循环加载”,只会输出执行的部分,不会输出未执行的部分。ES6 Modules ES6 模块中的值属于[Dynamic Read-Only References]。对于只读,即不允许修改导入变量的值。导入的变量是只读的,无论是基本数据类型还是复杂数据类型。当模块遇到导入命令时,会生成一个只读引用。当脚本实际执行时,根据这个只读引用,去加载的模块获取值。对于动态,原创值发生变化,导入加载的值也发生变化。无论是基本数据类型还是复杂数据类型。ES6 模块在循环加载时被动态引用。只要两个模块之间有一些引用,代码就可以执行。
  默认导出   export default 变量或者函数或者对象
默认引入  import name from "相对或绝对路径" 
导出的名字和引入的名字可以不一致
按需导出  export 需要声明 变量用const var let  函数用function
按需引入  import {变量名或函数名} form "路径"    
全部引入 使用  import * as 自定义name "路径"
会将默认导出和按需导出 全部引入
  服务器开发和前端开发的区别
  1.服务稳定,服务端可能遭受恶意攻击
  2.日志记录,单个客户端可以意外挂掉,但是服务端不行,pm2正在等待进程
  3.内存优化,服务器端承载很多请求,CPU和内存都是稀缺资源,用stream写日志,用redis存储session
  4.安全,服务器端可能会受到恶意攻击,未经授权的操作,数据库攻击,防止xss攻击,spl注入
  5.集群和服务拆分,扩大服务和机器拆分流量
  6.日志记录,服务器要记录日志,存储日志,分析日志。 查看全部

  nodejs抓取动态网页(ES6ES6模块的只读代码和javascript的区别?模块)
  安装node,百度查,node -v查询版本
  node.js 和 javascript 之间的区别
  es
定义了语法,js和nodejs都要遵守
不能操作dom,不能监听click,不能发送ajax
js
使用es语法,外加webAPI,可以操作dom,事件绑定,ajax等
nodejs
使用es语法,外加nodejsAPI,处理htpp,处理文件等
  commonjs 模块化,node.js 使用了这种模块化
  一个.js
  function add(a, b) {
return a + b
}
function mul(a, b) {
return a * b
}
module.exports = {
add,
mul
}
  b.js
  const { add, mul } = require('./a')
const _ = require('lodash')
const sum = add(10, 20)
const result = mul(100, 200)
console.log(sum)
console.log(result)
const arr = _.concat([1, 2], 3)
console.log('arr...', arr)
  npm init 初始化,运行,节点 b.js
  CommonJS 模块和 ES6 模块 CommonJS 的区别在于基本数据类型的复制。将被模块缓存。同时,该模块输出的变量可以在另一个模块中重新赋值。对于复杂的数据类型,它是一个浅拷贝。由于两个模块引用的对象都指向同一个内存空间,所以改变这个模块的值会影响到另一个模块。当使用 require 命令加载模块时,会运行整个模块的代码。当使用 require 命令加载同一个模块时,该模块不会被执行,而是会检索缓存中的值。也就是说,CommonJS 模块无论加载多少次,第一次加载时只会运行一次。如果稍后加载,它将返回第一次运行的结果,除非手动清除系统缓存。当循环被加载时,它属于加载时执行。也就是当需要脚本代码的时候,都会全部执行。一旦一个模块被“循环加载”,只会输出执行的部分,不会输出未执行的部分。ES6 Modules ES6 模块中的值属于[Dynamic Read-Only References]。对于只读,即不允许修改导入变量的值。导入的变量是只读的,无论是基本数据类型还是复杂数据类型。当模块遇到导入命令时,会生成一个只读引用。当脚本实际执行时,根据这个只读引用,去加载的模块获取值。对于动态,原创值发生变化,导入加载的值也发生变化。无论是基本数据类型还是复杂数据类型。ES6 模块在循环加载时被动态引用。只要两个模块之间有一些引用,代码就可以执行。
  默认导出   export default 变量或者函数或者对象
默认引入  import name from "相对或绝对路径" 
导出的名字和引入的名字可以不一致
按需导出  export 需要声明 变量用const var let  函数用function
按需引入  import {变量名或函数名} form "路径"    
全部引入 使用  import * as 自定义name "路径"
会将默认导出和按需导出 全部引入
  服务器开发和前端开发的区别
  1.服务稳定,服务端可能遭受恶意攻击
  2.日志记录,单个客户端可以意外挂掉,但是服务端不行,pm2正在等待进程
  3.内存优化,服务器端承载很多请求,CPU和内存都是稀缺资源,用stream写日志,用redis存储session
  4.安全,服务器端可能会受到恶意攻击,未经授权的操作,数据库攻击,防止xss攻击,spl注入
  5.集群和服务拆分,扩大服务和机器拆分流量
  6.日志记录,服务器要记录日志,存储日志,分析日志。

nodejs抓取动态网页( ()函数的处理方法与对应的修改 )

网站优化优采云 发表了文章 • 0 个评论 • 54 次浏览 • 2022-02-07 22:15 • 来自相关话题

  nodejs抓取动态网页(
()函数的处理方法与对应的修改
)
  <p>现存在如下图所示的文件目录: <br /> <br /> 我们希望通过02.js来实现能够静态加载static中的文件 <br /> 这篇博客是我的上一篇博客静态资源管理续篇,在上一篇博客中,因为遇到加载json格式的文件遇到了问题,所以写了一个续篇,如果有感兴趣的小伙伴可以看一下 <br /> 实现代码如下:
var http = require(&#39;http&#39;);
var url = require(&#39;url&#39;);
var fs = require(&#39;fs&#39;);
var path = require(&#39;path&#39;);
var server = http.createServer(function (req,res) {
//这里如果不用req.url来判断,那么不管用户输入什么操作,执行的操作都是一样的
//得到地址
var pathname = url.parse(req.url).pathname;
//不处理小图标的请求
if(pathname == &#39;/favicon.ico&#39;){
return;
}
//判断此时用户输入的是文件地址还是文件夹地址
//如果是文件夹地址,那么自动请求这个文件夹中的index.html
if(pathname.indexOf(&#39;.&#39;) == -1){
pathname += &#39;index.html&#39;;
}
//得到用户输入的文件的拓展名
var extname = path.extname(pathname);
console.log(pathname);
fs.readFile(&#39;./static/&#39;+pathname,function (err,data) {
if(err){
//如果页面报错,则让它返回一个新的页面
fs.readFile(&#39;./static/404.html&#39;,function (err,data) {
res.writeHead(200,{&#39;Content-Type&#39;:&#39;text/html;charset=UTF8&#39;});
res.end(data);
})
return;
}
//读完文件之后做的事情
console.log(process.cwd());
var mime = getMime(extname);
res.writeHead(200,{&#39;Content-Type&#39;:mime+&#39;;charset=UTF8&#39;});
res.end(data);
})
}).listen(80,&#39;127.0.0.1&#39;);
function getMime(extname) {
//读取mime.josn文件,得到JOSN,根据extname key ,f返回对应的value值
switch (extname){
case &#39;.html&#39;:
return &#39;text/html&#39;;
break;
case &#39;.css&#39;:
return &#39;text/css&#39;;
break;
case &#39;.img&#39;:
return &#39;image/img&#39;;
break;
case &#39;.json&#39;:
fs.readFile(&#39;./static/mime.json&#39;,function (err,data) {
if(err){
throw Error("找不到mime.json文件!");
return;
}
//转成JSON
console.log(typeof data);
//利用json.parse将一个josn字符串解析成为json格式
var mimeJSON = JSON.parse(data);
console.log(mimeJSON[extname]);
return mimeJSON[extname];
})
}
}</p>
  当我们读取到文件的扩展名是json时,我们读取的是json格式的文件,然后使用JSON.parse()方法将JSON字符串转换为JSON对象,然后检索JSON对象。转成json扩展名对应的MIME类型。
  然而,我们惊讶地发现响应头中的 content-type 类型是未定义的,
  
  但是我们不是已经在这部分代码中写好文件类型了吗?
  
  这是因为getMime()函数,当传入参数为'.json'时,函数执行代码中收录异步操作,即读取json文件,所以在未读取文件时,mime有还没读完。赋值时mime已写入content-type,未定义;
  那我们该怎么办呢?
  我们采取强制回调函数getMime的方法,传入文件的扩展名后,再执行回调函数中的内容,并将mime的值赋值给content-type,避免出现问题异步加载。
  修改代码如下:
  getMime(extname,function(mime){
res.writeHead(200,{&#39;Content-Type&#39;:mime+&#39;;charset=UTF8&#39;});
res.end(data);
})
  这样,我们就可以在参数mime的值返回后,进行将type值赋给Content-type的操作。对应的getMime()函数也应该相应修改:
  function getMime(extname,callback){
//读取mime的json文件,将对应扩展名的MIME类型名返回
fs.readfile(&#39;./static/mime.json&#39;,function(err,data){
if(err){
throw Error(&#39;找不到mime.json类型的文件!&#39;);
}
//将json字符串转换为json对象
var mimeJSON = JSON.parse(data);
var mime = mimeJSON[extname] || &#39;text/plain
&#39;;
callback(mime);
})
} 查看全部

  nodejs抓取动态网页(
()函数的处理方法与对应的修改
)
  <p>现存在如下图所示的文件目录: <br /> <br /> 我们希望通过02.js来实现能够静态加载static中的文件 <br /> 这篇博客是我的上一篇博客静态资源管理续篇,在上一篇博客中,因为遇到加载json格式的文件遇到了问题,所以写了一个续篇,如果有感兴趣的小伙伴可以看一下 <br /> 实现代码如下:
var http = require(&#39;http&#39;);
var url = require(&#39;url&#39;);
var fs = require(&#39;fs&#39;);
var path = require(&#39;path&#39;);
var server = http.createServer(function (req,res) {
//这里如果不用req.url来判断,那么不管用户输入什么操作,执行的操作都是一样的
//得到地址
var pathname = url.parse(req.url).pathname;
//不处理小图标的请求
if(pathname == &#39;/favicon.ico&#39;){
return;
}
//判断此时用户输入的是文件地址还是文件夹地址
//如果是文件夹地址,那么自动请求这个文件夹中的index.html
if(pathname.indexOf(&#39;.&#39;) == -1){
pathname += &#39;index.html&#39;;
}
//得到用户输入的文件的拓展名
var extname = path.extname(pathname);
console.log(pathname);
fs.readFile(&#39;./static/&#39;+pathname,function (err,data) {
if(err){
//如果页面报错,则让它返回一个新的页面
fs.readFile(&#39;./static/404.html&#39;,function (err,data) {
res.writeHead(200,{&#39;Content-Type&#39;:&#39;text/html;charset=UTF8&#39;});
res.end(data);
})
return;
}
//读完文件之后做的事情
console.log(process.cwd());
var mime = getMime(extname);
res.writeHead(200,{&#39;Content-Type&#39;:mime+&#39;;charset=UTF8&#39;});
res.end(data);
})
}).listen(80,&#39;127.0.0.1&#39;);
function getMime(extname) {
//读取mime.josn文件,得到JOSN,根据extname key ,f返回对应的value值
switch (extname){
case &#39;.html&#39;:
return &#39;text/html&#39;;
break;
case &#39;.css&#39;:
return &#39;text/css&#39;;
break;
case &#39;.img&#39;:
return &#39;image/img&#39;;
break;
case &#39;.json&#39;:
fs.readFile(&#39;./static/mime.json&#39;,function (err,data) {
if(err){
throw Error("找不到mime.json文件!");
return;
}
//转成JSON
console.log(typeof data);
//利用json.parse将一个josn字符串解析成为json格式
var mimeJSON = JSON.parse(data);
console.log(mimeJSON[extname]);
return mimeJSON[extname];
})
}
}</p>
  当我们读取到文件的扩展名是json时,我们读取的是json格式的文件,然后使用JSON.parse()方法将JSON字符串转换为JSON对象,然后检索JSON对象。转成json扩展名对应的MIME类型。
  然而,我们惊讶地发现响应头中的 content-type 类型是未定义的,
  
  但是我们不是已经在这部分代码中写好文件类型了吗?
  
  这是因为getMime()函数,当传入参数为'.json'时,函数执行代码中收录异步操作,即读取json文件,所以在未读取文件时,mime有还没读完。赋值时mime已写入content-type,未定义;
  那我们该怎么办呢?
  我们采取强制回调函数getMime的方法,传入文件的扩展名后,再执行回调函数中的内容,并将mime的值赋值给content-type,避免出现问题异步加载。
  修改代码如下:
  getMime(extname,function(mime){
res.writeHead(200,{&#39;Content-Type&#39;:mime+&#39;;charset=UTF8&#39;});
res.end(data);
})
  这样,我们就可以在参数mime的值返回后,进行将type值赋给Content-type的操作。对应的getMime()函数也应该相应修改:
  function getMime(extname,callback){
//读取mime的json文件,将对应扩展名的MIME类型名返回
fs.readfile(&#39;./static/mime.json&#39;,function(err,data){
if(err){
throw Error(&#39;找不到mime.json类型的文件!&#39;);
}
//将json字符串转换为json对象
var mimeJSON = JSON.parse(data);
var mime = mimeJSON[extname] || &#39;text/plain
&#39;;
callback(mime);
})
}

nodejs抓取动态网页(puppeteer和nodejs的区别)

网站优化优采云 发表了文章 • 0 个评论 • 104 次浏览 • 2022-02-06 15:03 • 来自相关话题

  nodejs抓取动态网页(puppeteer和nodejs的区别)
  傀儡师
  google chrome 团队出品的 puppeteer 是一个依赖 nodejs 和 chromium 的自动化测试库。它最大的优点是可以处理网页中的动态内容,比如JavaScript,可以更好地模拟用户。
  一些网站的反爬方法隐藏了一些javascript/ajax请求中的部分内容,使得直接获取a标签的方法不起作用。甚至一些 网站 会设置隐藏元素“陷阱”,对用户不可见,脚本将其作为机器触发。在这种情况下,puppeteer的优势就凸显出来了。
  它可以实现以下功能:
  生成页面的屏幕截图和 PDF。抓取 SPA 并生成预渲染内容(即“xxxx”)。自动提交表单、UI测试、键盘输入等。创建最新的自动化测试环境。使用最新的 JavaScript 和浏览器功能直接在最新版本的 Chrome 中运行测试。捕获跟踪您的 网站 的时间线以帮助诊断性能问题。
  开源地址:[][1]
  安装
  npm i puppeteer
  注意先安装nodejs,在nodejs文件的根目录下执行(与npm文件同级)。
  安装过程中会下载chromium,大约120M。
  经过两天(大约10个小时)的探索,绕过了很多异步的坑,作者对puppeteer和nodejs有一定的把握。
  长图,抢博客文章列表:
  
  爬博客文章
  以csdn博客为例,文章的内容需要通过点击“阅读全文”获取,使得只能阅读dom的脚本失效。
  /**
* load blog.csdn.net article to local files
**/
const puppeteer = require('puppeteer');
//emulate iphone
const userAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1';
const workPath = './contents';
const fs = require("fs");
if (!fs.existsSync(workPath)) {
fs.mkdirSync(workPath)
}
//base url
const rootUrl = 'https://blog.csdn.net/';
//max wait milliseconds
const maxWait = 100;
//max loop scroll times
const makLoop = 10;
(async () => {
let url;
let countUrl=0;
const browser = await puppeteer.launch({headless: false});//set headless: true will hide chromium UI
const page = await browser.newPage();
await page.setUserAgent(userAgent);
await page.setViewport({width:414, height:736});
await page.setRequestInterception(true);
//filter to block images
page.on('request', request => {
if (request.resourceType() === 'image')
request.abort();
else
request.continue();
});
await page.goto(rootUrl);
for(let i= 0; iwindow.scrollTo(0, document.body.scrollHeight));
await page.waitForNavigation({timeout:maxWait,waitUntil: ['networkidle0']});
}catch(err){
console.log('scroll to bottom and then wait '+maxWait+'ms.');
}
}
await page.screenshot({path: workPath+'/screenshot.png',fullPage: true, quality :100, type :'jpeg'});
//#feedlist_id li[data-type="blog"] a
const sel = '#feedlist_id li[data-type="blog"] h2 a';
const hrefs = await page.evaluate((sel) => {
let elements = Array.from(document.querySelectorAll(sel));
let links = elements.map(element => {
return element.href
})
return links;
}, sel);
console.log('total links: '+hrefs.length);
process();
async function process(){
if(countUrl {
if (request.resourceType() === 'image')
request.abort();
else
request.continue();
});
await tab.goto(url);
//execute tap request
try{
await tab.tap('.read_more_btn');
}catch(err){
console.log('there\'s none read more button. No need to TAP');
}
let title = await tab.evaluate(() => document.querySelector('#article .article_title').innerText);
let contents = await tab.evaluate(() => document.querySelector('#article .article_content').innerText);
contents = 'TITLE: '+title+'\nURL: '+url+'\nCONTENTS: \n'+contents;
const fs = require("fs");
fs.writeFileSync(workPath+'/'+tab.url().substring(tab.url().lastIndexOf('/'),tab.url().length)+'.txt',contents);
console.log(title + " has been downloaded to local.");
await tab.close();
}catch(err){
console.log('url: '+tab.url()+' \n'+err.toString());
}finally{
process();
}
}
})();
  实施过程
  录屏可以在我的公众号查看,截图如下:
  
  结果
  文章内容列表:
  
  文章内容:
  
  结束语
  之前我想既然nodejs使用的是JavaScript脚本语言,那么它一定能够处理网页的JavaScript内容,但是我还没有找到一个合适/高效的库。直到我找到了 puppeteer,我才决定试水。
  说了这么多,nodejs的异步性真是让人头疼。这几百行代码我折腾了10个小时。
  您可以在代码中展开 process() 方法并使用 async.eachSeries。我使用的递归方法不是最佳解决方案。
  事实上,一个一个地处理是没有效率的。本来我写了一个异步关闭浏览器的方法:
<p>let tryCloseBrowser = setInterval(function(){
console.log("check if any process running...")
if(countDown 查看全部

  nodejs抓取动态网页(puppeteer和nodejs的区别)
  傀儡师
  google chrome 团队出品的 puppeteer 是一个依赖 nodejs 和 chromium 的自动化测试库。它最大的优点是可以处理网页中的动态内容,比如JavaScript,可以更好地模拟用户。
  一些网站的反爬方法隐藏了一些javascript/ajax请求中的部分内容,使得直接获取a标签的方法不起作用。甚至一些 网站 会设置隐藏元素“陷阱”,对用户不可见,脚本将其作为机器触发。在这种情况下,puppeteer的优势就凸显出来了。
  它可以实现以下功能:
  生成页面的屏幕截图和 PDF。抓取 SPA 并生成预渲染内容(即“xxxx”)。自动提交表单、UI测试、键盘输入等。创建最新的自动化测试环境。使用最新的 JavaScript 和浏览器功能直接在最新版本的 Chrome 中运行测试。捕获跟踪您的 网站 的时间线以帮助诊断性能问题。
  开源地址:[][1]
  安装
  npm i puppeteer
  注意先安装nodejs,在nodejs文件的根目录下执行(与npm文件同级)。
  安装过程中会下载chromium,大约120M。
  经过两天(大约10个小时)的探索,绕过了很多异步的坑,作者对puppeteer和nodejs有一定的把握。
  长图,抢博客文章列表:
  
  爬博客文章
  以csdn博客为例,文章的内容需要通过点击“阅读全文”获取,使得只能阅读dom的脚本失效。
  /**
* load blog.csdn.net article to local files
**/
const puppeteer = require('puppeteer');
//emulate iphone
const userAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1';
const workPath = './contents';
const fs = require("fs");
if (!fs.existsSync(workPath)) {
fs.mkdirSync(workPath)
}
//base url
const rootUrl = 'https://blog.csdn.net/';
//max wait milliseconds
const maxWait = 100;
//max loop scroll times
const makLoop = 10;
(async () => {
let url;
let countUrl=0;
const browser = await puppeteer.launch({headless: false});//set headless: true will hide chromium UI
const page = await browser.newPage();
await page.setUserAgent(userAgent);
await page.setViewport({width:414, height:736});
await page.setRequestInterception(true);
//filter to block images
page.on('request', request => {
if (request.resourceType() === 'image')
request.abort();
else
request.continue();
});
await page.goto(rootUrl);
for(let i= 0; iwindow.scrollTo(0, document.body.scrollHeight));
await page.waitForNavigation({timeout:maxWait,waitUntil: ['networkidle0']});
}catch(err){
console.log('scroll to bottom and then wait '+maxWait+'ms.');
}
}
await page.screenshot({path: workPath+'/screenshot.png',fullPage: true, quality :100, type :'jpeg'});
//#feedlist_id li[data-type="blog"] a
const sel = '#feedlist_id li[data-type="blog"] h2 a';
const hrefs = await page.evaluate((sel) => {
let elements = Array.from(document.querySelectorAll(sel));
let links = elements.map(element => {
return element.href
})
return links;
}, sel);
console.log('total links: '+hrefs.length);
process();
async function process(){
if(countUrl {
if (request.resourceType() === 'image')
request.abort();
else
request.continue();
});
await tab.goto(url);
//execute tap request
try{
await tab.tap('.read_more_btn');
}catch(err){
console.log('there\'s none read more button. No need to TAP');
}
let title = await tab.evaluate(() => document.querySelector('#article .article_title').innerText);
let contents = await tab.evaluate(() => document.querySelector('#article .article_content').innerText);
contents = 'TITLE: '+title+'\nURL: '+url+'\nCONTENTS: \n'+contents;
const fs = require("fs");
fs.writeFileSync(workPath+'/'+tab.url().substring(tab.url().lastIndexOf('/'),tab.url().length)+'.txt',contents);
console.log(title + " has been downloaded to local.");
await tab.close();
}catch(err){
console.log('url: '+tab.url()+' \n'+err.toString());
}finally{
process();
}
}
})();
  实施过程
  录屏可以在我的公众号查看,截图如下:
  
  结果
  文章内容列表:
  
  文章内容:
  
  结束语
  之前我想既然nodejs使用的是JavaScript脚本语言,那么它一定能够处理网页的JavaScript内容,但是我还没有找到一个合适/高效的库。直到我找到了 puppeteer,我才决定试水。
  说了这么多,nodejs的异步性真是让人头疼。这几百行代码我折腾了10个小时。
  您可以在代码中展开 process() 方法并使用 async.eachSeries。我使用的递归方法不是最佳解决方案。
  事实上,一个一个地处理是没有效率的。本来我写了一个异步关闭浏览器的方法:
<p>let tryCloseBrowser = setInterval(function(){
console.log("check if any process running...")
if(countDown

nodejs抓取动态网页(我目前正在使用Express.js创建我的网站。。)

网站优化优采云 发表了文章 • 0 个评论 • 54 次浏览 • 2022-02-06 05:28 • 来自相关话题

  nodejs抓取动态网页(我目前正在使用Express.js创建我的网站。。)
  我目前正在使用 Express.js 来创建我的 网站。我的主服务器脚本称为 index.coffee。我还创建了一个脚本 request.js,它发出一个 GET 请求并显示响应
   console.log(list);
  我从控制台运行脚本没有任何问题:node request.js
  我的问题是:如何让页面上的“获取此列表”按钮通过在同一页面上显示列表来响应点击(即request.js在服务器上执行并显示结果)?
  app.js
  /**
* Module dependencies.
*/
var express = require('express')
, routes = require('./routes');
var app = module.exports = express.createServer();
// Configuration
app.configure(function(){
app.set('views', __dirname + '/views');
app.set ('view engine', 'coffee');
app.register('.coffee', require('coffeekup').adapters.express);
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
app.configure('development', function(){
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
app.use(express.errorHandler());
});
app.get('/', function(req, res) {
res.render('index',{ layout: false });
});
app.listen(3000);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
  索引咖啡
  doctype 5
html ->
head ->
body
  p->“嘿” 查看全部

  nodejs抓取动态网页(我目前正在使用Express.js创建我的网站。。)
  我目前正在使用 Express.js 来创建我的 网站。我的主服务器脚本称为 index.coffee。我还创建了一个脚本 request.js,它发出一个 GET 请求并显示响应
   console.log(list);
  我从控制台运行脚本没有任何问题:node request.js
  我的问题是:如何让页面上的“获取此列表”按钮通过在同一页面上显示列表来响应点击(即request.js在服务器上执行并显示结果)?
  app.js
  /**
* Module dependencies.
*/
var express = require('express')
, routes = require('./routes');
var app = module.exports = express.createServer();
// Configuration
app.configure(function(){
app.set('views', __dirname + '/views');
app.set ('view engine', 'coffee');
app.register('.coffee', require('coffeekup').adapters.express);
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
app.configure('development', function(){
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
app.use(express.errorHandler());
});
app.get('/', function(req, res) {
res.render('index',{ layout: false });
});
app.listen(3000);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
  索引咖啡
  doctype 5
html ->
head ->
body
  p->“嘿”

nodejs抓取动态网页( 如何在NodeJS的活跃生态系统帮助下高效地抓取Web内容)

网站优化优采云 发表了文章 • 0 个评论 • 462 次浏览 • 2022-02-06 01:15 • 来自相关话题

  nodejs抓取动态网页(
如何在NodeJS的活跃生态系统帮助下高效地抓取Web内容)
  
  作者 | Shenesh Perera 翻译 | 王强策划| 李俊辰 Javascript 多年来发展迅速,引入了一个叫做 NodeJS 的运行时,因此它已经成为最流行和使用最广泛的语言之一。无论您是在编写 Web 应用程序还是移动应用程序,您都可以在 Javascript 生态系统中找到合适的工具。本文是关于如何借助 NodeJS 的活跃生态系统有效地抓取 Web 内容,以满足大多数相关需求。
  本文首发于网站,经网站授权由InfoQ中文站翻译分享。
  前提
  本文章主要针对有一定Javascript开发经验的开发者。但是,如果您熟悉 Web 内容抓取,那么您可以从本文中学到很多东西,而无需任何 Javascript 经验。
  成就
  阅读此 文章 可以帮助读者:
  了解 NodeJS:简介
  Javascript 是一种简单而现代的语言,最初是为了向 网站 浏览器访问添加动态行为而创建的。网站加载后,Javascript 通过浏览器的 JS 引擎运行并翻译成一堆计算机可以理解的代码。为了让 Javascript 与您的浏览器交互,后者提供了一个运行时环境(文档、窗口等)。
  换句话说,Javascript 编程语言不能直接与计算机或它们的资源交互,或操纵它们。例如,在 Web 服务器中,服务器必须能够与文件系统交互才能读取文件或将记录存储在数据库中。
  NodeJS 的想法是让 Javascript 不仅可以在客户端运行,还可以在服务器端运行。为此,资深开发人员 Ryan Dahl 将 Google Chrome 的 v8 JS 引擎嵌入到名为 Node.js 的 C++ 程序中。所以 NodeJS 是一个运行时环境,它允许用 Javascript 编写的应用程序也可以在服务器上运行。
  大多数语言(例如 C 或 C++)使用多个线程来处理并发,相比之下 NodeJS 只使用单个主线程,并借助 Event Loop 以非阻塞的方式使用它来执行任务。我们可以轻松地设置一个简单的 Web 服务器,如下所示:
  1const http = require(&#x27;http&#x27;);
2const PORT = 3000;
3const server = http.createServer((req, res) => {
4  res.statusCode = 200;
5  res.setHeader(&#x27;Content-Type&#x27;, &#x27;text/plain&#x27;);
6  res.end(&#x27;Hello World&#x27;);
7});
8server.listen(port, () => {
9  console.log(`Server running at PORT:${port}/`);
10});
11
  如果您安装了 NodeJS,请运行 node.js(删除符号),然后打开浏览器并导航到 localhost:3000 以查看文本“HelloWorld”。NodeJS 非常适合 I/O 密集型应用程序。HTTP 客户端:查询 Web
  HTTP 客户端是一种向服务器发送请求,然后从服务器接收响应的工具。本文中讨论的大多数工具在后台使用 HTTP 客户端来查询您将尝试抓取的 网站 服务器。
  RequestRequest 是 Javascript 生态系统中使用最广泛的 HTTP 客户端之一,但是现在 Request 库的作者已经正式表示不建议大家继续使用它。不是说不能用,还有很多库还在用,真的很好用。使用 Request 发出 HTTP 请求非常简单:
  1const request = require(&#x27;request&#x27;)
2request(&#x27;https://www.reddit.com/r/progr ... 27%3B, function (
3  error,
4  response,
5  body) {
6  console.error(&#x27;error:&#x27;, error)
7  console.log(&#x27;body:&#x27;, body)
8})
9
  你可以在 Github 上找到 Request 库并运行 npm install request 来安装它。在这里您可以参考弃用通知和详细信息:
  AxiosAxios 是一个基于 Promise 的 HTTP 客户端,在浏览器和 NodeJS 中运行。如果你使用 Typescript,axios 可以覆盖内置类型。通过axios发起HTTP请求非常简单。默认情况下它具有内置的 Promise 支持,不像 Request,它必须使用回调:
  1const axios = require(&#x27;axios&#x27;)
2axios
3    .get(&#x27;https://www.reddit.com/r/programming.json&#x27;)
4    .then((response) => {
5        console.log(response)
6    })
7    .catch((error) => {
8        console.error(error)
9    });
10
  1如果你喜欢 Promises API 的 async/await 语法糖,那么也可以用它们,但由于顶级的 await 仍处于第 3 阶段,我们只能用 Async Function 来代替:
2
  1async function getForum() {
2    try {
3        const response = await axios.get(
4            &#x27;https://www.reddit.com/r/progr ... 27%3B
5        )
6        console.log(response)
7    } catch (error) {
8        console.error(error)
9    }
10}
11
  你只需调用getForum!你可以在 Github 上找到 Axios 库并运行 npm install axios 来安装它。
  超级代理
  与 Axios 类似,Superagent 是另一个强大的 HTTP 客户端,支持 Promises 和 async/await 语法糖。它的 API 和 Axios 一样简单,但 Superagent 依赖较多,不太流行。
  在 Superagent 中,HTTP 请求是使用 Promise、async/await 或回调发出的,如下所示:
  1const superagent = require("superagent")
2const forumURL = "https://www.reddit.com/r/programming.json"
3// callbacks
4superagent
5    .get(forumURL)
6    .end((error, response) => {
7        console.log(response)
8    })
9// promises
10superagent
11    .get(forumURL)
12    .then((response) => {
13        console.log(response)
14    })
15    .catch((error) => {
16        console.error(error)
17    })
18// promises with async/await
19async function getForum() {
20    try {
21        const response = await superagent.get(forumURL)
22        console.log(response)
23    } catch (error) {
24        console.error(error)
25    }
26
  你可以在 Github 上找到 Superagent 库并运行 npm install superagent 来安装它。
  对于下面介绍的网页抓取工具,本文将使用 Axios 作为 HTTP 客户端。正则表达式:艰难之路
  在没有任何依赖关系的情况下开始抓取 Web 内容的最简单方法是在使用 HTTP 客户端查询网页时收到的 HTML 字符串上应用一组正则表达式 - 但这种方法绕路太远了。正则表达式不是那么灵活,许多专业人士和爱好者很难编写正确的正则表达式。
  对于复杂的网页抓取任务,正则表达式很快就会成为瓶颈。无论如何,让我们先尝试一下。假设有一个带有用户名的标签,我们需要用户名在其中,那么使用正则表达式时的方法几乎是这样的:
  1const htmlString = &#x27;Username: John Doe&#x27;
2const result = htmlString.match(/(.+)/)
3console.log(result[1], result[1].split(": ")[1])
4// Username: John Doe, John Doe
5
  在 Javascript 中,match() 通常返回一个收录与正则表达式匹配的所有内容的数组。第二个元素(在索引 1 处)将找到标签的 textContent 或 innerHTML,这正是我们想要的。但是这个结果将收录一些我们不需要的文本(“用户名:”),必须将其删除。如您所见,这种方法对于一个非常简单的用例来说很麻烦。所以我们应该使用 HTML 解析器之类的工具,这些工具将在后面讨论。Cheerio:在其核心遍历 DOM JQuery Cheerio 是一个高效且轻量级的库,允许您在服务器端使用 JQuery 丰富而强大的 API。如果您以前使用过 JQuery,那么使用 Cheerio 很容易上手。它消除了 DOM 的所有不一致和与浏览器相关的特性,并公开了一个用于解析和操作 DOM 的高效 API。
  1const cheerio = require(&#x27;cheerio&#x27;)
2const $ = cheerio.load(&#x27;
3
  你好世界
  ')
  $('h2.title').text('你好!')
  $('h2').addClass('欢迎')
  $.html()
  //
  你好呀!
  如您所见,Cheerio 的工作方式与 JQuery 非常相似。但是,它与 Web 浏览器的工作方式不同,这意味着它不能:
  因此,如果您尝试抓取的 网站 或 Web 应用程序有很多 Javascript 内容(例如“单页应用程序”),那么 Cheerio 不是您的最佳选择,您可能不得不依赖以下讨论一些其他选项。
  为了展示 Cheerio 的强大功能,我们将尝试爬取 Reddit 中的 r/programming 论坛以获取帖子标题列表。
  首先,运行以下命令安装 Cheerio 和 axios:npm install Cheerio axios。然后创建一个名为 crawler.js 的新文件并复制/粘贴以下代码:
  1const axios = require(&#x27;axios&#x27;);
2const cheerio = require(&#x27;cheerio&#x27;);
3const getPostTitles = async () => {
4    try {
5        const { data } = await axios.get(
6            &#x27;https://old.reddit.com/r/programming/&#x27;
7        );
8        const $ = cheerio.load(data);
9        const postTitles = [];
10        $(&#x27;div > p.title > a&#x27;).each((_idx, el) => {
11            const postTitle = $(el).text()
12            postTitles.push(postTitle)
13        });
14        return postTitles;
15    } catch (error) {
16        throw error;
17    }
18};
19getPostTitles()
20.then((postTitles) => console.log(postTitles));
21
  getPostTitles() 是一个异步函数,用于抓取旧 reddit 的 r/programming 论坛。首先,使用来自 axios HTTP 客户端库的简单 HTTP GET 请求获取 网站 的 HTML,然后使用cheerio.load() 函数将 html 数据提供给 Cheerio。
  接下来,使用浏览器的开发工具,您可以获得通常可以针对所有明信片的选择器。如果您使用过 JQuery,那么 $('div &gt; p.title &gt; a') 非常熟悉。这将获取所有帖子,因为您只想获取每个帖子的标题,您必须遍历每个帖子(使用 each() 函数进行迭代)。
  要从每个标题中提取文本,必须在 Cheerio 的帮助下获取 DOM 元素(当前元素的 el)。然后在每个元素上调用 text() 以获取文本。
  现在,您可以弹出一个终端并运行 node crawler.js,您会看到一长串大约 25 或 26 个帖子标题。虽然这是一个非常简单的用例,但它显示了 Cheerio 提供的 API 是多么容易使用。
  如果您的用例需要执行 Javascript 和加载外部资源,这里有几个选项可供考虑。JSDOM:节点的 DOM
  JSDOM 是 NodeJS 中使用的文档对象模型 (DOM) 的纯 Javascript 实现。如前所述,DOM 不适用于 Node,而 JSDOM 是最接近的替代品。它或多或少地模拟了浏览器的机制。
  一旦创建了 DOM,我们就可以通过编程方式与要抓取的 Web 应用程序或 网站 进行交互,还可以完成单击按钮之类的操作。如果您熟悉 DOM 的工作原理,那么 JSDOM 也非常易于使用。
  1const { JSDOM } = require(&#x27;jsdom&#x27;)
2const { document } = new JSDOM(
3    &#x27;
4
  你好世界
  '
  )。窗户
  constheading = document.querySelector('.title')
  heading.textContent = '你好!'
  heading.classList.add('欢迎')
  标题.innerHTML
  //
  你好呀!
  如您所见,JSDOM 创建了一个 DOM,然后您可以使用与浏览器 DOM 相同的方法和属性对其进行操作。为了演示如何使用 JSDOM 与 网站 交互,我们将在 Redditr/programming 论坛上发表第一篇文章,点赞它,然后我们将验证该帖子是否已被点赞。首先运行以下命令安装jsdom和axios:
  1npm install jsdom axios
2
  1然后创建一个名为 rawler.js 的文件,并复制 / 粘贴以下代码:
2
  1const { JSDOM } = require("jsdom")
2const axios = require(&#x27;axios&#x27;)
3const upvoteFirstPost = async () => {
4  try {
5    const { data } = await axios.get("https://old.reddit.com/r/programming/");
6    const dom = new JSDOM(data, {
7      runScripts: "dangerously",
8      resources: "usable"
9    });
10    const { document } = dom.window;
11    const firstPost = document.querySelector("div > div.midcol > div.arrow");
12    firstPost.click();
13    const isUpvoted = firstPost.classList.contains("upmod");
14    const msg = isUpvoted
15      ? "Post has been upvoted successfully!"
16      : "The post has not been upvoted!";
17    return msg;
18  } catch (error) {
19    throw error;
20  }
21};
22upvoteFirstPost().then(msg => console.log(msg));
23
  upvoteFirstPost() 是一个异步函数,它将获得 r/programming 中的第一个帖子并对其进行投票。为此,axios 发送一个 HTTP GET 请求以获取指定 URL 的 HTML。然后将先前获取的 HTML 馈送到 JSDOM 以创建新的 DOM。JSDOM 构造函数将 HTML 作为第一个参数,将选项作为第二个参数。添加的 2 个选项执行以下功能: 查看全部

  nodejs抓取动态网页(
如何在NodeJS的活跃生态系统帮助下高效地抓取Web内容)
  
  作者 | Shenesh Perera 翻译 | 王强策划| 李俊辰 Javascript 多年来发展迅速,引入了一个叫做 NodeJS 的运行时,因此它已经成为最流行和使用最广泛的语言之一。无论您是在编写 Web 应用程序还是移动应用程序,您都可以在 Javascript 生态系统中找到合适的工具。本文是关于如何借助 NodeJS 的活跃生态系统有效地抓取 Web 内容,以满足大多数相关需求。
  本文首发于网站,经网站授权由InfoQ中文站翻译分享。
  前提
  本文章主要针对有一定Javascript开发经验的开发者。但是,如果您熟悉 Web 内容抓取,那么您可以从本文中学到很多东西,而无需任何 Javascript 经验。
  成就
  阅读此 文章 可以帮助读者:
  了解 NodeJS:简介
  Javascript 是一种简单而现代的语言,最初是为了向 网站 浏览器访问添加动态行为而创建的。网站加载后,Javascript 通过浏览器的 JS 引擎运行并翻译成一堆计算机可以理解的代码。为了让 Javascript 与您的浏览器交互,后者提供了一个运行时环境(文档、窗口等)。
  换句话说,Javascript 编程语言不能直接与计算机或它们的资源交互,或操纵它们。例如,在 Web 服务器中,服务器必须能够与文件系统交互才能读取文件或将记录存储在数据库中。
  NodeJS 的想法是让 Javascript 不仅可以在客户端运行,还可以在服务器端运行。为此,资深开发人员 Ryan Dahl 将 Google Chrome 的 v8 JS 引擎嵌入到名为 Node.js 的 C++ 程序中。所以 NodeJS 是一个运行时环境,它允许用 Javascript 编写的应用程序也可以在服务器上运行。
  大多数语言(例如 C 或 C++)使用多个线程来处理并发,相比之下 NodeJS 只使用单个主线程,并借助 Event Loop 以非阻塞的方式使用它来执行任务。我们可以轻松地设置一个简单的 Web 服务器,如下所示:
  1const http = require(&#x27;http&#x27;);
2const PORT = 3000;
3const server = http.createServer((req, res) => {
4  res.statusCode = 200;
5  res.setHeader(&#x27;Content-Type&#x27;, &#x27;text/plain&#x27;);
6  res.end(&#x27;Hello World&#x27;);
7});
8server.listen(port, () => {
9  console.log(`Server running at PORT:${port}/`);
10});
11
  如果您安装了 NodeJS,请运行 node.js(删除符号),然后打开浏览器并导航到 localhost:3000 以查看文本“HelloWorld”。NodeJS 非常适合 I/O 密集型应用程序。HTTP 客户端:查询 Web
  HTTP 客户端是一种向服务器发送请求,然后从服务器接收响应的工具。本文中讨论的大多数工具在后台使用 HTTP 客户端来查询您将尝试抓取的 网站 服务器。
  RequestRequest 是 Javascript 生态系统中使用最广泛的 HTTP 客户端之一,但是现在 Request 库的作者已经正式表示不建议大家继续使用它。不是说不能用,还有很多库还在用,真的很好用。使用 Request 发出 HTTP 请求非常简单:
  1const request = require(&#x27;request&#x27;)
2request(&#x27;https://www.reddit.com/r/progr ... 27%3B, function (
3  error,
4  response,
5  body) {
6  console.error(&#x27;error:&#x27;, error)
7  console.log(&#x27;body:&#x27;, body)
8})
9
  你可以在 Github 上找到 Request 库并运行 npm install request 来安装它。在这里您可以参考弃用通知和详细信息:
  AxiosAxios 是一个基于 Promise 的 HTTP 客户端,在浏览器和 NodeJS 中运行。如果你使用 Typescript,axios 可以覆盖内置类型。通过axios发起HTTP请求非常简单。默认情况下它具有内置的 Promise 支持,不像 Request,它必须使用回调:
  1const axios = require(&#x27;axios&#x27;)
2axios
3    .get(&#x27;https://www.reddit.com/r/programming.json&#x27;)
4    .then((response) => {
5        console.log(response)
6    })
7    .catch((error) => {
8        console.error(error)
9    });
10
  1如果你喜欢 Promises API 的 async/await 语法糖,那么也可以用它们,但由于顶级的 await 仍处于第 3 阶段,我们只能用 Async Function 来代替:
2
  1async function getForum() {
2    try {
3        const response = await axios.get(
4            &#x27;https://www.reddit.com/r/progr ... 27%3B
5        )
6        console.log(response)
7    } catch (error) {
8        console.error(error)
9    }
10}
11
  你只需调用getForum!你可以在 Github 上找到 Axios 库并运行 npm install axios 来安装它。
  超级代理
  与 Axios 类似,Superagent 是另一个强大的 HTTP 客户端,支持 Promises 和 async/await 语法糖。它的 API 和 Axios 一样简单,但 Superagent 依赖较多,不太流行。
  在 Superagent 中,HTTP 请求是使用 Promise、async/await 或回调发出的,如下所示:
  1const superagent = require("superagent")
2const forumURL = "https://www.reddit.com/r/programming.json"
3// callbacks
4superagent
5    .get(forumURL)
6    .end((error, response) => {
7        console.log(response)
8    })
9// promises
10superagent
11    .get(forumURL)
12    .then((response) => {
13        console.log(response)
14    })
15    .catch((error) => {
16        console.error(error)
17    })
18// promises with async/await
19async function getForum() {
20    try {
21        const response = await superagent.get(forumURL)
22        console.log(response)
23    } catch (error) {
24        console.error(error)
25    }
26
  你可以在 Github 上找到 Superagent 库并运行 npm install superagent 来安装它。
  对于下面介绍的网页抓取工具,本文将使用 Axios 作为 HTTP 客户端。正则表达式:艰难之路
  在没有任何依赖关系的情况下开始抓取 Web 内容的最简单方法是在使用 HTTP 客户端查询网页时收到的 HTML 字符串上应用一组正则表达式 - 但这种方法绕路太远了。正则表达式不是那么灵活,许多专业人士和爱好者很难编写正确的正则表达式。
  对于复杂的网页抓取任务,正则表达式很快就会成为瓶颈。无论如何,让我们先尝试一下。假设有一个带有用户名的标签,我们需要用户名在其中,那么使用正则表达式时的方法几乎是这样的:
  1const htmlString = &#x27;Username: John Doe&#x27;
2const result = htmlString.match(/(.+)/)
3console.log(result[1], result[1].split(": ")[1])
4// Username: John Doe, John Doe
5
  在 Javascript 中,match() 通常返回一个收录与正则表达式匹配的所有内容的数组。第二个元素(在索引 1 处)将找到标签的 textContent 或 innerHTML,这正是我们想要的。但是这个结果将收录一些我们不需要的文本(“用户名:”),必须将其删除。如您所见,这种方法对于一个非常简单的用例来说很麻烦。所以我们应该使用 HTML 解析器之类的工具,这些工具将在后面讨论。Cheerio:在其核心遍历 DOM JQuery Cheerio 是一个高效且轻量级的库,允许您在服务器端使用 JQuery 丰富而强大的 API。如果您以前使用过 JQuery,那么使用 Cheerio 很容易上手。它消除了 DOM 的所有不一致和与浏览器相关的特性,并公开了一个用于解析和操作 DOM 的高效 API。
  1const cheerio = require(&#x27;cheerio&#x27;)
2const $ = cheerio.load(&#x27;
3
  你好世界
  ')
  $('h2.title').text('你好!')
  $('h2').addClass('欢迎')
  $.html()
  //
  你好呀!
  如您所见,Cheerio 的工作方式与 JQuery 非常相似。但是,它与 Web 浏览器的工作方式不同,这意味着它不能:
  因此,如果您尝试抓取的 网站 或 Web 应用程序有很多 Javascript 内容(例如“单页应用程序”),那么 Cheerio 不是您的最佳选择,您可能不得不依赖以下讨论一些其他选项。
  为了展示 Cheerio 的强大功能,我们将尝试爬取 Reddit 中的 r/programming 论坛以获取帖子标题列表。
  首先,运行以下命令安装 Cheerio 和 axios:npm install Cheerio axios。然后创建一个名为 crawler.js 的新文件并复制/粘贴以下代码:
  1const axios = require(&#x27;axios&#x27;);
2const cheerio = require(&#x27;cheerio&#x27;);
3const getPostTitles = async () => {
4    try {
5        const { data } = await axios.get(
6            &#x27;https://old.reddit.com/r/programming/&#x27;
7        );
8        const $ = cheerio.load(data);
9        const postTitles = [];
10        $(&#x27;div > p.title > a&#x27;).each((_idx, el) => {
11            const postTitle = $(el).text()
12            postTitles.push(postTitle)
13        });
14        return postTitles;
15    } catch (error) {
16        throw error;
17    }
18};
19getPostTitles()
20.then((postTitles) => console.log(postTitles));
21
  getPostTitles() 是一个异步函数,用于抓取旧 reddit 的 r/programming 论坛。首先,使用来自 axios HTTP 客户端库的简单 HTTP GET 请求获取 网站 的 HTML,然后使用cheerio.load() 函数将 html 数据提供给 Cheerio。
  接下来,使用浏览器的开发工具,您可以获得通常可以针对所有明信片的选择器。如果您使用过 JQuery,那么 $('div &gt; p.title &gt; a') 非常熟悉。这将获取所有帖子,因为您只想获取每个帖子的标题,您必须遍历每个帖子(使用 each() 函数进行迭代)。
  要从每个标题中提取文本,必须在 Cheerio 的帮助下获取 DOM 元素(当前元素的 el)。然后在每个元素上调用 text() 以获取文本。
  现在,您可以弹出一个终端并运行 node crawler.js,您会看到一长串大约 25 或 26 个帖子标题。虽然这是一个非常简单的用例,但它显示了 Cheerio 提供的 API 是多么容易使用。
  如果您的用例需要执行 Javascript 和加载外部资源,这里有几个选项可供考虑。JSDOM:节点的 DOM
  JSDOM 是 NodeJS 中使用的文档对象模型 (DOM) 的纯 Javascript 实现。如前所述,DOM 不适用于 Node,而 JSDOM 是最接近的替代品。它或多或少地模拟了浏览器的机制。
  一旦创建了 DOM,我们就可以通过编程方式与要抓取的 Web 应用程序或 网站 进行交互,还可以完成单击按钮之类的操作。如果您熟悉 DOM 的工作原理,那么 JSDOM 也非常易于使用。
  1const { JSDOM } = require(&#x27;jsdom&#x27;)
2const { document } = new JSDOM(
3    &#x27;
4
  你好世界
  '
  )。窗户
  constheading = document.querySelector('.title')
  heading.textContent = '你好!'
  heading.classList.add('欢迎')
  标题.innerHTML
  //
  你好呀!
  如您所见,JSDOM 创建了一个 DOM,然后您可以使用与浏览器 DOM 相同的方法和属性对其进行操作。为了演示如何使用 JSDOM 与 网站 交互,我们将在 Redditr/programming 论坛上发表第一篇文章,点赞它,然后我们将验证该帖子是否已被点赞。首先运行以下命令安装jsdom和axios:
  1npm install jsdom axios
2
  1然后创建一个名为 rawler.js 的文件,并复制 / 粘贴以下代码:
2
  1const { JSDOM } = require("jsdom")
2const axios = require(&#x27;axios&#x27;)
3const upvoteFirstPost = async () => {
4  try {
5    const { data } = await axios.get("https://old.reddit.com/r/programming/";);
6    const dom = new JSDOM(data, {
7      runScripts: "dangerously",
8      resources: "usable"
9    });
10    const { document } = dom.window;
11    const firstPost = document.querySelector("div > div.midcol > div.arrow");
12    firstPost.click();
13    const isUpvoted = firstPost.classList.contains("upmod");
14    const msg = isUpvoted
15      ? "Post has been upvoted successfully!"
16      : "The post has not been upvoted!";
17    return msg;
18  } catch (error) {
19    throw error;
20  }
21};
22upvoteFirstPost().then(msg => console.log(msg));
23
  upvoteFirstPost() 是一个异步函数,它将获得 r/programming 中的第一个帖子并对其进行投票。为此,axios 发送一个 HTTP GET 请求以获取指定 URL 的 HTML。然后将先前获取的 HTML 馈送到 JSDOM 以创建新的 DOM。JSDOM 构造函数将 HTML 作为第一个参数,将选项作为第二个参数。添加的 2 个选项执行以下功能:

nodejs抓取动态网页(使用ScrapySharp快速从网页中采集数据中的采集方案介绍)

网站优化优采云 发表了文章 • 0 个评论 • 107 次浏览 • 2022-02-05 01:21 • 来自相关话题

  nodejs抓取动态网页(使用ScrapySharp快速从网页中采集数据中的采集方案介绍)
  在上一篇文章文章中,我介绍了使用ScrapySharp快速采集从网页中获取数据。该方法是通过直接发送的Http请求获取原创页面信息。对于静态网页非常有效,但是网站中也有很多页面内容并不是全部存储在原创页面中的,而且很多内容是通过javascript动态生成的,而这些数据是前面无法抓取的方法。本文简单介绍了动态网页的采集方案。
  对于这样的网页数据采集,往往会使用浏览器引擎来加载整个页面,加载后输出完整的页面,然后使用ScrapySharp等工具进行解析。常用的方法有以下几种:
  使用 WebBrowser 控件
  这种方法被认为已被大多数 .Net 开发人员使用。由于WebBrowser直接使用与操作系统集成的IE,无需下载第三方控件,比较简单快捷。但是,它只是一个演示控件,并没有提供很多接口。集成一些扩展比较麻烦。
  使用网络浏览器
  PhantomJS 是一个带有 Webkit 核心的无界面浏览器。它的一个特点就是可以很方便的集成javascript脚本,所以开发扩展更加方便,也可以用在服务器端无法使用UI控件的地方。目前网上的大部分解决方案都是这种。把我看过的几篇文章抄录到这里文章,就不做详细介绍了:
  这个程序本身是比较方便和强大的,但是在试用过程中还是存在一些问题,比如有些网页不是很规范,不能正确解析,或者有乱码。
  使用 CEF 控件
  CEF 是 Chromium Embedded Framework,谷歌提供的 Chrome 集成解决方案。相对而言,它提供了更底层的API,我们可以进行更强大的自定义(当然也需要更多的工作)。加快内容解析。
  直接解析Javascript模拟渲染
  上述方案虽然可以简单正确地获取完整的解析页面,但存在一个性能问题:速度很慢。虽然浏览器是由顶级专家开发的,但页面本身的渲染是一个非常复杂的过程。使用上述工具完全渲染一个页面仍然需要几秒钟,而且资源开销不小,无法支持大规模数据。采集。
  在大多数情况下,这不是什么大问题,但是如果你更关注性能问题,有一个比较原创的方法来解决它,那就是分析网页的JS的工作原理,并模拟浏览器只执行与内容相关的 JS。手动获取输出的内容。
  这样主要是需要一个javascript引擎,已经提供了大量的js引擎,基本没有问题。它的主要问题在于需要对网页进行自定义和分析,而这些网页的大部分JS都采用了一定的混淆策略,不易分析,而且往往需要大量的时间去调试。 查看全部

  nodejs抓取动态网页(使用ScrapySharp快速从网页中采集数据中的采集方案介绍)
  在上一篇文章文章中,我介绍了使用ScrapySharp快速采集从网页中获取数据。该方法是通过直接发送的Http请求获取原创页面信息。对于静态网页非常有效,但是网站中也有很多页面内容并不是全部存储在原创页面中的,而且很多内容是通过javascript动态生成的,而这些数据是前面无法抓取的方法。本文简单介绍了动态网页的采集方案。
  对于这样的网页数据采集,往往会使用浏览器引擎来加载整个页面,加载后输出完整的页面,然后使用ScrapySharp等工具进行解析。常用的方法有以下几种:
  使用 WebBrowser 控件
  这种方法被认为已被大多数 .Net 开发人员使用。由于WebBrowser直接使用与操作系统集成的IE,无需下载第三方控件,比较简单快捷。但是,它只是一个演示控件,并没有提供很多接口。集成一些扩展比较麻烦。
  使用网络浏览器
  PhantomJS 是一个带有 Webkit 核心的无界面浏览器。它的一个特点就是可以很方便的集成javascript脚本,所以开发扩展更加方便,也可以用在服务器端无法使用UI控件的地方。目前网上的大部分解决方案都是这种。把我看过的几篇文章抄录到这里文章,就不做详细介绍了:
  这个程序本身是比较方便和强大的,但是在试用过程中还是存在一些问题,比如有些网页不是很规范,不能正确解析,或者有乱码。
  使用 CEF 控件
  CEF 是 Chromium Embedded Framework,谷歌提供的 Chrome 集成解决方案。相对而言,它提供了更底层的API,我们可以进行更强大的自定义(当然也需要更多的工作)。加快内容解析。
  直接解析Javascript模拟渲染
  上述方案虽然可以简单正确地获取完整的解析页面,但存在一个性能问题:速度很慢。虽然浏览器是由顶级专家开发的,但页面本身的渲染是一个非常复杂的过程。使用上述工具完全渲染一个页面仍然需要几秒钟,而且资源开销不小,无法支持大规模数据。采集。
  在大多数情况下,这不是什么大问题,但是如果你更关注性能问题,有一个比较原创的方法来解决它,那就是分析网页的JS的工作原理,并模拟浏览器只执行与内容相关的 JS。手动获取输出的内容。
  这样主要是需要一个javascript引擎,已经提供了大量的js引擎,基本没有问题。它的主要问题在于需要对网页进行自定义和分析,而这些网页的大部分JS都采用了一定的混淆策略,不易分析,而且往往需要大量的时间去调试。

nodejs抓取动态网页(1.为什么需要一个前端监控系统?技术选型监控的意义)

网站优化优采云 发表了文章 • 0 个评论 • 62 次浏览 • 2022-02-05 01:20 • 来自相关话题

  nodejs抓取动态网页(1.为什么需要一个前端监控系统?技术选型监控的意义)
  1. 为什么需要前端监控系统
  通常大型web项目中会有很多监控系统,比如后端服务API监控、接口存活、调用、延迟等监控,一般用于监控后端数据级别的信息界面。而对于大型的网站系统,从后端服务到前端展示会有很多层:内网VIP、CDN等。
  但是这些监控并不能准确反映用户看到的前端页面的状态,比如页面上第三方系统数据调用失败、模块加载异常、数据不正确、天窗茫然地打开。
  这时候就需要从前端 DOM 展示的角度去分析采集用户真实看到的东西,从而检测页面是否存在异常问题。
  2. 监控系统需要解决的问题
  当页面出现以下问题时,您需要及时通过邮件或短信方式向相关人员报告,以解决问题。
  3. 技术选型
  监控的含义和测试的含义本质上是一样的,都是对已经上线的功能进行回归测试,但不同的是监控需要长期持续的、反复出现的回归测试,而测试之后只需要做一次发射,市场投入。回归测试。
  由于监控和测试的性质是相同的,我们可以用测试的方法来做监控系统。在自动化测试技术遍地开花的时代,有很多有用的自动化工具,我们只需要整合这些自动化工具供我们使用。
  节点JS
  NodeJS 是一个 JavaScript 运行环境,NodeJS 支持非阻塞 I/O 和异步、事件驱动,这对于我们构建基于 DOM 元素的监控更为重要。
  幻影JS
  PhantomJS 是一个基于 webkit 的 JavaScript API。它使用 QtWebKit 作为其核心浏览器,并使用 webkit 来编译、解释和执行 JavaScript 代码。它可以做任何你可以在基于 webkit 的浏览器中做的事情。
  它不仅是一个不可见的浏览器,提供诸如 CSS 选择器、对 Web 标准的支持、DOM 操作、JSON、HTML5、Canvas、SVG 等,它还提供处理文件 I/O 的操作,让您可以读写文件到操作系统等。PhantomJS的用途很广泛,比如网络监控、网页截图、免浏览器网页测试、页面访问自动化等等。
  为什么不使用硒
  做自动化测试的同学一定了解 Selenium。可以使用Selenium在浏览器中执行测试用例,Selenium对各种平台和常见浏览器的支持都很好,但是Selenium上手难度稍大,使用Selenium需要在服务器端安装浏览器。
  考虑到监控的主要任务是监控而不是测试。系统不需要过多考虑兼容性,监控功能也比较简单,主要针对页面的功能回归测试,所以选择了PhantomJS。
  4. 架构设计架构概述
  架构如下图所示。
  (点击放大图片)
  
  架构简介
  对于 DOM 监控服务,在应用层面做了纵向划分:
  应用层面的垂直划分,实现应用的分布式部署,提升处理能力。后期的性能优化、系统改造和扩展也可以提高简单性。
  5. 解决方案(1) 前端规则入口
  这是一个独立的Web系统。系统主要用于采集用户输入的页面信息,页面对应的规则,显示错误信息。通过调用后端页面爬取服务完成页面检测任务,通过系统可以创建三种检测页面:定期监控、高级监控、可用性监控。
  日常监测
  输入一个页面地址和几个检测规则。注意这里的检测规则,我们将一些常用的检测点抽象成一个类似测试用例的语句。每个规则用于匹配页面上的一个 DOM 元素,并使用 DOM 元素的属性来匹配预期。如果匹配失败,系统会产生错误信息,并交由报警系统处理。
  匹配类型一般有这些类型:长度、文本、HTML、属性
  处理器类似于编程语言中的运算符:大于、大于或等于、小于、小于或等于、等于、正则。
  这样做的好处是,输入规则的人只需要知道一点 DOM 选择器的知识就可以上手。在内部,我们通常将规则交给测试工程师完成输入。
  高级监控
  主要用于提供高级页面测试功能,测试用例一般由经验丰富的工程师编写。这个测试用例写起来会有一定的学习成本,但是可以模拟网页操作,比如点击、鼠标移动等事件。以实现对页面信息的准确抓取。
  可用性监控
  可用性监控侧重于即时监控页面可访问性、内容正确性等更严重的问题。通常我们只需要在程序中启动一个Worker,不断获取页面的HTML来检测和匹配结果,所以我们选择NodeJS做异步页面爬取队列来高效快速的完成这种网络密集型任务。
  (2)主动报错页面脚本执行错误监控
  页面引入监控脚本,采集页面产生的错误事件信息,并自动上报给后端服务。在系统中可以汇总所有的错误信息,以及对应的客户端浏览器版本、操作系统、IP地址等。
  活动页面报告
  该功能需要对应的前端工程师调用代码中的报错API主动提交错误信息。主要使用场景包括,页面异步服务延迟无响应、模块降级、主动通知等。监控脚本提供了几个简单的API来完成这个任务。
  // error 方法调用后立即上报错误信息并发出邮件、短信通知
errorTracker.error('错误描述')
// info 方法调用后立即上报信息,并在单位时间内仅仅产生一条邮件、短信通知
errorTracker.info('信息描述')
// log 方法调用后由报错检测是否达到设置阀值,最终确认是否报错
errorTracker.log('日志信息')
  (3)后端页面抓取服务
  由于京东的很多页面内容是异步加载的,而首页、单品等系统有很多第三方异步接口调用,后端程序抓取的页面数据是同步的,动态JavaScript渲染的数据不能获得。所以你必须使用像 PhantomJS 这样可以模拟浏览器的工具。
  对于日常监控,我们使用 PhantomJS 模拟浏览器打开页面进行爬取,然后将监控规则解析成 JavaScript 代码片段执行并采集结果。
  对于高级监控,我们使用 PhantomJS 打开页面,将 jasmine、Mocha 等前端 JavaScript 测试框架注入到页面中,然后在页面上执行相应的输入测试用例并返回结果。
  常规队列生成器
  规则队列生成器会将规则系统采集的规则转换成消息队列,然后交给长期持久化处理器进行顺序处理。
  为什么要使用类似消息队列的处理方式?
  这与 PhantomJS 的性能是分不开的。通过多次实践发现,PhantomJS 不能很好的进行并发处理。当并发量过多时,CPU会过载,导致机器宕机。
  对于本地环境的虚拟机并发测试,数据并不理想,限制基本在ab -n 100 -c 50左右。所以为了防止并发带来的问题,我们选择使用消息队列来进行避免高并发导致服务不可用。
  类似消息队列的实现
  这里我们通过调用内部分布式缓存系统来生成一个消息队列。队列的产生其实可以参考数据接口——队列。最基本的模型是在缓存中创建一个KEY,然后根据队列数据结构的schema插入和提取数据。
  当然消息队列的中间介质可以根据你的实际情况选择,当然你也可以使用原生内存实现。这可能会导致应用程序和类似消息队列的内存争用。
  长效处理器
  长期持久处理器是由消费规则队列生成器生成的消息队列。
  长效处理实施
  在长时持久化处理器的具体实现中,我们充分利用了JavaScript的setInterval方法,不断的获取疲倦消息队列的内容并发送给规则转换器,再转发给负载均衡调度器。之后统一处理返回的结果,如邮件或短信报警。
  API
  PhantomJS服务可以作为公共API提供给客户端处理测试需求,通过HTTP调用API。在 API 的处理中,需要提供 HTTP 数据到规则和 PhantomJS 的转换。因此,HTTP 数据到规则的转换器得到了发展。
  PhantomJS 服务
  PhantomJS 服务是指将 PhantomJS 与 HTTP 服务和子进程相结合的服务处理 查看全部

  nodejs抓取动态网页(1.为什么需要一个前端监控系统?技术选型监控的意义)
  1. 为什么需要前端监控系统
  通常大型web项目中会有很多监控系统,比如后端服务API监控、接口存活、调用、延迟等监控,一般用于监控后端数据级别的信息界面。而对于大型的网站系统,从后端服务到前端展示会有很多层:内网VIP、CDN等。
  但是这些监控并不能准确反映用户看到的前端页面的状态,比如页面上第三方系统数据调用失败、模块加载异常、数据不正确、天窗茫然地打开。
  这时候就需要从前端 DOM 展示的角度去分析采集用户真实看到的东西,从而检测页面是否存在异常问题。
  2. 监控系统需要解决的问题
  当页面出现以下问题时,您需要及时通过邮件或短信方式向相关人员报告,以解决问题。
  3. 技术选型
  监控的含义和测试的含义本质上是一样的,都是对已经上线的功能进行回归测试,但不同的是监控需要长期持续的、反复出现的回归测试,而测试之后只需要做一次发射,市场投入。回归测试。
  由于监控和测试的性质是相同的,我们可以用测试的方法来做监控系统。在自动化测试技术遍地开花的时代,有很多有用的自动化工具,我们只需要整合这些自动化工具供我们使用。
  节点JS
  NodeJS 是一个 JavaScript 运行环境,NodeJS 支持非阻塞 I/O 和异步、事件驱动,这对于我们构建基于 DOM 元素的监控更为重要。
  幻影JS
  PhantomJS 是一个基于 webkit 的 JavaScript API。它使用 QtWebKit 作为其核心浏览器,并使用 webkit 来编译、解释和执行 JavaScript 代码。它可以做任何你可以在基于 webkit 的浏览器中做的事情。
  它不仅是一个不可见的浏览器,提供诸如 CSS 选择器、对 Web 标准的支持、DOM 操作、JSON、HTML5、Canvas、SVG 等,它还提供处理文件 I/O 的操作,让您可以读写文件到操作系统等。PhantomJS的用途很广泛,比如网络监控、网页截图、免浏览器网页测试、页面访问自动化等等。
  为什么不使用硒
  做自动化测试的同学一定了解 Selenium。可以使用Selenium在浏览器中执行测试用例,Selenium对各种平台和常见浏览器的支持都很好,但是Selenium上手难度稍大,使用Selenium需要在服务器端安装浏览器。
  考虑到监控的主要任务是监控而不是测试。系统不需要过多考虑兼容性,监控功能也比较简单,主要针对页面的功能回归测试,所以选择了PhantomJS。
  4. 架构设计架构概述
  架构如下图所示。
  (点击放大图片)
  
  架构简介
  对于 DOM 监控服务,在应用层面做了纵向划分:
  应用层面的垂直划分,实现应用的分布式部署,提升处理能力。后期的性能优化、系统改造和扩展也可以提高简单性。
  5. 解决方案(1) 前端规则入口
  这是一个独立的Web系统。系统主要用于采集用户输入的页面信息,页面对应的规则,显示错误信息。通过调用后端页面爬取服务完成页面检测任务,通过系统可以创建三种检测页面:定期监控、高级监控、可用性监控。
  日常监测
  输入一个页面地址和几个检测规则。注意这里的检测规则,我们将一些常用的检测点抽象成一个类似测试用例的语句。每个规则用于匹配页面上的一个 DOM 元素,并使用 DOM 元素的属性来匹配预期。如果匹配失败,系统会产生错误信息,并交由报警系统处理。
  匹配类型一般有这些类型:长度、文本、HTML、属性
  处理器类似于编程语言中的运算符:大于、大于或等于、小于、小于或等于、等于、正则。
  这样做的好处是,输入规则的人只需要知道一点 DOM 选择器的知识就可以上手。在内部,我们通常将规则交给测试工程师完成输入。
  高级监控
  主要用于提供高级页面测试功能,测试用例一般由经验丰富的工程师编写。这个测试用例写起来会有一定的学习成本,但是可以模拟网页操作,比如点击、鼠标移动等事件。以实现对页面信息的准确抓取。
  可用性监控
  可用性监控侧重于即时监控页面可访问性、内容正确性等更严重的问题。通常我们只需要在程序中启动一个Worker,不断获取页面的HTML来检测和匹配结果,所以我们选择NodeJS做异步页面爬取队列来高效快速的完成这种网络密集型任务。
  (2)主动报错页面脚本执行错误监控
  页面引入监控脚本,采集页面产生的错误事件信息,并自动上报给后端服务。在系统中可以汇总所有的错误信息,以及对应的客户端浏览器版本、操作系统、IP地址等。
  活动页面报告
  该功能需要对应的前端工程师调用代码中的报错API主动提交错误信息。主要使用场景包括,页面异步服务延迟无响应、模块降级、主动通知等。监控脚本提供了几个简单的API来完成这个任务。
  // error 方法调用后立即上报错误信息并发出邮件、短信通知
errorTracker.error('错误描述')
// info 方法调用后立即上报信息,并在单位时间内仅仅产生一条邮件、短信通知
errorTracker.info('信息描述')
// log 方法调用后由报错检测是否达到设置阀值,最终确认是否报错
errorTracker.log('日志信息')
  (3)后端页面抓取服务
  由于京东的很多页面内容是异步加载的,而首页、单品等系统有很多第三方异步接口调用,后端程序抓取的页面数据是同步的,动态JavaScript渲染的数据不能获得。所以你必须使用像 PhantomJS 这样可以模拟浏览器的工具。
  对于日常监控,我们使用 PhantomJS 模拟浏览器打开页面进行爬取,然后将监控规则解析成 JavaScript 代码片段执行并采集结果。
  对于高级监控,我们使用 PhantomJS 打开页面,将 jasmine、Mocha 等前端 JavaScript 测试框架注入到页面中,然后在页面上执行相应的输入测试用例并返回结果。
  常规队列生成器
  规则队列生成器会将规则系统采集的规则转换成消息队列,然后交给长期持久化处理器进行顺序处理。
  为什么要使用类似消息队列的处理方式?
  这与 PhantomJS 的性能是分不开的。通过多次实践发现,PhantomJS 不能很好的进行并发处理。当并发量过多时,CPU会过载,导致机器宕机。
  对于本地环境的虚拟机并发测试,数据并不理想,限制基本在ab -n 100 -c 50左右。所以为了防止并发带来的问题,我们选择使用消息队列来进行避免高并发导致服务不可用。
  类似消息队列的实现
  这里我们通过调用内部分布式缓存系统来生成一个消息队列。队列的产生其实可以参考数据接口——队列。最基本的模型是在缓存中创建一个KEY,然后根据队列数据结构的schema插入和提取数据。
  当然消息队列的中间介质可以根据你的实际情况选择,当然你也可以使用原生内存实现。这可能会导致应用程序和类似消息队列的内存争用。
  长效处理器
  长期持久处理器是由消费规则队列生成器生成的消息队列。
  长效处理实施
  在长时持久化处理器的具体实现中,我们充分利用了JavaScript的setInterval方法,不断的获取疲倦消息队列的内容并发送给规则转换器,再转发给负载均衡调度器。之后统一处理返回的结果,如邮件或短信报警。
  API
  PhantomJS服务可以作为公共API提供给客户端处理测试需求,通过HTTP调用API。在 API 的处理中,需要提供 HTTP 数据到规则和 PhantomJS 的转换。因此,HTTP 数据到规则的转换器得到了发展。
  PhantomJS 服务
  PhantomJS 服务是指将 PhantomJS 与 HTTP 服务和子进程相结合的服务处理

nodejs抓取动态网页(【系列文章目录】「项目源码(GitHub)」本篇目录)

网站优化优采云 发表了文章 • 0 个评论 • 78 次浏览 • 2022-02-03 05:10 • 来自相关话题

  nodejs抓取动态网页(【系列文章目录】「项目源码(GitHub)」本篇目录)
  重要链接:
  "系列文章目录"
  “项目源代码(GitHub)”
  本目录
  前言
  不出所料,我没能按时写完文章。时至今日,我已经没有任何愧疚感了,大概就是长大了。但话说回来,我曾经是一个每月写10篇文章的人。
  这个文章的主要内容是根据用户角色动态加载后台管理页面的菜单,关键点如下:
  在正文之前,让我告诉你这两周背后的故事:
  1、在官方活动——《CSDN发力计划》的加持下,经历了一波快速增长的浪潮,阅读量翻了一番。一连几天都肿不起来,甚至想下定决心,尽快达到破万的目标。然后我在房间里沉思良久,喝了一大杯快乐水,放弃了这个危险的想法。
  2、最近,我发现我们的项目除了入门练习之外,还可以作为一些常见应用的脚手架。如图书查询管理系统、个人博客(首页)、企业门户网站等。上周,我什至基于这个项目为公司内部活动搭建了一个投票系统。虽然觉得自己很崩溃,但是骗过不懂代码的同事就够了。
  为了让这个项目看起来很严肃,我对结构做了一些调整:
  其实前端配置了路由,修改了book组件,后端取消了对book查询端口的拦截。我相信你可以自己做。一位读者指出,我在一个地方漏掉了导入组件的代码。一开始我以为我错过了,但后来我想起我没有故意复制整个组件,因为我认为没有必要。不要只是复制代码并考虑它。不可能不报错,就是直接克隆别人的仓库,会出现各种问题。如果有问题,百度就可以了。
  3、有些读者给我发了十几封邮件,上千行的错误信息贴了好几次。没有这回事,我太南了(给我狂笑.jpg)
  但是,会说话的读者越来越多。我脸皮薄,很多时候还是不忍心拒绝你。在过去的两周里,我花了很多时间回答问题。一些认真的读者发现了项目的错误并指出了如何纠正它们。我很高兴,希望上帝能给我分配更多这样的读者。随着项目越来越复杂,难免会出现各种缺陷。欢迎大家指出各方面的不足,也可以直接在GitHub上提交Issue或者PR,一起来做吧。
  一、后端实现
  实现动态加载菜单功能的第一步是完成根据当前用户查询可访问菜单信息的界面。
  1.餐桌设计
  基于前面提到的RBAC原理,我们应该设计一个角色表,并使用角色来对应菜单。同时,为了建立用户和角色、角色和菜单之间的关系,需要两个中间表。除了前面的用户表,一共需要五个表,每个表的字段如下图所示:
  
  在这里,我将 admin 前缀添加到专用于后台管理的表中。命名是一件非常重要的事情。好名字是保证代码质量的前提。不要那么随随便便地跟着我。很多公司都有自己的命名原则,大家可以学习一下。(推荐《阿里巴巴Java开发手册》)
  另外,和之前book和category的做法不同,这里没有使用外键。一般来说,决定使用外键取决于系统对数据一致性和效率的要求哪个更突出,但我认为数据一致性问题可以通过代码来解决,使用外键既麻烦又尴尬。
  这里我简单介绍一下admin_menu表的字段:
  字段说明
  ID
  唯一标识
  小路
  对应Vue路由中的路径,即地址路径
  名称
  对应Vue路由中的name属性
  name_zh
  中文名称,用于渲染导航栏(菜单)界面
  icon_cls
  元素图标类名,用于渲染菜单名前的小图标
  零件
  组件名称,用于解析路由对应的组件
  parent_id
  父节点id,用于存储导航栏层次结构
  您可以自己设计表中的数据。为了方便测试,记得多注册一个账号来配置不同的角色,为角色配置相应的菜单。如果嫌麻烦,直接执行我的sql文件就行了:
  里面有admin、test和editor三个账号,密码都是123,admin的角色是系统管理员,editor是内容管理员,test是空的。
  2.pojo
  因为我们使用JPA作为ORM,所以在创建POJO时需要注意以下几点:
  PS 其实我们过去创建的POJO应该更具体一些,叫PO(persistant object,持久对象)或者Entity(实体),使用DTO(Data Transfer Object)与客户端进行交互。教程简化了一点,源代码添加了这些类别。
  我们需要创建四个 PO,AdminUserRole、AdminRole、AdminRoleMenu 和 AdminMenu。特别的是AdminMenu。在这里我发布代码:
  package com.gm.wj.pojo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name = "admin_menu")
@JsonIgnoreProperties({"handler", "hibernateLazyInitializer"})
public class AdminMenu {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
int id;
String path;
String name;
String nameZh;
String iconCls;
String component;
int parentId;
@Transient
List children;
// getter and setter...
  与数据库不同的是 Children 属性,用于存储子节点。
  3.菜单查询界面(树结构查询)
  根据用户查询找到对应菜单的步骤为:
  为了实现这个接口,我们需要添加三个数据库访问对象,AdminUserRoleDAO、AdminRoleMenuDAO和AdminMenuDAO,并编写Service对象。一路上我们也可以写一组AdminRoles,但是还不能用。
  在AdminMenuService中,需要实现一个根据当前用户查询所有菜单项的方法:
  public List getMenusByCurrentUser() {
// 从数据库中获取当前用户
String username = SecurityUtils.getSubject().getPrincipal().toString();
User user = userService.findByUsername(username);
// 获得当前用户对应的所有角色的 id 列表
List rids = adminUserRoleService.listAllByUid(user.getId())
.stream().map(AdminUserRole::getRid).collect(Collectors.toList());
// 查询出这些角色对应的所有菜单项
List menuIds = adminRoleMenuService.findAllByRid(rids)
.stream().map(AdminRoleMenu::getMid).collect(Collectors.toList());
List menus = adminMenuDAO.findAllById(menuIds).stream().distinct().collect(Collectors.toList());
// 处理菜单项的结构
handleMenus(menus);
return menus;
}
  该方法的主体对应于上面的前三个步骤。这里我们使用stream来简化列表的处理,包括使用map()提取集合中的某个属性,以及通过distinct()对查询到的菜单项进行去重,避免在多角色的情况下出现多余的菜单等。
  接下来,需要将查询到的菜单数据列表整合成具有层次关系的菜单树,即编写handleMenus()方法。
  这里多说一点,因为导航菜单一般不会很长,所以我们使用这种一次性取出的方式。在上面的过程中,我们会一边遍历列表一边查询数据库。在前台需要尽可能避免这样的多重交互。最好一次性查询全量数据,减轻服务器负担。不过后台一般都是管理员用的,流量也不大,不用担心。
  如果数据量特别大,应该考虑按节点动态加载。即通过监听节点的展开事件,将节点id作为参数发送到后端,在前端动态查询并渲染所有子节点。该方法的实现将在后面详细讨论。
  对查询到的菜单数据进行整合的思路如下:
  整合方法如下:
  public void handleMenus(List menus) {
for (AdminMenu menu : menus) {
List children = getAllByParentId(menu.getId());
menu.setChildren(children);
}

Iterator iterator = menus.iterator();
while (iterator.hasNext()) {
AdminMenu menu = iterator.next();
if (menu.getParentId() != 0) {
iterator.remove();
}
}
}
  首先,让我解释一下,查询树结构的方法有很多种。看到有的逐级查询,有的根据实际情况直接写死。我用的这个方法的好处是不管多少层都可以正确查询,虽然执行效率会低一些,但是反正是在后台使用的,问题不大。
  有的同学可能对这个方法有疑问。似乎一次应该只有两个级别的遍历。另外,菜单已经被查询过了,每次遍历的时候还是会调用查询方法。会不会频繁访问数据库,创建额外的对象,增加不必要的开销?虽然是背景,但这样写也太离谱了吧?
  (感谢@m0_46435907仔细分析后提供了这个问题的答案)
  这里可以放心,JPA为我们提供了持久化上下文(Persistence Context,是实体的集合),用来保证同一个持久化对象只有一个实例,不会再访问数据库当有对应的实例时(详见“JPA Persistence Context”)。因此,我们查询到的子列表中的每个 AdminMenu 对象实例都复用了 Menus 列表中的 AdminMenu 对象。
  同时,Java 中的对象都是引用类型。假设我们将b放入a的孩子中,将c放入b的孩子中,然后将c放入a的孩子的孩子中。因此,经过一次遍历,就可以得到正确的层次关系。
  下面的remove方法实际上是把对象名指向了null,对象本身还是存在的。所以虽然我们不能再通过b和c获取到原来的对象,但是a中的信息不会改变。
  为什么在移除子节点时使用 iterator.remove() 而不是 List 的 remove 方法?这是因为在使用List遍历的时候,如果删除了一个元素,就会添加后面的元素,也就是说后面的元素的索引和列表长度会发生变化。循环继续,循环次数还是列表的原创长度,不仅会漏掉一些元素,还会导致下标溢出,运行时性能会报ConcurrentModificationException。iterator.remove() 做了一些封装,将当前索引和循环次数减1,从而避免了这个问题。
  JDK 8+ 可以使用 lambda 表达式:
  public void handleMenus(List menus) {
menus.forEach(m -> {
List children = getAllByParentId(m.getId());
m.setChildren(children);
});
menus.removeIf(m -> m.getParentId() != 0);
}
  在 MenuController 中,根据请求调用查询逻辑。代码如下:
   @GetMapping("/api/menu")
public List menu() {
return adminMenuService.getMenusByCurrentUser();
}
  完成后就可以测试菜单界面了。在此之前,请确保系统处于登录状态,否则无法查询信息。
  
  二、前端实现
  前端要做的就是处理来自后端的数据,并将其传递给路由和导航菜单进行动态渲染。
  1.背景页面设计
  我目前有以下组件:
  主要是实现页面的基本设计,方便测试的动态加载,暂时还没有这个功能。效果大致是这样的:
  
  可以参考 GitHub 上的源码,也可以自己设计。开发完组件后,别忘了添加后台首页的路由。其他菜单对应的路由可以动态加载。如果没有事先写好,您将无法进入该页面。
  2.数据处理
  在我们设计AdminMenu表之前,它实际上收录了前端路由器和导航菜单所需的信息。从后台传来的数据需要整理成路由器能识别的格式。导航菜单无所谓,分配给相应的属性即可。
  格式转换方法如下:
  const formatRoutes = (routes) => {
let fmtRoutes = []
routes.forEach(route => {
if (route.children) {
route.children = formatRoutes(route.children)
}
let fmtRoute = {
path: route.path,
component: resolve => {
require([&#39;./components/admin/&#39; + route.component + &#39;.vue&#39;], resolve)
},
name: route.name,
nameZh: route.nameZh,
iconCls: route.iconCls,
children: route.children
}
fmtRoutes.push(fmtRoute)
})
return fmtRoutes
}
  这里传入的参数routes代表我们从后端获取的菜单列表。遍历这个列表,首先判断一个菜单项是否收录子项,如果是,则进行递归处理。
  下面的语句是将路由的属性与菜单项的属性对应起来。其余的都好说,主要是组件属性是一个对象,所以需要根据名字来解析(也就是获取对象引用)。同时,我们需要导入组件,所以我们可以使用Vue的异步组件加载机制(也称为延迟加载)在解析的同时完成导入。
  我们数据库中存储的是组件相对于@components/admin的路径,所以在解析的时候要根据js文件所在的位置加上对应的前缀。
  3.添加路由和渲染菜单
  首先我们需要考虑什么时候需要请求界面和渲染菜单。加载您访问的每个页面有点太浪费了。如果只在后台渲染主页面时加载一次,则不能在子页面中进行刷新操作。所以我们可以继续使用路由全局守卫在用户登录时请求菜单信息,访问以/admin开头的路径。完整代码如下
  router.beforeEach((to, from, next) => {
if (store.state.user.username && to.path.startsWith(&#39;/admin&#39;)) {
initAdminMenu(router, store)
}
// 已登录状态下访问 login 页面直接跳转到后台首页
if (store.state.username && to.path.startsWith(&#39;/login&#39;)) {
next({
path: &#39;admin/dashboard&#39;
})
}
if (to.meta.requireAuth) {
if (store.state.user.username) {
axios.get(&#39;/authentication&#39;).then(resp => {
if (resp) next()
})
} else {
next({
path: &#39;login&#39;,
query: {redirect: to.fullPath}
})
}
} else {
next()
}
}
)
  为了确保用户确实登录,仍然需要向后台发送认证请求。
  initAdminMenu 用于执行请求,调用格式化方法并向路由表添加信息,代码如下:
  const initAdminMenu = (router, store) => {
if (store.state.adminMenus.length > 0) {
return
}
axios.get(&#39;/menu&#39;).then(resp => {
if (resp && resp.status === 200) {
var fmtRoutes = formatRoutes(resp.data)
router.addRoutes(fmtRoutes)
store.commit(&#39;initAdminMenu&#39;, fmtRoutes)
}
})
}
  首先判断店铺是否有菜单数据。如果有说明,则为正常跳转,无需重新加载。(第一次进入或刷新时需要重新加载)
  记得在store.state中添加变量adminMenu: [],在mutations中添加如下方法:
   initAdminMenu (state, menus) {
state.adminMenus = menus
}
  这个菜单就是上面的 fmtRoutes。当然,你也可以将数据放入localStorage,只要记得注销时清空即可。
  最后,让我们编写菜单组件 AdminMenu.vue:
  








{{item.nameZh}}



{{ child.nameZh }}





export default {
name: &#39;AdminMenu&#39;,
computed: {
adminMenus () {
return this.$store.state.adminMenus
}
}
}
  这里我们使用 element 的导航栏组件来执行一个两层循环来渲染我们需要的菜单。表示带有子菜单的菜单项,并表示单个菜单项。命名似乎有问题,而且似乎没有错。. .
  如果有三个级别,则设置然后设置,以此类推。
  终于完成了。我们试试用admin账号登录,就是上面的效果,菜单满了:
  
  可以点击用户信息菜单跳转到对应的路由并加载组件:
  
  使用编辑器账号登录,只会显示内容管理
  
  下一步
  这个页面很匆忙。接下来,我们计划根据之前的设计对各个模块进行改进,包括:
  下一篇文章的重点是功能级权限的实现。其他方面会顺带提一下,但我不会过多赘述,因为都是已经讨论过的知识点。
  终于写完了,但是感觉这个文章还是有一些地方需要打磨的。如果大家有什么不明白的地方可以随便提,但我就直接忽略代码为什么不能运行的问题。
  另外,本文内容参考《_江南小雨》,是松哥的实现思路。我首先从松哥的项目中学习了 Vue。我们项目里很多前端代码都是模仿松哥的“微”写的“人力资源”项目,现在微人有11.6k星,大家可以学习一下,比我做了什么。
  上一篇:Vue+Spring Boot项目实战(十四):用户认证方案和完善的访问拦截
  下一篇:Vue+Spring Boot项目实战(十六):功能级访问控制的实现 查看全部

  nodejs抓取动态网页(【系列文章目录】「项目源码(GitHub)」本篇目录)
  重要链接:
  "系列文章目录"
  “项目源代码(GitHub)”
  本目录
  前言
  不出所料,我没能按时写完文章。时至今日,我已经没有任何愧疚感了,大概就是长大了。但话说回来,我曾经是一个每月写10篇文章的人。
  这个文章的主要内容是根据用户角色动态加载后台管理页面的菜单,关键点如下:
  在正文之前,让我告诉你这两周背后的故事:
  1、在官方活动——《CSDN发力计划》的加持下,经历了一波快速增长的浪潮,阅读量翻了一番。一连几天都肿不起来,甚至想下定决心,尽快达到破万的目标。然后我在房间里沉思良久,喝了一大杯快乐水,放弃了这个危险的想法。
  2、最近,我发现我们的项目除了入门练习之外,还可以作为一些常见应用的脚手架。如图书查询管理系统、个人博客(首页)、企业门户网站等。上周,我什至基于这个项目为公司内部活动搭建了一个投票系统。虽然觉得自己很崩溃,但是骗过不懂代码的同事就够了。
  为了让这个项目看起来很严肃,我对结构做了一些调整:
  其实前端配置了路由,修改了book组件,后端取消了对book查询端口的拦截。我相信你可以自己做。一位读者指出,我在一个地方漏掉了导入组件的代码。一开始我以为我错过了,但后来我想起我没有故意复制整个组件,因为我认为没有必要。不要只是复制代码并考虑它。不可能不报错,就是直接克隆别人的仓库,会出现各种问题。如果有问题,百度就可以了。
  3、有些读者给我发了十几封邮件,上千行的错误信息贴了好几次。没有这回事,我太南了(给我狂笑.jpg)
  但是,会说话的读者越来越多。我脸皮薄,很多时候还是不忍心拒绝你。在过去的两周里,我花了很多时间回答问题。一些认真的读者发现了项目的错误并指出了如何纠正它们。我很高兴,希望上帝能给我分配更多这样的读者。随着项目越来越复杂,难免会出现各种缺陷。欢迎大家指出各方面的不足,也可以直接在GitHub上提交Issue或者PR,一起来做吧。
  一、后端实现
  实现动态加载菜单功能的第一步是完成根据当前用户查询可访问菜单信息的界面。
  1.餐桌设计
  基于前面提到的RBAC原理,我们应该设计一个角色表,并使用角色来对应菜单。同时,为了建立用户和角色、角色和菜单之间的关系,需要两个中间表。除了前面的用户表,一共需要五个表,每个表的字段如下图所示:
  
  在这里,我将 admin 前缀添加到专用于后台管理的表中。命名是一件非常重要的事情。好名字是保证代码质量的前提。不要那么随随便便地跟着我。很多公司都有自己的命名原则,大家可以学习一下。(推荐《阿里巴巴Java开发手册》)
  另外,和之前book和category的做法不同,这里没有使用外键。一般来说,决定使用外键取决于系统对数据一致性和效率的要求哪个更突出,但我认为数据一致性问题可以通过代码来解决,使用外键既麻烦又尴尬。
  这里我简单介绍一下admin_menu表的字段:
  字段说明
  ID
  唯一标识
  小路
  对应Vue路由中的路径,即地址路径
  名称
  对应Vue路由中的name属性
  name_zh
  中文名称,用于渲染导航栏(菜单)界面
  icon_cls
  元素图标类名,用于渲染菜单名前的小图标
  零件
  组件名称,用于解析路由对应的组件
  parent_id
  父节点id,用于存储导航栏层次结构
  您可以自己设计表中的数据。为了方便测试,记得多注册一个账号来配置不同的角色,为角色配置相应的菜单。如果嫌麻烦,直接执行我的sql文件就行了:
  里面有admin、test和editor三个账号,密码都是123,admin的角色是系统管理员,editor是内容管理员,test是空的。
  2.pojo
  因为我们使用JPA作为ORM,所以在创建POJO时需要注意以下几点:
  PS 其实我们过去创建的POJO应该更具体一些,叫PO(persistant object,持久对象)或者Entity(实体),使用DTO(Data Transfer Object)与客户端进行交互。教程简化了一点,源代码添加了这些类别。
  我们需要创建四个 PO,AdminUserRole、AdminRole、AdminRoleMenu 和 AdminMenu。特别的是AdminMenu。在这里我发布代码:
  package com.gm.wj.pojo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name = "admin_menu")
@JsonIgnoreProperties({"handler", "hibernateLazyInitializer"})
public class AdminMenu {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
int id;
String path;
String name;
String nameZh;
String iconCls;
String component;
int parentId;
@Transient
List children;
// getter and setter...
  与数据库不同的是 Children 属性,用于存储子节点。
  3.菜单查询界面(树结构查询)
  根据用户查询找到对应菜单的步骤为:
  为了实现这个接口,我们需要添加三个数据库访问对象,AdminUserRoleDAO、AdminRoleMenuDAO和AdminMenuDAO,并编写Service对象。一路上我们也可以写一组AdminRoles,但是还不能用。
  在AdminMenuService中,需要实现一个根据当前用户查询所有菜单项的方法:
  public List getMenusByCurrentUser() {
// 从数据库中获取当前用户
String username = SecurityUtils.getSubject().getPrincipal().toString();
User user = userService.findByUsername(username);
// 获得当前用户对应的所有角色的 id 列表
List rids = adminUserRoleService.listAllByUid(user.getId())
.stream().map(AdminUserRole::getRid).collect(Collectors.toList());
// 查询出这些角色对应的所有菜单项
List menuIds = adminRoleMenuService.findAllByRid(rids)
.stream().map(AdminRoleMenu::getMid).collect(Collectors.toList());
List menus = adminMenuDAO.findAllById(menuIds).stream().distinct().collect(Collectors.toList());
// 处理菜单项的结构
handleMenus(menus);
return menus;
}
  该方法的主体对应于上面的前三个步骤。这里我们使用stream来简化列表的处理,包括使用map()提取集合中的某个属性,以及通过distinct()对查询到的菜单项进行去重,避免在多角色的情况下出现多余的菜单等。
  接下来,需要将查询到的菜单数据列表整合成具有层次关系的菜单树,即编写handleMenus()方法。
  这里多说一点,因为导航菜单一般不会很长,所以我们使用这种一次性取出的方式。在上面的过程中,我们会一边遍历列表一边查询数据库。在前台需要尽可能避免这样的多重交互。最好一次性查询全量数据,减轻服务器负担。不过后台一般都是管理员用的,流量也不大,不用担心。
  如果数据量特别大,应该考虑按节点动态加载。即通过监听节点的展开事件,将节点id作为参数发送到后端,在前端动态查询并渲染所有子节点。该方法的实现将在后面详细讨论。
  对查询到的菜单数据进行整合的思路如下:
  整合方法如下:
  public void handleMenus(List menus) {
for (AdminMenu menu : menus) {
List children = getAllByParentId(menu.getId());
menu.setChildren(children);
}

Iterator iterator = menus.iterator();
while (iterator.hasNext()) {
AdminMenu menu = iterator.next();
if (menu.getParentId() != 0) {
iterator.remove();
}
}
}
  首先,让我解释一下,查询树结构的方法有很多种。看到有的逐级查询,有的根据实际情况直接写死。我用的这个方法的好处是不管多少层都可以正确查询,虽然执行效率会低一些,但是反正是在后台使用的,问题不大。
  有的同学可能对这个方法有疑问。似乎一次应该只有两个级别的遍历。另外,菜单已经被查询过了,每次遍历的时候还是会调用查询方法。会不会频繁访问数据库,创建额外的对象,增加不必要的开销?虽然是背景,但这样写也太离谱了吧?
  (感谢@m0_46435907仔细分析后提供了这个问题的答案)
  这里可以放心,JPA为我们提供了持久化上下文(Persistence Context,是实体的集合),用来保证同一个持久化对象只有一个实例,不会再访问数据库当有对应的实例时(详见“JPA Persistence Context”)。因此,我们查询到的子列表中的每个 AdminMenu 对象实例都复用了 Menus 列表中的 AdminMenu 对象。
  同时,Java 中的对象都是引用类型。假设我们将b放入a的孩子中,将c放入b的孩子中,然后将c放入a的孩子的孩子中。因此,经过一次遍历,就可以得到正确的层次关系。
  下面的remove方法实际上是把对象名指向了null,对象本身还是存在的。所以虽然我们不能再通过b和c获取到原来的对象,但是a中的信息不会改变。
  为什么在移除子节点时使用 iterator.remove() 而不是 List 的 remove 方法?这是因为在使用List遍历的时候,如果删除了一个元素,就会添加后面的元素,也就是说后面的元素的索引和列表长度会发生变化。循环继续,循环次数还是列表的原创长度,不仅会漏掉一些元素,还会导致下标溢出,运行时性能会报ConcurrentModificationException。iterator.remove() 做了一些封装,将当前索引和循环次数减1,从而避免了这个问题。
  JDK 8+ 可以使用 lambda 表达式:
  public void handleMenus(List menus) {
menus.forEach(m -> {
List children = getAllByParentId(m.getId());
m.setChildren(children);
});
menus.removeIf(m -> m.getParentId() != 0);
}
  在 MenuController 中,根据请求调用查询逻辑。代码如下:
   @GetMapping("/api/menu")
public List menu() {
return adminMenuService.getMenusByCurrentUser();
}
  完成后就可以测试菜单界面了。在此之前,请确保系统处于登录状态,否则无法查询信息。
  
  二、前端实现
  前端要做的就是处理来自后端的数据,并将其传递给路由和导航菜单进行动态渲染。
  1.背景页面设计
  我目前有以下组件:
  主要是实现页面的基本设计,方便测试的动态加载,暂时还没有这个功能。效果大致是这样的:
  
  可以参考 GitHub 上的源码,也可以自己设计。开发完组件后,别忘了添加后台首页的路由。其他菜单对应的路由可以动态加载。如果没有事先写好,您将无法进入该页面。
  2.数据处理
  在我们设计AdminMenu表之前,它实际上收录了前端路由器和导航菜单所需的信息。从后台传来的数据需要整理成路由器能识别的格式。导航菜单无所谓,分配给相应的属性即可。
  格式转换方法如下:
  const formatRoutes = (routes) => {
let fmtRoutes = []
routes.forEach(route => {
if (route.children) {
route.children = formatRoutes(route.children)
}
let fmtRoute = {
path: route.path,
component: resolve => {
require([&#39;./components/admin/&#39; + route.component + &#39;.vue&#39;], resolve)
},
name: route.name,
nameZh: route.nameZh,
iconCls: route.iconCls,
children: route.children
}
fmtRoutes.push(fmtRoute)
})
return fmtRoutes
}
  这里传入的参数routes代表我们从后端获取的菜单列表。遍历这个列表,首先判断一个菜单项是否收录子项,如果是,则进行递归处理。
  下面的语句是将路由的属性与菜单项的属性对应起来。其余的都好说,主要是组件属性是一个对象,所以需要根据名字来解析(也就是获取对象引用)。同时,我们需要导入组件,所以我们可以使用Vue的异步组件加载机制(也称为延迟加载)在解析的同时完成导入。
  我们数据库中存储的是组件相对于@components/admin的路径,所以在解析的时候要根据js文件所在的位置加上对应的前缀。
  3.添加路由和渲染菜单
  首先我们需要考虑什么时候需要请求界面和渲染菜单。加载您访问的每个页面有点太浪费了。如果只在后台渲染主页面时加载一次,则不能在子页面中进行刷新操作。所以我们可以继续使用路由全局守卫在用户登录时请求菜单信息,访问以/admin开头的路径。完整代码如下
  router.beforeEach((to, from, next) => {
if (store.state.user.username && to.path.startsWith(&#39;/admin&#39;)) {
initAdminMenu(router, store)
}
// 已登录状态下访问 login 页面直接跳转到后台首页
if (store.state.username && to.path.startsWith(&#39;/login&#39;)) {
next({
path: &#39;admin/dashboard&#39;
})
}
if (to.meta.requireAuth) {
if (store.state.user.username) {
axios.get(&#39;/authentication&#39;).then(resp => {
if (resp) next()
})
} else {
next({
path: &#39;login&#39;,
query: {redirect: to.fullPath}
})
}
} else {
next()
}
}
)
  为了确保用户确实登录,仍然需要向后台发送认证请求。
  initAdminMenu 用于执行请求,调用格式化方法并向路由表添加信息,代码如下:
  const initAdminMenu = (router, store) => {
if (store.state.adminMenus.length > 0) {
return
}
axios.get(&#39;/menu&#39;).then(resp => {
if (resp && resp.status === 200) {
var fmtRoutes = formatRoutes(resp.data)
router.addRoutes(fmtRoutes)
store.commit(&#39;initAdminMenu&#39;, fmtRoutes)
}
})
}
  首先判断店铺是否有菜单数据。如果有说明,则为正常跳转,无需重新加载。(第一次进入或刷新时需要重新加载)
  记得在store.state中添加变量adminMenu: [],在mutations中添加如下方法:
   initAdminMenu (state, menus) {
state.adminMenus = menus
}
  这个菜单就是上面的 fmtRoutes。当然,你也可以将数据放入localStorage,只要记得注销时清空即可。
  最后,让我们编写菜单组件 AdminMenu.vue:
  








{{item.nameZh}}



{{ child.nameZh }}





export default {
name: &#39;AdminMenu&#39;,
computed: {
adminMenus () {
return this.$store.state.adminMenus
}
}
}
  这里我们使用 element 的导航栏组件来执行一个两层循环来渲染我们需要的菜单。表示带有子菜单的菜单项,并表示单个菜单项。命名似乎有问题,而且似乎没有错。. .
  如果有三个级别,则设置然后设置,以此类推。
  终于完成了。我们试试用admin账号登录,就是上面的效果,菜单满了:
  
  可以点击用户信息菜单跳转到对应的路由并加载组件:
  
  使用编辑器账号登录,只会显示内容管理
  
  下一步
  这个页面很匆忙。接下来,我们计划根据之前的设计对各个模块进行改进,包括:
  下一篇文章的重点是功能级权限的实现。其他方面会顺带提一下,但我不会过多赘述,因为都是已经讨论过的知识点。
  终于写完了,但是感觉这个文章还是有一些地方需要打磨的。如果大家有什么不明白的地方可以随便提,但我就直接忽略代码为什么不能运行的问题。
  另外,本文内容参考《_江南小雨》,是松哥的实现思路。我首先从松哥的项目中学习了 Vue。我们项目里很多前端代码都是模仿松哥的“微”写的“人力资源”项目,现在微人有11.6k星,大家可以学习一下,比我做了什么。
  上一篇:Vue+Spring Boot项目实战(十四):用户认证方案和完善的访问拦截
  下一篇:Vue+Spring Boot项目实战(十六):功能级访问控制的实现

nodejs抓取动态网页(Google和Baidu的爬虫技术是不公开的,算是商业机密了)

网站优化优采云 发表了文章 • 0 个评论 • 80 次浏览 • 2022-02-02 03:12 • 来自相关话题

  nodejs抓取动态网页(Google和Baidu的爬虫技术是不公开的,算是商业机密了)
  之前玩千里的时候,有这样一个问题,如下:谷歌、百度的两大技术:爬虫和搜索。爬虫负责爬取整个互联网的内容,搜索负责生成索引供用户检索。所以爬虫是这两家公司的看家本领。但是,谷歌和百度的爬虫技术并不公开,属于商业机密。所以,360搜索刚起步的时候,第一件事就是挖了百度核心搜索爬虫群的程序员,基本是3倍的工资。虽然没有办法知道这两家公司的爬虫技术,但是我们可以向开源项目学习。例如,Scrapy 是一个非常好的开源爬虫框架。如果你对爬虫感兴趣,
  简单来说,爬虫分为两步:
  这里有一个很简单的任务,让大家感受一下爬虫:豆瓣电影Top250收录迄今为止最喜欢的250部电影。该列表显示了每部电影的基本信息,例如评级、年份等。这个问题的简单答案是该列表中排名前 166 部电影的评分总和。例子:目前第一名《肖申克的救赎》是9.6分,第二名《这个杀手不太冷》是9.4分,第三名《阿甘正传》是9.4 分。那么前3部电影的总分是9.6+9.4+9.4=28.4。
  下面是具体的实现代码:
<p>var url = &#39;https://movie.douban.com/top250?start=&#39;;
//需要统计166部,每页25条
var superagent = require(&#39;superagent&#39;);
var cheerio = require(&#39;cheerio&#39;);
var numArr = [];
var getNext = function(path,start){
var tempPath = path+start;
superagent.get(tempPath).set({
&#39;cookie&#39;:&#39;bid=xEbtZyJpLBs; _pk_ref.100001.4cf6=%5B%22%22%2C%22%22%2C1474114942%2C%22http%3A%2F%2Fwww.qlcoder.com%2Ftask%2F7560%22%5D; _pk_id.100001.4cf6=ebdb1fdadde80a53.1474114942.1.1474115010.1474114942.; _pk_ses.100001.4cf6=*; __utma=30149280.834196820.1474114942.1474114942.1474114942.1; __utmb=30149280.0.10.1474114942; __utmc=30149280; __utmz=30149280.1474114942.1.1.utmcsr=qlcoder.com|utmccn=(referral)|utmcmd=referral|utmcct=/task/7560; __utma=223695111.500893696.1474114942.1474114942.1474114942.1; __utmb=223695111.0.10.1474114942; __utmc=223695111; __utmz=223695111.1474114942.1.1.utmcsr=qlcoder.com|utmccn=(referral)|utmcmd=referral|utmcct=/task/7560&#39;
}).end(function(err,res){
var text = res.text;
var $ = cheerio.load(text);
var flag = false;
$(&#39;.rating_num&#39;).each(function(index,ele){
var num = $(ele).html();
num = parseFloat(num);
if(numArr.length == 166){
flag = true;
countNum ();
}
if(!flag){
numArr.push(num);
}
});
if(!flag){
getNext(path,start+25);
}
});
};
var countNum = function(){
var total = 0 ;
for(var i=0,max=numArr.length;i 查看全部

  nodejs抓取动态网页(Google和Baidu的爬虫技术是不公开的,算是商业机密了)
  之前玩千里的时候,有这样一个问题,如下:谷歌、百度的两大技术:爬虫和搜索。爬虫负责爬取整个互联网的内容,搜索负责生成索引供用户检索。所以爬虫是这两家公司的看家本领。但是,谷歌和百度的爬虫技术并不公开,属于商业机密。所以,360搜索刚起步的时候,第一件事就是挖了百度核心搜索爬虫群的程序员,基本是3倍的工资。虽然没有办法知道这两家公司的爬虫技术,但是我们可以向开源项目学习。例如,Scrapy 是一个非常好的开源爬虫框架。如果你对爬虫感兴趣,
  简单来说,爬虫分为两步:
  这里有一个很简单的任务,让大家感受一下爬虫:豆瓣电影Top250收录迄今为止最喜欢的250部电影。该列表显示了每部电影的基本信息,例如评级、年份等。这个问题的简单答案是该列表中排名前 166 部电影的评分总和。例子:目前第一名《肖申克的救赎》是9.6分,第二名《这个杀手不太冷》是9.4分,第三名《阿甘正传》是9.4 分。那么前3部电影的总分是9.6+9.4+9.4=28.4。
  下面是具体的实现代码:
<p>var url = &#39;https://movie.douban.com/top250?start=&#39;;
//需要统计166部,每页25条
var superagent = require(&#39;superagent&#39;);
var cheerio = require(&#39;cheerio&#39;);
var numArr = [];
var getNext = function(path,start){
var tempPath = path+start;
superagent.get(tempPath).set({
&#39;cookie&#39;:&#39;bid=xEbtZyJpLBs; _pk_ref.100001.4cf6=%5B%22%22%2C%22%22%2C1474114942%2C%22http%3A%2F%2Fwww.qlcoder.com%2Ftask%2F7560%22%5D; _pk_id.100001.4cf6=ebdb1fdadde80a53.1474114942.1.1474115010.1474114942.; _pk_ses.100001.4cf6=*; __utma=30149280.834196820.1474114942.1474114942.1474114942.1; __utmb=30149280.0.10.1474114942; __utmc=30149280; __utmz=30149280.1474114942.1.1.utmcsr=qlcoder.com|utmccn=(referral)|utmcmd=referral|utmcct=/task/7560; __utma=223695111.500893696.1474114942.1474114942.1474114942.1; __utmb=223695111.0.10.1474114942; __utmc=223695111; __utmz=223695111.1474114942.1.1.utmcsr=qlcoder.com|utmccn=(referral)|utmcmd=referral|utmcct=/task/7560&#39;
}).end(function(err,res){
var text = res.text;
var $ = cheerio.load(text);
var flag = false;
$(&#39;.rating_num&#39;).each(function(index,ele){
var num = $(ele).html();
num = parseFloat(num);
if(numArr.length == 166){
flag = true;
countNum ();
}
if(!flag){
numArr.push(num);
}
});
if(!flag){
getNext(path,start+25);
}
});
};
var countNum = function(){
var total = 0 ;
for(var i=0,max=numArr.length;i

nodejs抓取动态网页(华为云开发者社区搭建python爬虫管理平台的相关内容吗)

网站优化优采云 发表了文章 • 0 个评论 • 49 次浏览 • 2022-01-29 23:11 • 来自相关话题

  nodejs抓取动态网页(华为云开发者社区搭建python爬虫管理平台的相关内容吗)
  想知道快速搭建python爬虫管理平台的相关内容吗?在这篇文章中,华为云开发者社区将为大家讲解搭建python爬虫平台的相关知识和一些代码示例。欢迎阅读和指正。我们先把重点放在:python、爬虫、爬虫、构建,一起来学习。
  爬行动物有多重要
  对于搜索引擎来说,爬虫是必不可少的;对于舆论公司来说,爬虫是基础;对于 NLP,爬虫可以获得语料库;对于初创公司,爬虫可以获取初始内容。但是爬虫技术比较复杂,不同类型的爬取场景会用到不同的技术。比如一个简单的静态页面可以直接用HTTP请求+HTML解析器完成;动态页面需要使用 Puppeteer 或 Selenium 等自动化测试工具;反爬网站需要使用代理、编码等技术;等等。这时就需要一个成熟的爬虫管理平台来帮助企业或个人处理大量的爬虫类别。
  了解爬虫管理平台的定义是什么
  爬虫管理平台是集爬虫部署、任务调度、任务监控、结果展示等模块于一体的一站式管理系统。它通常配备可视化的 UI 界面,通过与 Web 端的 UI 界面交互,可以有效地管理爬虫。爬虫管理平台一般支持多机分布式协同运行。
  当然,上述定义是狭隘的,通常针对技术人员或开发人员或技术管理人员。企业一般会自行开发内部爬虫管理系统,以应对复杂的爬虫管理需求。这样的系统就是上面定义的狭义的爬虫管理平台。
  通用爬虫管理平台
  什么是通用爬虫管理平台?您可能听说过 优采云(后来转换为 优采云采集器)和 优采云。前者是基于云服务的爬虫管理平台,可以在线编写、运行和监控爬虫。是一般爬虫平台中最接近狭义的爬虫管理平台;后者是一种流行的商业爬虫爬取工具,允许新手用户通过拖放来编写和运行爬虫导出数据。您可能还见过各种 API 聚合服务提供商,例如 Aggregate Data。这是一个可以直接调用网站接口获取数据的平台。这实际上是爬虫平台的一个变种,但它可以帮助您完成爬虫编写过程。过程。而在两者之间,国外有个叫Kimonolab的公司,开发了一个Chrome插件叫Kimono,可以让用户直观的点击页面上的元素,生成抓取规则,放到网站爬虫生成后,用户提交任务,数据可以在网站上自动捕获。Kimono是一款很棒的爬虫应用,可惜Kimonolab已经被大数据公司Plantir收购,现在无法体验。
  在本文中,我们主要关注狭义的爬虫管理平台,因此后面提到的爬虫管理平台指的是狭义的。
  爬虫管理平台模块
  以下是一个典型的爬虫管理平台所涉及的模块。
  
  爬虫管理平台架构
  一个典型的爬虫管理平台的模块主要包括以下几个:
  当然,有些爬虫管理平台的模块可能不止这些。它可能包括其他更实用的功能,如可配置爬虫规则、可视化配置爬虫规则、代理池、cookie池、异常监控等。
  为什么需要爬虫管理平台
  有了爬虫管理平台,开发者,尤其是爬虫工程师,可以很方便的添加爬虫,执行任务,查看结果,不用在命令行之间来回切换,非常容易出错。一个常见的场景是爬虫工程师在最初的技术选型中使用了scrapy和crontab来管理爬虫任务。他必须仔细选择计划任务的时间间隔,以免填满服务器 CPU 或内存;比较棘手的问题是,他还需要将scrapy生成的日志保存在一个文件中。爬虫一旦出错,就得使用shell命令一一查看日志,定位出错原因。如果严重的话,需要一整天的时间;还有一个严重的问题。爬虫工程师可能会发现公司的业务量 当他需要编写数百个爬虫来满足公司的业务需求时,使用 scrapy 和 crontab 进行管理是一场彻头彻尾的噩梦。可怜的爬虫工程师其实可以选择一个合适的爬虫管理平台来解决他的问题。
  如何选择合适的爬虫管理平台
  当您愿意解决上述爬虫工程师遇到的难题,而是想选择合适的爬虫管理平台时。
  您应该回答的第一个问题是:我们是否需要从头开始开发系统(Start from scratch)?要回答这个问题,您应该首先回答以下问题:
  1.我们的需求是否复杂到可以完全定制一个新系统(例如,需要复杂的权限管理)?
  2.我们的团队是否有足够的技术力量来开发这个系统(例如经验丰富的前端和后端开发工程师)?
  3.我们的时间资源是否足以让我们开发系统(例如一年的项目规划周期)?
  如果以上三个问题的任何一个问题的答案都是“否”,您应该考虑使用市面上的开源爬虫管理平台来满足您的需求。
  以下是市面上的开源爬虫管理平台:
  
  总的来说,SpiderKeeper可能是最早的爬虫管理平台,但功能相对有限;Gerapy 有一个功能齐全且美观的界面,但有许多 bug 需要处理。建议有需要的用户等待2.0版本;Scrapydweb是一个比较完善的爬虫管理平台,基于scrapyd,所以只能运行scrapy爬虫;而 Crawlab 是一个非常灵活的爬虫管理平台,可以运行用 Python、Nodejs、Java、PHP 和 Go 编写的爬虫。比较全,但是部署起来比前三个麻烦一些,但是对于Docker用户来说可以一体部署(后面会讲)。
  所以严重依赖scrapy爬虫不想折腾的开发者可以考虑Scrapydweb;而对于具有各类复杂技术结构的爬虫开发者,应优先考虑更灵活的Crawlab。当然,并不是说 Crawlab 对 scrapy 支持不友好。Crawlab 也可以很好的集成scrapy。稍后会介绍。
  作为Crawlab的作者,我不希望王破卖瓜吹牛。笔者只是想把最好的技术选型推荐给开发者,让开发者可以根据自己的需求决定使用哪个爬虫管理平台。
  爬虫管理平台Crawlab介绍
  Crawlab是一个基于Golang的分布式爬虫管理平台,支持Python、NodeJS、Java、Go、PHP等多种编程语言和爬虫框架。
  Crawlab自今年3月上线以来,深受爬虫爱好者和开发者的好评。不少用户也表示会使用Crawlab搭建公司的爬虫平台。经过几个月的迭代,Crawlab先后推出了定时任务、数据分析、网站信息、可配置爬虫、自动字段提取、下载结果、上传爬虫等功能,使平台更加实用和全面,可以真正帮助用户解决爬虫管理的难题。现在 Github 上有近 1k 的明星相关社区,四分之一的用户表示已经将 Crawlab 应用到企业爬虫管理中。可见Crawlab受到开发者的关注和喜爱。
  解决这个问题
  Crawlab主要解决大量爬虫管理困难的问题,比如需要监控数百个网站混合的scrapy和selenium项目。同时不容易管理,命令行管理成本很高,容易出错。Crawlab 支持任意语言、任意框架的任务调度和任务监控,轻松有效监控和管理大型爬虫项目。
  接口和使用
  下面是 Crawlab 爬虫列表页面的截图。
  
  Crawlab爬虫列表
  用户只需要将爬虫上传到Crawlab配置执行命令,点击“运行”按钮即可执行爬虫任务。爬虫任务可以在任何节点上运行。从上图可以看出,Crawlab有节点管理、爬虫管理、任务管理、定时任务、用户管理等模块。
  整体结构
  以下是Crawlab的整体架构图,由五部分组成:
  1.Master Node:负责任务调度、API、爬虫部署等;
  2.Worker Node:负责执行爬虫任务;
  3.MongoDB数据库:存储节点、爬虫、任务等日常运行数据;
  4.Redis数据库:存储任务消息队列、节点心跳等信息。
  5.前端客户端:Vue应用负责前端交互,向后端请求数据。
  
  Github地址和Demo
  查看演示 DemoGithub:
  使用 Docker 部署安装 CrawlabDocker 映像
  Docker 是部署 Crawlab 最方便、最简洁的方式。其他部署方式包括直接部署,但不推荐给想要快速搭建平台的开发者。Crawlab 已经在 Dockerhub 上注册了相关镜像。开发者只需执行 docker pull tikazyq/crawlab 命令即可下载 Crawlab 镜像。
  读者可以去Dockerhub看看,Crawlab的镜像只有不到300Mb。地址:
  
  Dockerhub 页面
  安装 Docker
  要使用 Docker 部署 Crawlab,您必须首先确保已安装 Docker。请参考以下文档进行安装。
  
  安装 Docker Compose
  Docker Compose 是一个运行 Docker 集群的简单工具。非常轻巧。我们将使用 Docker Compose 一键部署 Crawlab。
  Docker 官方 网站 已经有安装 Docker Compose 的教程,点击链接查看。这里有一个简单的介绍。
  
  对于 Linux 用户,请使用以下命令进行安装。
  
# 下载 docker-compose
sudo curl -L "https://github.com/docker/comp ... pose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose`
# 将 docker-compose 变成执行文件
sudo chmod +x /usr/local/bin/docker-compose
  拉图像
  拉取镜像前需要配置镜像源。因为在国内使用原创镜像源的速度不是很快,所以需要使用DockerHub的国产加速器。请创建 /etc/docker/daemon.json 文件并输入以下内容。
  
{
"registry-mirrors": ["https://registry.docker-cn.com"]
}
  然后拉图像会快得多。当然,您也可以使用其他镜像源在线搜索。运行以下命令拉取 Crawlab 镜像。
  
docker pull tikazyq/crawlab:latest
  下图是拉取镜像时的命令行界面。
  
  码头工人拉
  开始爬行实验室
  我们将使用 Docker Compose 启动 Crawlab 及其依赖的数据库 MongoDB 和 Redis。首先我们需要修改Docker Compose的yaml配置文件docker-compose.yml。该配置文件定义了需要启动的容器服务和网络配置。这里我们使用 Crawlab 自带的 docker-compose.yml。
  
version: '3.3' # Docker Compose 的版本号(请看后续说明)
services: # 服务
master: # 服务名称
image: tikazyq/crawlab:latest # 服务对应的镜像名称
container_name: master # 服务对应的容器名称
environment: # 这里定义传入的环境变量
CRAWLAB_API_ADDRESS: "localhost:8000" # 前端调用的 API 地址默认为 localhost:8000
CRAWLAB_SERVER_MASTER: "Y" # 是否为主节点Y/N
CRAWLAB_MONGO_HOST: "mongo" # MongoDB host由于在 Docker Compose 里可以引用服务名称
CRAWLAB_REDIS_ADDRESS: "redis" # Redis host由于在 Docker Compose 里可以引用服务名称
ports: # 映射的端口
- "8080:8080" # 前端端口
- "8000:8000" # 后端端口
depends_on: # 依赖的服务
- mongo # MongoDB
- redis # Redis
worker: # 工作节点与主节点配置类似不重复写了
image: tikazyq/crawlab:latest
container_name: worker
environment:
CRAWLAB_SERVER_MASTER: "N"
CRAWLAB_MONGO_HOST: "mongo"
CRAWLAB_REDIS_ADDRESS: "redis"
depends_on:
- mongo
- redis
mongo: # MongoDB 服务名称
image: mongo:latest # MongoDB 镜像名称
restart: always # 重启策略为“总是”
ports: # 映射端口
- "27017:27017"
redis: # Redis 服务名称
image: redis:latest # Redis 镜像名称
restart: always # 重启策略为“总是”
ports: # 映射端口
- "6379:6379"
  读者可以根据自己的需求配置docker-compose.yml。特别要注意环境变量 CRAWLAB_API_ADDRESS。很多新手用户因为变量配置不正确而无法登录。在大多数情况下,您不需要进行任何配置更改。请参阅常见问题解答和详细的环境变量配置文档,以帮助根据您的环境配置 Crawlab。
  然后运行以下命令启动 Crawlab。您可以添加 -d 参数以使 Docker Compose 在后台运行。
  
docker-compose up
  运行上述命令后,Docker Compose 将拉取 MongoDB 和 Redis 映像,这可能需要几分钟时间。拉取完成后,四个服务会依次启动。您将在命令行上看到以下内容。
  
  码头工人撰写
  正常情况下应该可以看到四个服务全部启动成功,并且可以顺利打印日志。
  如果在本地机器上启动Docker Compose,可以在浏览器中输入:8080,可以看到登录界面;如果在另一台机器上启动Docker Compose,需要在浏览器中输入:8080才能看到登录界面是其他机器的IP地址(请确保本机8080端口对外开放)。
  
  登录
  初始登录用户名和密码为 admin/admin,您可以使用此用户名和密码登录。如果您的环境变量 CRAWLAB_API_ADDRESS 设置不正确,您可能会看到登录按钮在单击登录后一直在旋转而没有任何提示。此时请在 docker-compose.yml 中设置正确的 CRAWLAB_API_ADDRESS(将 localhost 替换为)并重启 docker-compose up。然后在浏览器中输入:8080。
  登录后,您将看到 Crawlab 主页。
  
  家
  本文文章主要介绍如何搭建爬虫管理平台Crawlab,所以不会详细介绍如何使用Crawlab(可以另写一篇文章文章详细介绍,有兴趣的可以付费注意力)。如果您感到困惑,请查看相关文档以了解如何使用它。
  如何将 Scrapy 等爬虫集成到 Crawlab 中
  众所周知,Scrapy 是一个非常流行的爬虫框架。其灵活的框架设计、高并发、易用性和可扩展性已被众多开发者和企业广泛采用。市面上几乎所有爬虫管理平台都支持Scrapy爬虫Crawlab也不例外,但Crawlab可以运行puppeteer、selenium等其他爬虫。下面将介绍如何在Crawlab中运行scrapy爬虫。
  Crawlab是执行爬虫的基本原理
  Crawlab的爬虫原理很简单。它实际上是一个 shell 命令。用户在爬虫中输入shell命令执行爬虫,比如scrapy crawl some_spiderCrawlab执行器会读取这个命令,直接在shell中执行。因此,每次运行爬虫任务时,都要执行一个shell命令(当然实际情况比这复杂得多,有兴趣的可以参考官方文档)。Crawlab 支持显示和导出爬虫结果,但这需要更多的工作。
  编写管道
  集成scrapy爬虫无非是将爬虫爬取的数据存储在Crawlab的数据库中,并与任务ID相关联。每次执行爬虫任务时,都会通过环境变量将任务ID传递给爬虫程序,所以我们只需要将任务ID加上结果存入数据库即可(Crawlab目前只支持MongoDB,后续会开发) MySQL、SQL Server、Postgres 等关系类型未来需要数据库的用户可以关注)。
  在 Scrapy 中,我们需要编写存储逻辑。原理图代码如下:
  
# 引入相关的库pymongo 是标准连接 MongoDB 的库
import os
from pymongo import MongoClient
# MongoDB 配置参数
MONGO_HOST = '192.168.99.100'
MONGO_PORT = 27017
MONGO_DB = 'crawlab_test'
class JuejinPipeline(object):
mongo = MongoClient(host=MONGO_HOST, port=MONGO_PORT) # mongo 连接实例
db = mongo[MONGO_DB] # 数据库实例
col_name = os.environ.get('CRAWLAB_COLLECTION') # 集合名称通过环境变量 CRAWLAB_COLLECTION 传过来
# 如果 CRAWLAB_COLLECTION 不存在则默认集合名称为 test
if not col_name:
col_name = 'test'

col = db[col_name] # 集合实例
# 每一个传入 item 会调用的函数参数分别为 item 和 spider
def process_item(self, item, spider):
item['task_id'] = os.environ.get('CRAWLAB_TASK_ID') # 将 task_id 设置为环境变量传过来的任务 ID
self.col.save(item) # 保存 item 在数据库中
return item
  同时,还需要在 items.py 中添加 task_id 字段,确保可以赋值(这个很重要)。
  上传并配置爬虫
  在运行爬虫之前,您需要将爬虫文件上传到主节点。进行如下操作:
  1.将爬虫文件打包成zip(请确保直接打包在根目录下);
  2.点击侧边栏的“爬虫”导航到爬虫列表,点击“添加爬虫”按钮选择“自定义爬虫”;
  3.点击“上传”按钮选择刚刚打包好的zip文件
  4.上传成功后,新添加的自定义爬虫会出现在爬虫列表中,所以上传成功。
  您可以点击爬虫详情中的“文件”选项卡选择一个文件来编辑文件中的代码。
  
  接下来,需要在“概览”选项卡的“执行命令”栏中输入爬虫的shell执行命令。Crawlab的Docker镜像内置了scrapy,可以直接运行scrapy爬虫。命令是scrapy crawl。点击“保存”按钮保存爬虫配置。
  运行爬虫任务
  然后是时候运行爬虫任务了。其实很简单,点击“概览”选项卡中的“运行”按钮即可启动爬虫任务。如果日志提示找不到scrapy命令,可以将scrapy改成绝对路径/usr/local/bin/scrapy,运行成功。
  任务的运行状态会显示在“任务”页面或爬虫的“概览”中,每5秒更新一次。你可以在这里查看。并且在爬虫“结果”选项卡中,您可以预览结果的详细信息并将数据导出到 CSV 文件。
  构建持续集成 (CI) 工作流
  软件开发通常是企业的自动化过程。它将经历需求、开发、部署、测试和启动的步骤。而且这个过程一般是迭代的,需要不断的更新和发布。
  以爬虫为例。您已经启动了一个爬虫。此爬虫将定期获取 网站 数据。但突然有一天,你发现无法捕获数据。你快速定位原因,发现网站版本已经修改。你需要改变爬虫的爬取规则来应对网站的修改。无论如何,您需要发布代码更新。最快的做法是直接在线更改代码。但这很危险:第一,不能测试自己更新的代码,只能通过不断调整在线代码来测试爬取是否成功;其次,如果出现问题,您以后无法记录更改,您很可能会忽略它。此更改导致了错误。您需要做的就是使用版本管理工具来管理您的爬虫代码。
  当我们更新代码时,我们需要将更新后的代码发布到在线服务器。这时候就需要自己编写部署脚本或者更方便的使用Jenkins作为持续集成管理平台。Jenkins 是一个持续集成平台,可以通过获取存储库来更新部署代码。它是一个非常实用的工具,被许多企业使用。下图是如何将 Crawlab 爬虫应用于持续集成工作流的示例。
  
  词
  在 Crawlab 中有两种创建或更新爬虫的方法:
  1.上传打包好的zip文件;
  2.通过更改主节点CRAWLAB_SPIDER_PATH目录下的爬虫文件。
  我们针对第二种方式进行持续集成。进行如下操作:
  1.使用Gitlab或者其他平台搭建代码仓库;
  2.在Jenkins中创建一个项目,并将代码源指向项目中之前创建的仓库;
  3.在Jenkins项目中编写工作流,并将发布地址指向Crawlab的CRAWLAB_SPIDER_PATH。如果是Docker,注意挂载地址到宿主机文件系统;
  4.Jenkins项目的工作可以直接写也可以使用Jenkinsfile查看相关信息;
  5.这样每次代码更新提交到代码仓库后,Jenkins都会将更新后的代码发布到Crawlab。Crawlab主节点会将爬虫代码同步到worker节点进行爬取。
  总结
  本篇文章主要介绍爬虫管理平台的定义,如何选择爬虫管理平台,重点介绍如何搭建开源的爬虫管理平台Crawlab。它还讨论了如何集成scrapy爬虫以及如何创建持续集成工作流。这篇文章文章并没有涉及很多内容,包括如何Crawlab的原理和架构细节,如何使用Crawlab,如何编写大型爬虫,如何使用Jenkins等等。这些内容可能会在其他文章上发表,感兴趣的读者请留意。此外,Crawlab 还有一些需要改进的地方,比如异常监控(零值、空值)、可配置爬虫、可视化爬取、集中日志采集等。
  相关文章 查看全部

  nodejs抓取动态网页(华为云开发者社区搭建python爬虫管理平台的相关内容吗)
  想知道快速搭建python爬虫管理平台的相关内容吗?在这篇文章中,华为云开发者社区将为大家讲解搭建python爬虫平台的相关知识和一些代码示例。欢迎阅读和指正。我们先把重点放在:python、爬虫、爬虫、构建,一起来学习。
  爬行动物有多重要
  对于搜索引擎来说,爬虫是必不可少的;对于舆论公司来说,爬虫是基础;对于 NLP,爬虫可以获得语料库;对于初创公司,爬虫可以获取初始内容。但是爬虫技术比较复杂,不同类型的爬取场景会用到不同的技术。比如一个简单的静态页面可以直接用HTTP请求+HTML解析器完成;动态页面需要使用 Puppeteer 或 Selenium 等自动化测试工具;反爬网站需要使用代理、编码等技术;等等。这时就需要一个成熟的爬虫管理平台来帮助企业或个人处理大量的爬虫类别。
  了解爬虫管理平台的定义是什么
  爬虫管理平台是集爬虫部署、任务调度、任务监控、结果展示等模块于一体的一站式管理系统。它通常配备可视化的 UI 界面,通过与 Web 端的 UI 界面交互,可以有效地管理爬虫。爬虫管理平台一般支持多机分布式协同运行。
  当然,上述定义是狭隘的,通常针对技术人员或开发人员或技术管理人员。企业一般会自行开发内部爬虫管理系统,以应对复杂的爬虫管理需求。这样的系统就是上面定义的狭义的爬虫管理平台。
  通用爬虫管理平台
  什么是通用爬虫管理平台?您可能听说过 优采云(后来转换为 优采云采集器)和 优采云。前者是基于云服务的爬虫管理平台,可以在线编写、运行和监控爬虫。是一般爬虫平台中最接近狭义的爬虫管理平台;后者是一种流行的商业爬虫爬取工具,允许新手用户通过拖放来编写和运行爬虫导出数据。您可能还见过各种 API 聚合服务提供商,例如 Aggregate Data。这是一个可以直接调用网站接口获取数据的平台。这实际上是爬虫平台的一个变种,但它可以帮助您完成爬虫编写过程。过程。而在两者之间,国外有个叫Kimonolab的公司,开发了一个Chrome插件叫Kimono,可以让用户直观的点击页面上的元素,生成抓取规则,放到网站爬虫生成后,用户提交任务,数据可以在网站上自动捕获。Kimono是一款很棒的爬虫应用,可惜Kimonolab已经被大数据公司Plantir收购,现在无法体验。
  在本文中,我们主要关注狭义的爬虫管理平台,因此后面提到的爬虫管理平台指的是狭义的。
  爬虫管理平台模块
  以下是一个典型的爬虫管理平台所涉及的模块。
  
  爬虫管理平台架构
  一个典型的爬虫管理平台的模块主要包括以下几个:
  当然,有些爬虫管理平台的模块可能不止这些。它可能包括其他更实用的功能,如可配置爬虫规则、可视化配置爬虫规则、代理池、cookie池、异常监控等。
  为什么需要爬虫管理平台
  有了爬虫管理平台,开发者,尤其是爬虫工程师,可以很方便的添加爬虫,执行任务,查看结果,不用在命令行之间来回切换,非常容易出错。一个常见的场景是爬虫工程师在最初的技术选型中使用了scrapy和crontab来管理爬虫任务。他必须仔细选择计划任务的时间间隔,以免填满服务器 CPU 或内存;比较棘手的问题是,他还需要将scrapy生成的日志保存在一个文件中。爬虫一旦出错,就得使用shell命令一一查看日志,定位出错原因。如果严重的话,需要一整天的时间;还有一个严重的问题。爬虫工程师可能会发现公司的业务量 当他需要编写数百个爬虫来满足公司的业务需求时,使用 scrapy 和 crontab 进行管理是一场彻头彻尾的噩梦。可怜的爬虫工程师其实可以选择一个合适的爬虫管理平台来解决他的问题。
  如何选择合适的爬虫管理平台
  当您愿意解决上述爬虫工程师遇到的难题,而是想选择合适的爬虫管理平台时。
  您应该回答的第一个问题是:我们是否需要从头开始开发系统(Start from scratch)?要回答这个问题,您应该首先回答以下问题:
  1.我们的需求是否复杂到可以完全定制一个新系统(例如,需要复杂的权限管理)?
  2.我们的团队是否有足够的技术力量来开发这个系统(例如经验丰富的前端和后端开发工程师)?
  3.我们的时间资源是否足以让我们开发系统(例如一年的项目规划周期)?
  如果以上三个问题的任何一个问题的答案都是“否”,您应该考虑使用市面上的开源爬虫管理平台来满足您的需求。
  以下是市面上的开源爬虫管理平台:
  
  总的来说,SpiderKeeper可能是最早的爬虫管理平台,但功能相对有限;Gerapy 有一个功能齐全且美观的界面,但有许多 bug 需要处理。建议有需要的用户等待2.0版本;Scrapydweb是一个比较完善的爬虫管理平台,基于scrapyd,所以只能运行scrapy爬虫;而 Crawlab 是一个非常灵活的爬虫管理平台,可以运行用 Python、Nodejs、Java、PHP 和 Go 编写的爬虫。比较全,但是部署起来比前三个麻烦一些,但是对于Docker用户来说可以一体部署(后面会讲)。
  所以严重依赖scrapy爬虫不想折腾的开发者可以考虑Scrapydweb;而对于具有各类复杂技术结构的爬虫开发者,应优先考虑更灵活的Crawlab。当然,并不是说 Crawlab 对 scrapy 支持不友好。Crawlab 也可以很好的集成scrapy。稍后会介绍。
  作为Crawlab的作者,我不希望王破卖瓜吹牛。笔者只是想把最好的技术选型推荐给开发者,让开发者可以根据自己的需求决定使用哪个爬虫管理平台。
  爬虫管理平台Crawlab介绍
  Crawlab是一个基于Golang的分布式爬虫管理平台,支持Python、NodeJS、Java、Go、PHP等多种编程语言和爬虫框架。
  Crawlab自今年3月上线以来,深受爬虫爱好者和开发者的好评。不少用户也表示会使用Crawlab搭建公司的爬虫平台。经过几个月的迭代,Crawlab先后推出了定时任务、数据分析、网站信息、可配置爬虫、自动字段提取、下载结果、上传爬虫等功能,使平台更加实用和全面,可以真正帮助用户解决爬虫管理的难题。现在 Github 上有近 1k 的明星相关社区,四分之一的用户表示已经将 Crawlab 应用到企业爬虫管理中。可见Crawlab受到开发者的关注和喜爱。
  解决这个问题
  Crawlab主要解决大量爬虫管理困难的问题,比如需要监控数百个网站混合的scrapy和selenium项目。同时不容易管理,命令行管理成本很高,容易出错。Crawlab 支持任意语言、任意框架的任务调度和任务监控,轻松有效监控和管理大型爬虫项目。
  接口和使用
  下面是 Crawlab 爬虫列表页面的截图。
  
  Crawlab爬虫列表
  用户只需要将爬虫上传到Crawlab配置执行命令,点击“运行”按钮即可执行爬虫任务。爬虫任务可以在任何节点上运行。从上图可以看出,Crawlab有节点管理、爬虫管理、任务管理、定时任务、用户管理等模块。
  整体结构
  以下是Crawlab的整体架构图,由五部分组成:
  1.Master Node:负责任务调度、API、爬虫部署等;
  2.Worker Node:负责执行爬虫任务;
  3.MongoDB数据库:存储节点、爬虫、任务等日常运行数据;
  4.Redis数据库:存储任务消息队列、节点心跳等信息。
  5.前端客户端:Vue应用负责前端交互,向后端请求数据。
  
  Github地址和Demo
  查看演示 DemoGithub:
  使用 Docker 部署安装 CrawlabDocker 映像
  Docker 是部署 Crawlab 最方便、最简洁的方式。其他部署方式包括直接部署,但不推荐给想要快速搭建平台的开发者。Crawlab 已经在 Dockerhub 上注册了相关镜像。开发者只需执行 docker pull tikazyq/crawlab 命令即可下载 Crawlab 镜像。
  读者可以去Dockerhub看看,Crawlab的镜像只有不到300Mb。地址:
  
  Dockerhub 页面
  安装 Docker
  要使用 Docker 部署 Crawlab,您必须首先确保已安装 Docker。请参考以下文档进行安装。
  
  安装 Docker Compose
  Docker Compose 是一个运行 Docker 集群的简单工具。非常轻巧。我们将使用 Docker Compose 一键部署 Crawlab。
  Docker 官方 网站 已经有安装 Docker Compose 的教程,点击链接查看。这里有一个简单的介绍。
  
  对于 Linux 用户,请使用以下命令进行安装。
  
# 下载 docker-compose
sudo curl -L "https://github.com/docker/comp ... pose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose`
# 将 docker-compose 变成执行文件
sudo chmod +x /usr/local/bin/docker-compose
  拉图像
  拉取镜像前需要配置镜像源。因为在国内使用原创镜像源的速度不是很快,所以需要使用DockerHub的国产加速器。请创建 /etc/docker/daemon.json 文件并输入以下内容。
  
{
"registry-mirrors": ["https://registry.docker-cn.com"]
}
  然后拉图像会快得多。当然,您也可以使用其他镜像源在线搜索。运行以下命令拉取 Crawlab 镜像。
  
docker pull tikazyq/crawlab:latest
  下图是拉取镜像时的命令行界面。
  
  码头工人拉
  开始爬行实验室
  我们将使用 Docker Compose 启动 Crawlab 及其依赖的数据库 MongoDB 和 Redis。首先我们需要修改Docker Compose的yaml配置文件docker-compose.yml。该配置文件定义了需要启动的容器服务和网络配置。这里我们使用 Crawlab 自带的 docker-compose.yml。
  
version: '3.3' # Docker Compose 的版本号(请看后续说明)
services: # 服务
master: # 服务名称
image: tikazyq/crawlab:latest # 服务对应的镜像名称
container_name: master # 服务对应的容器名称
environment: # 这里定义传入的环境变量
CRAWLAB_API_ADDRESS: "localhost:8000" # 前端调用的 API 地址默认为 localhost:8000
CRAWLAB_SERVER_MASTER: "Y" # 是否为主节点Y/N
CRAWLAB_MONGO_HOST: "mongo" # MongoDB host由于在 Docker Compose 里可以引用服务名称
CRAWLAB_REDIS_ADDRESS: "redis" # Redis host由于在 Docker Compose 里可以引用服务名称
ports: # 映射的端口
- "8080:8080" # 前端端口
- "8000:8000" # 后端端口
depends_on: # 依赖的服务
- mongo # MongoDB
- redis # Redis
worker: # 工作节点与主节点配置类似不重复写了
image: tikazyq/crawlab:latest
container_name: worker
environment:
CRAWLAB_SERVER_MASTER: "N"
CRAWLAB_MONGO_HOST: "mongo"
CRAWLAB_REDIS_ADDRESS: "redis"
depends_on:
- mongo
- redis
mongo: # MongoDB 服务名称
image: mongo:latest # MongoDB 镜像名称
restart: always # 重启策略为“总是”
ports: # 映射端口
- "27017:27017"
redis: # Redis 服务名称
image: redis:latest # Redis 镜像名称
restart: always # 重启策略为“总是”
ports: # 映射端口
- "6379:6379"
  读者可以根据自己的需求配置docker-compose.yml。特别要注意环境变量 CRAWLAB_API_ADDRESS。很多新手用户因为变量配置不正确而无法登录。在大多数情况下,您不需要进行任何配置更改。请参阅常见问题解答和详细的环境变量配置文档,以帮助根据您的环境配置 Crawlab。
  然后运行以下命令启动 Crawlab。您可以添加 -d 参数以使 Docker Compose 在后台运行。
  
docker-compose up
  运行上述命令后,Docker Compose 将拉取 MongoDB 和 Redis 映像,这可能需要几分钟时间。拉取完成后,四个服务会依次启动。您将在命令行上看到以下内容。
  
  码头工人撰写
  正常情况下应该可以看到四个服务全部启动成功,并且可以顺利打印日志。
  如果在本地机器上启动Docker Compose,可以在浏览器中输入:8080,可以看到登录界面;如果在另一台机器上启动Docker Compose,需要在浏览器中输入:8080才能看到登录界面是其他机器的IP地址(请确保本机8080端口对外开放)。
  
  登录
  初始登录用户名和密码为 admin/admin,您可以使用此用户名和密码登录。如果您的环境变量 CRAWLAB_API_ADDRESS 设置不正确,您可能会看到登录按钮在单击登录后一直在旋转而没有任何提示。此时请在 docker-compose.yml 中设置正确的 CRAWLAB_API_ADDRESS(将 localhost 替换为)并重启 docker-compose up。然后在浏览器中输入:8080。
  登录后,您将看到 Crawlab 主页。
  
  家
  本文文章主要介绍如何搭建爬虫管理平台Crawlab,所以不会详细介绍如何使用Crawlab(可以另写一篇文章文章详细介绍,有兴趣的可以付费注意力)。如果您感到困惑,请查看相关文档以了解如何使用它。
  如何将 Scrapy 等爬虫集成到 Crawlab 中
  众所周知,Scrapy 是一个非常流行的爬虫框架。其灵活的框架设计、高并发、易用性和可扩展性已被众多开发者和企业广泛采用。市面上几乎所有爬虫管理平台都支持Scrapy爬虫Crawlab也不例外,但Crawlab可以运行puppeteer、selenium等其他爬虫。下面将介绍如何在Crawlab中运行scrapy爬虫。
  Crawlab是执行爬虫的基本原理
  Crawlab的爬虫原理很简单。它实际上是一个 shell 命令。用户在爬虫中输入shell命令执行爬虫,比如scrapy crawl some_spiderCrawlab执行器会读取这个命令,直接在shell中执行。因此,每次运行爬虫任务时,都要执行一个shell命令(当然实际情况比这复杂得多,有兴趣的可以参考官方文档)。Crawlab 支持显示和导出爬虫结果,但这需要更多的工作。
  编写管道
  集成scrapy爬虫无非是将爬虫爬取的数据存储在Crawlab的数据库中,并与任务ID相关联。每次执行爬虫任务时,都会通过环境变量将任务ID传递给爬虫程序,所以我们只需要将任务ID加上结果存入数据库即可(Crawlab目前只支持MongoDB,后续会开发) MySQL、SQL Server、Postgres 等关系类型未来需要数据库的用户可以关注)。
  在 Scrapy 中,我们需要编写存储逻辑。原理图代码如下:
  
# 引入相关的库pymongo 是标准连接 MongoDB 的库
import os
from pymongo import MongoClient
# MongoDB 配置参数
MONGO_HOST = '192.168.99.100'
MONGO_PORT = 27017
MONGO_DB = 'crawlab_test'
class JuejinPipeline(object):
mongo = MongoClient(host=MONGO_HOST, port=MONGO_PORT) # mongo 连接实例
db = mongo[MONGO_DB] # 数据库实例
col_name = os.environ.get('CRAWLAB_COLLECTION') # 集合名称通过环境变量 CRAWLAB_COLLECTION 传过来
# 如果 CRAWLAB_COLLECTION 不存在则默认集合名称为 test
if not col_name:
col_name = 'test'

col = db[col_name] # 集合实例
# 每一个传入 item 会调用的函数参数分别为 item 和 spider
def process_item(self, item, spider):
item['task_id'] = os.environ.get('CRAWLAB_TASK_ID') # 将 task_id 设置为环境变量传过来的任务 ID
self.col.save(item) # 保存 item 在数据库中
return item
  同时,还需要在 items.py 中添加 task_id 字段,确保可以赋值(这个很重要)。
  上传并配置爬虫
  在运行爬虫之前,您需要将爬虫文件上传到主节点。进行如下操作:
  1.将爬虫文件打包成zip(请确保直接打包在根目录下);
  2.点击侧边栏的“爬虫”导航到爬虫列表,点击“添加爬虫”按钮选择“自定义爬虫”;
  3.点击“上传”按钮选择刚刚打包好的zip文件
  4.上传成功后,新添加的自定义爬虫会出现在爬虫列表中,所以上传成功。
  您可以点击爬虫详情中的“文件”选项卡选择一个文件来编辑文件中的代码。
  
  接下来,需要在“概览”选项卡的“执行命令”栏中输入爬虫的shell执行命令。Crawlab的Docker镜像内置了scrapy,可以直接运行scrapy爬虫。命令是scrapy crawl。点击“保存”按钮保存爬虫配置。
  运行爬虫任务
  然后是时候运行爬虫任务了。其实很简单,点击“概览”选项卡中的“运行”按钮即可启动爬虫任务。如果日志提示找不到scrapy命令,可以将scrapy改成绝对路径/usr/local/bin/scrapy,运行成功。
  任务的运行状态会显示在“任务”页面或爬虫的“概览”中,每5秒更新一次。你可以在这里查看。并且在爬虫“结果”选项卡中,您可以预览结果的详细信息并将数据导出到 CSV 文件。
  构建持续集成 (CI) 工作流
  软件开发通常是企业的自动化过程。它将经历需求、开发、部署、测试和启动的步骤。而且这个过程一般是迭代的,需要不断的更新和发布。
  以爬虫为例。您已经启动了一个爬虫。此爬虫将定期获取 网站 数据。但突然有一天,你发现无法捕获数据。你快速定位原因,发现网站版本已经修改。你需要改变爬虫的爬取规则来应对网站的修改。无论如何,您需要发布代码更新。最快的做法是直接在线更改代码。但这很危险:第一,不能测试自己更新的代码,只能通过不断调整在线代码来测试爬取是否成功;其次,如果出现问题,您以后无法记录更改,您很可能会忽略它。此更改导致了错误。您需要做的就是使用版本管理工具来管理您的爬虫代码。
  当我们更新代码时,我们需要将更新后的代码发布到在线服务器。这时候就需要自己编写部署脚本或者更方便的使用Jenkins作为持续集成管理平台。Jenkins 是一个持续集成平台,可以通过获取存储库来更新部署代码。它是一个非常实用的工具,被许多企业使用。下图是如何将 Crawlab 爬虫应用于持续集成工作流的示例。
  
  词
  在 Crawlab 中有两种创建或更新爬虫的方法:
  1.上传打包好的zip文件;
  2.通过更改主节点CRAWLAB_SPIDER_PATH目录下的爬虫文件。
  我们针对第二种方式进行持续集成。进行如下操作:
  1.使用Gitlab或者其他平台搭建代码仓库;
  2.在Jenkins中创建一个项目,并将代码源指向项目中之前创建的仓库;
  3.在Jenkins项目中编写工作流,并将发布地址指向Crawlab的CRAWLAB_SPIDER_PATH。如果是Docker,注意挂载地址到宿主机文件系统;
  4.Jenkins项目的工作可以直接写也可以使用Jenkinsfile查看相关信息;
  5.这样每次代码更新提交到代码仓库后,Jenkins都会将更新后的代码发布到Crawlab。Crawlab主节点会将爬虫代码同步到worker节点进行爬取。
  总结
  本篇文章主要介绍爬虫管理平台的定义,如何选择爬虫管理平台,重点介绍如何搭建开源的爬虫管理平台Crawlab。它还讨论了如何集成scrapy爬虫以及如何创建持续集成工作流。这篇文章文章并没有涉及很多内容,包括如何Crawlab的原理和架构细节,如何使用Crawlab,如何编写大型爬虫,如何使用Jenkins等等。这些内容可能会在其他文章上发表,感兴趣的读者请留意。此外,Crawlab 还有一些需要改进的地方,比如异常监控(零值、空值)、可配置爬虫、可视化爬取、集中日志采集等。
  相关文章

nodejs抓取动态网页(谷歌发布的一款Chromium无头的基本使用(s)模块)

网站优化优采云 发表了文章 • 0 个评论 • 57 次浏览 • 2022-01-25 10:04 • 来自相关话题

  nodejs抓取动态网页(谷歌发布的一款Chromium无头的基本使用(s)模块)
  我们可以使用 nodejs 的 http(s) 模块来请求目标 URL,也可以使用 axios 之类的第三方库来请求网页。拿到html后,使用html解析库解析成方便的js对象,然后抓取目标内容。这里推荐解析库cheerio。Cheerio 提供了类似 jQuery 的操作方式,可以轻松抓取我们想要的内容。
  // demo1
const cheerio = require('cheerio')
const axios = require('axios')
async function main() {
const resp = await axios.get('https://www.jianshu.com/')
console.log(resp.data)
}
main()
  通过节点demo1执行demo1,我们可以得到短书首页的html。
  
  图像.png
  现在让我们改进demo1的代码,使用cheerio来解析html。
  // demo1
const cheerio = require('cheerio')
const axios = require('axios')
async function main() {
const resp = await axios.get('https://www.jianshu.com/')
const $ = cheerio.load(resp.data)
const articleList = []
$('#list-container li').each((i, el) => {
articleList.push({
id: $(el).attr('id').replace('', ''),
title: unescape($(el).find('.title').html().replace(/&#x/g, '%u').replace(/;/g, '')),
href: $(el).find('.title').attr('href'),
})
})
console.log(articleList)
}
main()
  
  图像.png
  这样我们就得到了文章的id、title和link。
  采用这种方式捕获数据的优点是效率高,占用的硬件资源极少。但是这种方法也有一些缺点。例如,延迟加载页面很难爬取。因为懒加载页面的内容是由浏览器js动态加载的,这样对于我们请求的页面,由于js还没有渲染页面上的内容,我们无法正确获取页面内容,而我们的请求也未能提供页面js所需的运行环境。这时候,我们将介绍另一种爬虫。
  无头浏览器
  无头浏览器是一种在后台运行的网络浏览器程序,没有操作界面。常用的无头浏览器有PhantomJS、Selenium、Puppeteer等(还有很多很多,有兴趣的可以去看看)。
  无头浏览器常用于自动化测试、爬虫等技术。它提供了浏览器js所需的运行环境,我们可以利用它来处理延迟加载的页面。
  Puppeteer 是谷歌发布的 Chromium 无头浏览器。这里我们以 Puppeteer 为例,编写一些小 Demo。
  // demo2
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch({
// 是否为无头,无头模式下没有用户操作界面
headless: false,
defaultViewport: null
})
const page = await browser.newPage();
await page.goto('https://www.jianshu.com/u/04d11dd2f924');
}
main()
  让我们使用 node demo2 来执行:
  
  图像.png
  我们现在已经用代码打开了一个浏览器窗口。接下来我们会继续完善demo2来爬取这个页面的内容。
  // demo2
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch({
// 是否为无头,无头模式下没有用户操作界面
headless: false,
defaultViewport: null,
devtools: true,
})
const page = await browser.newPage();
await page.goto('https://www.jianshu.com/u/04d11dd2f924');
const articleList = await page.$$eval('#list-container li', els =>
els.map(el => {
const id = el.dataset.noteId
const title = el.querySelector('.title').innerText
const url = el.querySelector('.title').href
return {id, title, url}
})
)
console.log(articleList)
}
main()
  执行这段代码,我们得到以下结果:
  
  图像.png
  这是无头浏览器的基本用法。它还提供了很多方便的API,比如page.waitFor(selector, pageFun)方法,它可以在执行pageFun方法之前等待一个元素出现在页面上。还有page.addScriptTag(options)方法,可以直接在页面上使用。注入你自己的js脚本。
  在这里,再次请大家不要滥用爬虫采集的资源去攻击别人的服务器。
  两种方式的比较 查看全部

  nodejs抓取动态网页(谷歌发布的一款Chromium无头的基本使用(s)模块)
  我们可以使用 nodejs 的 http(s) 模块来请求目标 URL,也可以使用 axios 之类的第三方库来请求网页。拿到html后,使用html解析库解析成方便的js对象,然后抓取目标内容。这里推荐解析库cheerio。Cheerio 提供了类似 jQuery 的操作方式,可以轻松抓取我们想要的内容。
  // demo1
const cheerio = require('cheerio')
const axios = require('axios')
async function main() {
const resp = await axios.get('https://www.jianshu.com/')
console.log(resp.data)
}
main()
  通过节点demo1执行demo1,我们可以得到短书首页的html。
  
  图像.png
  现在让我们改进demo1的代码,使用cheerio来解析html。
  // demo1
const cheerio = require('cheerio')
const axios = require('axios')
async function main() {
const resp = await axios.get('https://www.jianshu.com/')
const $ = cheerio.load(resp.data)
const articleList = []
$('#list-container li').each((i, el) => {
articleList.push({
id: $(el).attr('id').replace('', ''),
title: unescape($(el).find('.title').html().replace(/&#x/g, '%u').replace(/;/g, '')),
href: $(el).find('.title').attr('href'),
})
})
console.log(articleList)
}
main()
  
  图像.png
  这样我们就得到了文章的id、title和link。
  采用这种方式捕获数据的优点是效率高,占用的硬件资源极少。但是这种方法也有一些缺点。例如,延迟加载页面很难爬取。因为懒加载页面的内容是由浏览器js动态加载的,这样对于我们请求的页面,由于js还没有渲染页面上的内容,我们无法正确获取页面内容,而我们的请求也未能提供页面js所需的运行环境。这时候,我们将介绍另一种爬虫。
  无头浏览器
  无头浏览器是一种在后台运行的网络浏览器程序,没有操作界面。常用的无头浏览器有PhantomJS、Selenium、Puppeteer等(还有很多很多,有兴趣的可以去看看)。
  无头浏览器常用于自动化测试、爬虫等技术。它提供了浏览器js所需的运行环境,我们可以利用它来处理延迟加载的页面。
  Puppeteer 是谷歌发布的 Chromium 无头浏览器。这里我们以 Puppeteer 为例,编写一些小 Demo。
  // demo2
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch({
// 是否为无头,无头模式下没有用户操作界面
headless: false,
defaultViewport: null
})
const page = await browser.newPage();
await page.goto('https://www.jianshu.com/u/04d11dd2f924');
}
main()
  让我们使用 node demo2 来执行:
  
  图像.png
  我们现在已经用代码打开了一个浏览器窗口。接下来我们会继续完善demo2来爬取这个页面的内容。
  // demo2
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch({
// 是否为无头,无头模式下没有用户操作界面
headless: false,
defaultViewport: null,
devtools: true,
})
const page = await browser.newPage();
await page.goto('https://www.jianshu.com/u/04d11dd2f924');
const articleList = await page.$$eval('#list-container li', els =>
els.map(el => {
const id = el.dataset.noteId
const title = el.querySelector('.title').innerText
const url = el.querySelector('.title').href
return {id, title, url}
})
)
console.log(articleList)
}
main()
  执行这段代码,我们得到以下结果:
  
  图像.png
  这是无头浏览器的基本用法。它还提供了很多方便的API,比如page.waitFor(selector, pageFun)方法,它可以在执行pageFun方法之前等待一个元素出现在页面上。还有page.addScriptTag(options)方法,可以直接在页面上使用。注入你自己的js脚本。
  在这里,再次请大家不要滥用爬虫采集的资源去攻击别人的服务器。
  两种方式的比较

nodejs抓取动态网页(爬虫爬取的流程和最终如何展示数据的地址?)

网站优化优采云 发表了文章 • 0 个评论 • 81 次浏览 • 2022-01-25 10:03 • 来自相关话题

  nodejs抓取动态网页(爬虫爬取的流程和最终如何展示数据的地址?)
  其实很早以前我做过即时理财的销售统计,不过是前端js写的。需要在首页的控制台调试面板中粘贴一段代码来执行,点这里。主要是通过定期爬取异步接口来获取数据。然后,通过一定的排序算法得到最终的数据。但这样做有以下缺点:
  0. 代码只能在浏览器窗口下运行,关闭浏览器或电脑无效
  0. 只能爬取一个页面的数据,不能整合其他页面的数据
  0. 爬取的数据无法存储在本地
  0. 上面的异步接口数据会被部分过滤,导致我们的重载算法失败
  由于最近学习了节点爬虫,所以可以在后台模拟请求,爬取页面数据。而且我已经开通了阿里云服务器,可以把代码放到云端运行。这样,1、2、3就可以解决了。4是因为我不知道这个ajax接口每三分钟更新一次,所以我们可以按照这个来安排权重,保证数据不会重复。说到爬虫,大部分人都会想到python。确实,python有Scrapy等成熟的框架,可以实现强大的爬取功能。但是,节点也有自己的优势。凭借其强大的异步特性,可以轻松实现高效的异步并发请求,节省CPU开销。其实节点爬虫比较简单。让'
  在线地址
  一、爬虫进程
  我们的最终目标是爬取利马财经的每日销售额,并了解哪些产品被销售,哪些用户在什么时间购买了每个产品。首先介绍爬取的主要步骤:
  1. 结构分析
  如果我们要爬取页面的数据,第一步当然是分析页面的结构,要爬哪些页面,页面的结构是什么,是否登录;是否有ajax接口,返回什么样的数据等。
  2. 数据抓取
  分析好爬取哪些页面和ajax后,就需要爬取数据了。现在网页的数据大致分为同步页面和ajax接口。要获取同步页面数据,我们需要先分析网页的结构。Python取数据一般通过正则表达式匹配来获取需要的数据;node有一个cheerio工具,可以将获取到的页面内容转换成jquery对象,然后可以使用jquery强大的dom API来获取node相关的数据。其实看源码的话,这些API的本质就是正则匹配。Ajax接口数据一般为json格式,处理起来比较简单。
  3. 数据存储
  采集到数据后,会进行简单的筛选,然后将需要的数据保存下来,供后续分析处理。当然我们可以将数据存储在 MySQL 和 Mongodb 等数据库中。这里,为了方便,我们直接使用文件存储。
  4. 数据分析
  因为我们最终要展示数据,所以需要对原创数据按照一定的维度进行处理和分析,然后返回给客户端。这个过程可以在存储的时候进行处理,也可以在显示的时候,前端发送请求,后台取回存储的数据进行处理。这取决于我们希望如何显示数据。
  5. 结果显示
  做了这么多工作,一点显示输出都没有,怎么舍得?这又回到了我们的老业务,前端展示页面大家应该都很熟悉了。数据展示更加直观,方便我们进行统计分析。
  二、爬虫常用库介绍1. Superagent
  Superagent 是一个轻量级的 http 库。是nodejs中一个非常方便的客户端请求代理模块。当我们需要进行get、post、head等网络请求的时候,试试吧。
  2. 谢里奥
  Cheerio 可以理解为 jquery 的 Node.js 版本,用于从带有 css 选择器的网页中获取数据,方式与 jquery 完全相同。
  3. 异步
  Async 是一个流程控制工具包,提供了直接且强大的异步函数 mapLimit(arr, limit, iterator, callback)。我们主要使用这种方法。您可以在官方网站上查看API。
  4. arr-del
  arr-del 是我自己写的一个删除数组元素方法的工具。可以通过传入一个由要删除的数组元素的索引组成的数组来执行一次性删除。
  5. arr-排序
  arr-sort 是我自己编写的用于对数组进行排序的工具。可以按一个或多个属性排序,支持嵌套属性。并且可以指定每个条件下的排序方向,并支持传入比较函数。
  三、页面结构分析
  让我们重复我们首先抓取的想法。利马理财在线的产品主要是普通的利马银行(光大银行最新的理财产品,手续复杂,前期投入大,基本没人买,这里不计)。定期我们可以爬取财务页面的ajax接口:. (更新:近期定期缺货,可能看不到数据)数据如下图所示:
  
  这包括在线销售的所有常规产品。ajax数据只有产品本身相关的信息,比如产品id、募集金额、当前销售额、年化收益率、投资天数等,并没有哪些用户购买了产品的信息。. 所以我们需要到它的商品详情页面去爬取id参数,比如Lima Jucai-December issue HLB01239511。详情页有一栏投资记录,里面收录了我们需要的信息,如下图所示:
  
  但是详情页需要我们登录才能查看,这就需要我们带着cookie来访问,而且cookie的有效期是有限的。如何让我们的cookie保持登录状态?请看下文。
  其实Lima Vault也有类似的ajax接口:,但是里面的相关数据都写死了,没有意义。而且,金库的详情页没有投资记录信息。这就需要我们爬取开头提到的首页的ajax接口:. 但是后来发现这个接口每三分钟更新一次,也就是说后台每三分钟向服务器请求一次数据。并且一次有10条数据,所以如果三分钟内购买产品的记录数超过10条,数据将被省略。没有办法绕过它,所以金库统计数据会立即低于真实数据。
  四、爬虫代码分析1.获取登录cookie
  因为商品详情页面需要登录,所以我们需要先获取登录cookie。getCookie 方法如下:
  function getCookie() {
superagent.post('https://www.lmlc.com/user/s/web/logon')
.type('form')
.send({
phone: phone,
password: password,
productCode: "LMLC",
origin: "PC"
})
.end(function(err, res) {
if (err) {
handleErr(err.message);
return;
}
cookie = res.header['set-cookie']; //从response中得到cookie
emitter.emit("setCookeie");
})
}
  手机和密码参数是命令行传入的,就是你用手机号登录的账号和密码。我们使用superagent模拟即时财务管理登录界面的请求:. 传入相应的参数,在回调中我们获取到header的set-cookie信息,并发出setCookieie事件。因为我们设置了监听事件:emitter.on("setCookie", requestData),一旦获取到cookie,我们就会执行requestData方法。
  2. 财务页面ajax的爬取
  requestData 方法的代码如下:
<p>function requestData() {
superagent.get('https://www.lmlc.com/web/product/product_list?pageSize=100&pageNo=1&type=0')
.end(function(err,pres){
// 常规的错误处理
if (err) {
handleErr(err.message);
return;
}
// 在这里清空数据,避免一个文件被同时写入
if(clearProd){
fs.writeFileSync('data/prod.json', JSON.stringify([]));
clearProd = false;
}
let addData = JSON.parse(pres.text).data;
let formatedAddData = formatData(addData.result);
let pageUrls = [];
if(addData.totalPage > 1){
handleErr('产品个数超过100个!');
return;
}
for(let i=0,len=addData.result.length; i 查看全部

  nodejs抓取动态网页(爬虫爬取的流程和最终如何展示数据的地址?)
  其实很早以前我做过即时理财的销售统计,不过是前端js写的。需要在首页的控制台调试面板中粘贴一段代码来执行,点这里。主要是通过定期爬取异步接口来获取数据。然后,通过一定的排序算法得到最终的数据。但这样做有以下缺点:
  0. 代码只能在浏览器窗口下运行,关闭浏览器或电脑无效
  0. 只能爬取一个页面的数据,不能整合其他页面的数据
  0. 爬取的数据无法存储在本地
  0. 上面的异步接口数据会被部分过滤,导致我们的重载算法失败
  由于最近学习了节点爬虫,所以可以在后台模拟请求,爬取页面数据。而且我已经开通了阿里云服务器,可以把代码放到云端运行。这样,1、2、3就可以解决了。4是因为我不知道这个ajax接口每三分钟更新一次,所以我们可以按照这个来安排权重,保证数据不会重复。说到爬虫,大部分人都会想到python。确实,python有Scrapy等成熟的框架,可以实现强大的爬取功能。但是,节点也有自己的优势。凭借其强大的异步特性,可以轻松实现高效的异步并发请求,节省CPU开销。其实节点爬虫比较简单。让'
  在线地址
  一、爬虫进程
  我们的最终目标是爬取利马财经的每日销售额,并了解哪些产品被销售,哪些用户在什么时间购买了每个产品。首先介绍爬取的主要步骤:
  1. 结构分析
  如果我们要爬取页面的数据,第一步当然是分析页面的结构,要爬哪些页面,页面的结构是什么,是否登录;是否有ajax接口,返回什么样的数据等。
  2. 数据抓取
  分析好爬取哪些页面和ajax后,就需要爬取数据了。现在网页的数据大致分为同步页面和ajax接口。要获取同步页面数据,我们需要先分析网页的结构。Python取数据一般通过正则表达式匹配来获取需要的数据;node有一个cheerio工具,可以将获取到的页面内容转换成jquery对象,然后可以使用jquery强大的dom API来获取node相关的数据。其实看源码的话,这些API的本质就是正则匹配。Ajax接口数据一般为json格式,处理起来比较简单。
  3. 数据存储
  采集到数据后,会进行简单的筛选,然后将需要的数据保存下来,供后续分析处理。当然我们可以将数据存储在 MySQL 和 Mongodb 等数据库中。这里,为了方便,我们直接使用文件存储。
  4. 数据分析
  因为我们最终要展示数据,所以需要对原创数据按照一定的维度进行处理和分析,然后返回给客户端。这个过程可以在存储的时候进行处理,也可以在显示的时候,前端发送请求,后台取回存储的数据进行处理。这取决于我们希望如何显示数据。
  5. 结果显示
  做了这么多工作,一点显示输出都没有,怎么舍得?这又回到了我们的老业务,前端展示页面大家应该都很熟悉了。数据展示更加直观,方便我们进行统计分析。
  二、爬虫常用库介绍1. Superagent
  Superagent 是一个轻量级的 http 库。是nodejs中一个非常方便的客户端请求代理模块。当我们需要进行get、post、head等网络请求的时候,试试吧。
  2. 谢里奥
  Cheerio 可以理解为 jquery 的 Node.js 版本,用于从带有 css 选择器的网页中获取数据,方式与 jquery 完全相同。
  3. 异步
  Async 是一个流程控制工具包,提供了直接且强大的异步函数 mapLimit(arr, limit, iterator, callback)。我们主要使用这种方法。您可以在官方网站上查看API。
  4. arr-del
  arr-del 是我自己写的一个删除数组元素方法的工具。可以通过传入一个由要删除的数组元素的索引组成的数组来执行一次性删除。
  5. arr-排序
  arr-sort 是我自己编写的用于对数组进行排序的工具。可以按一个或多个属性排序,支持嵌套属性。并且可以指定每个条件下的排序方向,并支持传入比较函数。
  三、页面结构分析
  让我们重复我们首先抓取的想法。利马理财在线的产品主要是普通的利马银行(光大银行最新的理财产品,手续复杂,前期投入大,基本没人买,这里不计)。定期我们可以爬取财务页面的ajax接口:. (更新:近期定期缺货,可能看不到数据)数据如下图所示:
  
  这包括在线销售的所有常规产品。ajax数据只有产品本身相关的信息,比如产品id、募集金额、当前销售额、年化收益率、投资天数等,并没有哪些用户购买了产品的信息。. 所以我们需要到它的商品详情页面去爬取id参数,比如Lima Jucai-December issue HLB01239511。详情页有一栏投资记录,里面收录了我们需要的信息,如下图所示:
  
  但是详情页需要我们登录才能查看,这就需要我们带着cookie来访问,而且cookie的有效期是有限的。如何让我们的cookie保持登录状态?请看下文。
  其实Lima Vault也有类似的ajax接口:,但是里面的相关数据都写死了,没有意义。而且,金库的详情页没有投资记录信息。这就需要我们爬取开头提到的首页的ajax接口:. 但是后来发现这个接口每三分钟更新一次,也就是说后台每三分钟向服务器请求一次数据。并且一次有10条数据,所以如果三分钟内购买产品的记录数超过10条,数据将被省略。没有办法绕过它,所以金库统计数据会立即低于真实数据。
  四、爬虫代码分析1.获取登录cookie
  因为商品详情页面需要登录,所以我们需要先获取登录cookie。getCookie 方法如下:
  function getCookie() {
superagent.post('https://www.lmlc.com/user/s/web/logon')
.type('form')
.send({
phone: phone,
password: password,
productCode: "LMLC",
origin: "PC"
})
.end(function(err, res) {
if (err) {
handleErr(err.message);
return;
}
cookie = res.header['set-cookie']; //从response中得到cookie
emitter.emit("setCookeie");
})
}
  手机和密码参数是命令行传入的,就是你用手机号登录的账号和密码。我们使用superagent模拟即时财务管理登录界面的请求:. 传入相应的参数,在回调中我们获取到header的set-cookie信息,并发出setCookieie事件。因为我们设置了监听事件:emitter.on("setCookie", requestData),一旦获取到cookie,我们就会执行requestData方法。
  2. 财务页面ajax的爬取
  requestData 方法的代码如下:
<p>function requestData() {
superagent.get('https://www.lmlc.com/web/product/product_list?pageSize=100&pageNo=1&type=0')
.end(function(err,pres){
// 常规的错误处理
if (err) {
handleErr(err.message);
return;
}
// 在这里清空数据,避免一个文件被同时写入
if(clearProd){
fs.writeFileSync('data/prod.json', JSON.stringify([]));
clearProd = false;
}
let addData = JSON.parse(pres.text).data;
let formatedAddData = formatData(addData.result);
let pageUrls = [];
if(addData.totalPage > 1){
handleErr('产品个数超过100个!');
return;
}
for(let i=0,len=addData.result.length; i

官方客服QQ群

微信人工客服

QQ人工客服


线