只会js怎么写爬虫?跟我用Puppeteer+jQuery实现爬虫

1,843 阅读5分钟

前言

某位不愿意透漏姓名的同事要定期访问某些网站,复制页面的某条信息到自己电脑上。

整个操作简单、重复性高。他找到我以后,其实,一开始我是拒绝的

image-20210726173451869.png

本来想用 Tampermonkey 写个脚本,打开网站的时候自动复制数据。但是!油猴没办法将数据存到文件里。

很明显了,爬虫最适合干这事。

JavaScript怎么写爬虫?

主角登场👉 Puppeteer

Puppeteer是什么

  1. Puppeteer 是无头浏览器(没有浏览器界面)
  2. Puppeteer 由Google Chrome出品(ps:谷歌的亲孙子😬)
  3. Puppeteer 能创建真实行为,比如输入
  4. Puppeteer 支持async await

简单说,Puppeteer 可以用程序控制浏览器操作,操作过程能看到也能隐藏。

推荐文档

  • 奇客谷 Puppeteer 👉 点击

  • Puppeteer中文网 👉 点击

1.准备工作

被爬的网站

scrape.center

ps:一个专门用来练习爬虫的网站,随便爬。👍

可以看到很多例站,后面的代码以ssr2为例,地址是 ssr2.scrape.center

image-20210726194929620.png

实现目标

我们要从电影的详情页拿到三个信息,然后组成自己的信息页面

  1. 电影标题
  2. 电影描述
  3. 上映时间

image-20210726201315323.png

工具

人工操作浏览器的流程图

image-20210726191043590.png

Puppeteer的工作流程和人工一模一样,不过是把点击的动作换成了代码。

看一下用到的包和工具

Node.js版本 v14.16.0

npm版本 7.20.0

ps:参考就好,不要求完全一致。

npm包用途
puppeteer核心库,操作浏览器
bufferutilpuppeteer依赖,必须安装
utf-8-validatepuppeteer依赖,必须安装
swig-templateshtml模板引擎,最后展示数据用
jQuery抓取数据(可不用,puppeteer自带一些方法)

2.分析

目标站 - 首页:ssr2.scrape.center

目标站的首页是不包括电影描述的,所以得从详情页面入手

image-20210726201315323.png

这是三个详情页的地址,可以发现,url的构成是 https://ssr2.scrape.center/detail/ + 数字

所以只要构造出url就可以拿到数据。

image-20210726202223170.png

同时,找出对应数据的CSS选择器(后面通过选择器查找数据)

内容CSS选择器
标题h2.m-b-sm
描述.drama p
时间.m-v-sm.info+.m-v-sm.info

image-20210727085344443.png

3.开发

新建一个项目

安装包

💡puppeteer用npm安装会报错,直接用cnpm就好了

puppeteer的包很大,因为它依赖Chromium浏览器,会在你的项目中下载一个浏览器

cnpm i puppeteer --save

image-20210726204142666.png

提示缺少两个包,装一下

cnpm i bufferutil utf-8-validate --save

image-20210726204358517.png

再安装一下模板引擎

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. 渲染方法
    // ----------------------------------

})();

image-20210727142817239.png

注意代码中注释的1-6,与人工操作的流程图完全一致,保存数据 这一步,将在数据展示部分编写

image-20210726191043590 - 副本.png

运行

在index.js的目录 运行index.js就能看到爬虫结果

node index.js

image-20210727110300214.png

4.数据展示

数据拿到以后,还未存储到本地。简单的把数据存两份,一份json格式,一份html方便预览。ps:🙄完全不需要数据库

  • JSON格式,保存成result.txt
  • HTML格式,保存成result.html

swig-templates怎么用

👉github地址

模板引擎就是渲染数据到静态的html文件,只要定义好模板和for循环,然后传数据即可。

swig-templates非常简单,只有for和if,在github上有示例代码,1分钟上手。

  1. 准备html模板
  2. 定义渲染方法
  3. 调用渲染方法

image-20210727144028132.png

新建 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,可以看到左边多了两个文件,表示渲染成功

image-20210727114344262.png

打开看一下爬到的成果

image-20210727114549164.png

template.html中增加css样式,重新生成的result.html页面会更漂亮。✨

image-20210727142313897.png

结语

用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同样可以执行网页的任何操作,比如:自动写个日报

我在做的事不要问,猜到了不要说。🤡