前言
某位不愿意透漏姓名的同事要定期访问某些网站,复制页面的某条信息到自己电脑上。
整个操作简单、重复性高。他找到我以后,其实,一开始我是拒绝的
本来想用 Tampermonkey 写个脚本,打开网站的时候自动复制数据。但是!油猴没办法将数据存到文件里。
很明显了,爬虫最适合干这事。
JavaScript怎么写爬虫?
主角登场👉 Puppeteer
Puppeteer是什么
- Puppeteer 是无头浏览器(没有浏览器界面)
- Puppeteer 由Google Chrome出品(ps:谷歌的亲孙子😬)
- Puppeteer 能创建真实行为,比如输入
- Puppeteer 支持async await
简单说,Puppeteer 可以用程序控制浏览器操作,操作过程能看到也能隐藏。
推荐文档
1.准备工作
被爬的网站
ps:一个专门用来练习爬虫的网站,随便爬。👍
可以看到很多例站,后面的代码以ssr2为例,地址是 ssr2.scrape.center
实现目标
我们要从电影的详情页拿到三个信息,然后组成自己的信息页面
- 电影标题
- 电影描述
- 上映时间
工具
人工操作浏览器的流程图
Puppeteer的工作流程和人工一模一样,不过是把点击的动作换成了代码。
看一下用到的包和工具
Node.js版本 v14.16.0
npm版本 7.20.0
ps:参考就好,不要求完全一致。
| npm包 | 用途 |
|---|---|
| puppeteer | 核心库,操作浏览器 |
| bufferutil | puppeteer依赖,必须安装 |
| utf-8-validate | puppeteer依赖,必须安装 |
| swig-templates | html模板引擎,最后展示数据用 |
| jQuery | 抓取数据(可不用,puppeteer自带一些方法) |
2.分析
目标站 - 首页:ssr2.scrape.center
目标站的首页是不包括电影描述的,所以得从详情页面入手。
这是三个详情页的地址,可以发现,url的构成是 https://ssr2.scrape.center/detail/ + 数字
所以只要构造出url就可以拿到数据。
同时,找出对应数据的CSS选择器(后面通过选择器查找数据)
| 内容 | CSS选择器 |
|---|---|
| 标题 | h2.m-b-sm |
| 描述 | .drama p |
| 时间 | .m-v-sm.info+.m-v-sm.info |
3.开发
新建一个项目
安装包
💡puppeteer用npm安装会报错,直接用cnpm就好了
puppeteer的包很大,因为它依赖Chromium浏览器,会在你的项目中下载一个浏览器
cnpm i puppeteer --save
提示缺少两个包,装一下
cnpm i bufferutil utf-8-validate --save
再安装一下模板引擎
cnpm i swig-templates --save
新建index.js
// 引入包
const puppeteer = require("puppeteer");
const swig = require("swig-templates");
// 爬取数量
const websiteNumber = 3;
// 配置
const config = {
name : "SSR2爬虫练习", // 爬虫名字
url : "https://ssr2.scrape.center/detail/@@", // 地址,@@作为url变量
$title : "h2.m-b-sm", // 标题Dom
$describe: ".drama p", // 描述Dom
$time : ".m-v-sm.info+.m-v-sm.info", // 时间Dom
};
// 生成网站列表
let websiteList = new Array(websiteNumber).fill().map((item, index) => {
let _item = Object.assign({}, config);
_item.url = _item.url.replace(/@@/g, index + 1);
return _item;
});
(async () => {
// 1.运行浏览器
// ----------------------------------
const browser = await puppeteer.launch({
headless: true, // 改成 false 就能看到浏览器界面
});
for (const websiteItem of websiteList) {
const url = websiteItem.url;
// 2.新建页面
// ----------------------------------
const page = await browser.newPage();
// 3.跳转页面
// ----------------------------------
await page.goto(url);
console.log(`打开地址:${url}`);
// 4.抓取数据(使用jQuery)
// ----------------------------------
// 注入jQuery
await page.mainFrame().addScriptTag({
url: "https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js",
});
const pageData = await page.evaluate((websiteItemInPage) => {
// 这里的代码与函数外是隔离的,不能使用外面的方法
let data = {
title : $(websiteItemInPage.$title).text().trim(), // 标题
describe: $(websiteItemInPage.$describe).text().trim(), // 描述
time : $(websiteItemInPage.$time).text().trim(), // 时间
};
return data; // 抓取的数据返回到函数外
}, websiteItem); // 把 websiteItem 传给页面中的 websiteItemInPage
console.log(`数据:\n ${JSON.stringify(pageData, undefined, 2)}`);
Object.assign(websiteItem, pageData);
// 5.关闭页面
// ----------------------------------
await page.close();
console.log(`--------------------`);
}
// 6.关闭浏览器
// ----------------------------------
await browser.close();
// 7. 渲染方法
// ----------------------------------
})();
注意代码中注释的1-6,与人工操作的流程图完全一致,保存数据 这一步,将在数据展示部分编写
运行
在index.js的目录 运行index.js就能看到爬虫结果
node index.js
4.数据展示
数据拿到以后,还未存储到本地。简单的把数据存两份,一份json格式,一份html方便预览。ps:🙄完全不需要数据库
- JSON格式,保存成result.txt
- HTML格式,保存成result.html
swig-templates怎么用
模板引擎就是渲染数据到静态的html文件,只要定义好模板和for循环,然后传数据即可。
swig-templates非常简单,只有for和if,在github上有示例代码,1分钟上手。
- 准备html模板
- 定义渲染方法
- 调用渲染方法
新建 template.html
在模板中,list是数组,swig会循环数组,并且把每条数据生成对应的html标签
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>渲染模板</title>
</head>
<body>
{% for item in list %}
<!-- 爬虫名字 -->
<h1>{{ item.name }}</h1>
<!-- 来源url -->
<h2>{{ item.url }}</h2>
<!-- 电影名字 -->
<h3>{{ item.title }}</h3>
<!-- 描述 -->
<p>{{ item.describe }}</p>
<div>
<!-- 上映时间 -->
<span>{{ item.time }}</span>
</div>
{% endfor %}
</body>
</html>
定义渲染方法
在index.js 的最后增加一个方法renderData并且调用
/**
* @description: 渲染html和txt
* @param {Array} data - 被渲染的数据
*/
function renderData(data) {
//读取模板
const templateData = swig.compileFile("./template.html");
//模板渲染后html数据
const outputData = templateData({
list: data,
});
// 输出路径
const path = require("path");
const htmlPath = path.join(__dirname + "result.html");
const txtPath = path.join(__dirname + "result.txt");
// 写入文件
const fs = require("fs");
fs.writeFileSync(htmlPath, outputData);
fs.writeFileSync(txtPath, data);
}
在 // 7. 渲染方法 后加入调用方法,必须在这个位置,否则渲染出来是空数据
...
// 7. 渲染方法
// ----------------------------------
renderData(websiteList);
})();
运行并渲染
运行node index.js,可以看到左边多了两个文件,表示渲染成功
打开看一下爬到的成果
在template.html中增加css样式,重新生成的result.html页面会更漂亮。✨
结语
用jQuery的优势是上手速度快,更推荐Puppeteer的api
// 输入文字
await page.type("#选择器", "我是谁?", { delay: 100 });
// 点击
await page.click("#选择器");
// 等待2秒
await page.waitForTimeout(2000);
// ...还有很多,从文档里都可以查到
有的网站(比如某信部)有无头浏览器的检测,在page.goto前加上下面这段可绕过
await page.evaluateOnNewDocument(() => {
const newProto = navigator.__proto__;
delete newProto.webdriver;
navigator.__proto__ = newProto;
window.navigator.chrome = {
runtime: {},
};
});
除了爬虫,Puppeteer同样可以执行网页的任何操作,比如:自动写个日报
我在做的事不要问,猜到了不要说。🤡