当前位置: 首页 > news >正文

node爬虫-使用puppeteer

headless

最近看了些关于谷歌 headless的介绍,简单的说就是一个无界面的浏览器的,可用于前端自动化测试和爬虫抓取 因为headless是模拟用户行为操作的,所以爬虫也是完全模拟用户的行为,且截图也非常方便。

puppeteer

是一个基于headless的封装,提供了很多非常方便实用的api,截个图如下

详细文档可以查看 官网

练练手

文档也看啦,是该练练手的时候啦,恰好公司打车报销,需要到hr系统中,将每晚下班的打卡截图出来,往常都是手动截图,效率非常低,且都是重复工作,是时候解放生产力啦~ 先说下大体的流程

  1. 登陆
  2. 跳转到考勤记录界面
  3. 输入查询日期和结束日期,因为每一张截图只要当天的记录,所以只能一天天的查
  4. 点击查询按钮
  5. 获取当天最晚打卡记录的时间,判断时间是否在报销时段内,若是,则截图保存

代码如下,因为是内部系统,关键参数已打码

const puppeteer = require('puppeteer');
var moment = require('moment');

// 参数配置
const config = {
    name: '***',
    password: '***',
    targetTime: '21:00:00',
    canbuTime: '19:30:00',
    startTime: '2017-12-01',
    endTime: '2018-04-01',
    searchCanbu: true,
    searchDache: true
}

let resultData = {
    canbuCounts: 0
}

async function run(params) {
    const browser = await puppeteer.launch({ headless: true });  //启动浏览器,headless为false,可以开启模拟器,默认为true
    const page = await browser.newPage();
    await page.goto('***'); //跳转目标地址
    const loginDom = {
        name: '#username',
        password: '#password',
        loginBtn: '#btnSubmit'
    }

    const searchDom = {
        createDateStart: '#createDateStart',
        createDateEnd: '#createDateEnd',
        searchDom: '#btnSubmit'
    }

    await page.type(loginDom.name, config.name); // 将文本写入输入框

    await page.type(loginDom.password, config.password);

    await page.click(loginDom.loginBtn); //点击按钮

    await page.waitForNavigation(); // 等待页面跳转返回

    await page.goto('***');

    const days = moment.duration(new Date(config.endTime).getTime() - new Date(config.startTime).getTime()).asDays();
    let searchTime = config.startTime;

    for (let i = 0; i < days; i++) {
        // 可以看作在浏览器中执行的片段
        await page.evaluate(() => {
            const startDom = document.querySelector('#createDateStart');
            const endDom = document.querySelector('#createDateEnd');
            startDom.removeAttribute("readOnly");
            endDom.removeAttribute("readOnly");
            startDom.value = '';
            endDom.value = '';
        })
        await page.type(searchDom.createDateStart, searchTime);
        await page.type(searchDom.createDateEnd, searchTime);
        await page.click(searchDom.searchDom);
        await page.waitFor(800);

        const afterTime = await page.evaluate(() => {
            try {
                return document.querySelector('#contentTable tbody tr:nth-child(1) td:last-child').innerHTML;
            } catch (error) {
                return null;
            }
        })
        if (config.searchDache && checkApplyTime(afterTime)) {
            //截屏
            await page.screenshot({ path: `screenshot/clock-${searchTime}.png`, clip: { x: 0, y: 0, width: 800, height: 217 } });
            console.log(`${searchTime}可以报销的士票哦~`);
        }
        if (config.searchCanbu && checkCanbuTime(afterTime)) {
            console.log(`${searchTime}可以报销餐补啦~`);
            resultData.canbuCounts++;
        }

        searchTime = moment(searchTime).add(1, 'day').format('YYYY-MM-DD');

    }

    await browser.close();
};
function checkApplyTime(time) {
    if (!time) {
        return false;
    }
    return new Date(time) >= new Date(`${moment(time).format('YYYY-MM-DD')} ${config.targetTime}`)
};

function checkCanbuTime(time) {
    if (!time) {
        return false;
    }
    return new Date(time) >= new Date(`${moment(time).format('YYYY-MM-DD')} ${config.canbuTime}`)
}

run()
复制代码

其中会涉及到一些dom节点的获取与修改,比如输入时间的input,时候通过插件来选择时间的,且input是readonly的状态,所以就需要些小操作,多尝试几次还是可以解决的

###待提升 运行效率上,还是比较慢,有空会在优化下~

相关文章:

  • 使用linux下的crontab定时任务跑定时脚本
  • mycat的wrapper.log日志中发现主从切换报错
  • react组件的生命周期
  • oracle中两个时间类型的数据相减默认得到的是天数。
  • 阿里云禁止25端口,使用465端口发送运维邮件
  • CentOS下设置Tomcat开机自动启动操作步骤
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • 柔弱的APP如何自我保护,浅谈APP防御手段,使用360加固助手加固/签名/多渠道打包/应用市场发布...
  • vue-学习系列之vue双向绑定原理
  • 答 ACM 调查问卷,限时领取阿里云代金券
  • phpmyadmin配置
  • 41、【华为HCIE-Storage】--Oceanstor9000 组网规划
  • spring对缓存的支持
  • Service Worker和HTTP缓存
  • java安全性的一种简单思路
  • [NodeJS] 关于Buffer
  • __proto__ 和 prototype的关系
  • 230. Kth Smallest Element in a BST
  • golang中接口赋值与方法集
  • python docx文档转html页面
  • RedisSerializer之JdkSerializationRedisSerializer分析
  • Wamp集成环境 添加PHP的新版本
  • 不用申请服务号就可以开发微信支付/支付宝/QQ钱包支付!附:直接可用的代码+demo...
  • 初识 webpack
  • 搭建gitbook 和 访问权限认证
  • 关于字符编码你应该知道的事情
  • 实战:基于Spring Boot快速开发RESTful风格API接口
  • gunicorn工作原理
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • 移动端高清、多屏适配方案
  • ​configparser --- 配置文件解析器​
  • #{} 和 ${}区别
  • (C)一些题4
  • (C语言)输入自定义个数的整数,打印出最大值和最小值
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (TipsTricks)用客户端模板精简JavaScript代码
  • (八)Docker网络跨主机通讯vxlan和vlan
  • (附源码)ssm捐赠救助系统 毕业设计 060945
  • (四)Tiki-taka算法(TTA)求解无人机三维路径规划研究(MATLAB)
  • (推荐)叮当——中文语音对话机器人
  • (原)本想说脏话,奈何已放下
  • (转)关于如何学好游戏3D引擎编程的一些经验
  • .md即markdown文件的基本常用编写语法
  • .Net 4.0并行库实用性演练
  • .net CHARTING图表控件下载地址
  • .net操作Excel出错解决
  • .net和jar包windows服务部署
  • /etc/shadow字段详解
  • [16/N]论得趣
  • [20181219]script使用小技巧.txt
  • [Android]Android P(9) WIFI学习笔记 - 扫描 (1)
  • [android]-如何在向服务器发送request时附加已保存的cookie数据
  • [BZOJ1060][ZJOI2007]时态同步 树形dp
  • [c++] C++多态(虚函数和虚继承)
  • [Excel VBA]单元格区域引用方式的小结