实例:使用puppeteer headless方式抓取JS网页

puppeteer

google chrome团队出品的puppeteer 是依赖nodejs和chromium的自动化测试库,它的最大优点就是可以处理网页中的动态内容,如JavaScript,能够更好的模拟用户。
有些网站的反爬虫手段是将部分内容隐藏于某些javascript/ajax请求中,致使直接获取a标签的方式不奏效。甚至有些网站会设置隐藏元素“陷阱”,对用户不可见,脚本触发则认为是机器。这种情况下,puppeteer的优势就凸显出来了。
它可实现如下功能:

  1. 生成页面的屏幕截图和PDF。
  2. 抓取SPA并生成预先呈现的内容(即“SSR”)。
  3. 自动表单提交,UI测试,键盘输入等。
  4. 创建一个最新的自动化测试环境。使用最新的JavaScript和浏览器功能,直接在最新版本的Chrome中运行测试。
  5. 捕获跟踪您网站的时间线,以帮助诊断性能问题。

开源地址:[https://github.com/GoogleChrome/puppeteer/][1]

安装

npm i puppeteer

注意先安装nodejs, 并在nodejs文件根目录下执行(npm文件同级)。
安装过程中会下载chromium,大约120M。

用两天(大约10小时)摸索,绕过了相当多的异步的坑,笔者对puppeteer和nodejs有了一定的掌握。
一张长图,抓取blog文章列表:

抓取blog文章

以csdn blog为例,文章内容需要点击“阅读全文”来获取,这就导致只能读取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; i<makLoop;i++){
        try{
            await page.evaluate(()=>window.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<hrefs.length){
        url = hrefs[countUrl];
        countUrl++;
    }else{
        browser.close();
        return;
    }
    console.log(‘processing url: ‘+url);
    try{
        const tab = await browser.newPage();
        await tab.setUserAgent(userAgent);
        await tab.setViewport({width:414, height:736});
        await tab.setRequestInterception(true);
        //filter to block images
        tab.on(‘request‘, request => {
        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,我使用的递归方式并不是最优解。
事实上,逐一处理并不高效,原本我写了一个异步的关闭browser方法:

let tryCloseBrowser = setInterval(function(){
        console.log("check if any process running...")
        if(countDown<=0){
          clearInterval(tryCloseBrowser);
          console.log("none process running, close.")
          browser.close();
        }
    },3000);

按照这个思路,代码的最初版本是同时打开多个tab页,效率很高,但容错率很低,大家可以试着自己写一下。

题外话

看过我的文章的人都知道,我写文章更强调处理问题的方式/方法,给大家一些思维上的建议。
对于nodejs和puppeteer我是完全陌生的(当然,我知道他们适合做什么,仅此而已)。如果大家还记得《10倍速程序员》里提到的按需记忆的理念,那么你就会理解我刻意的去系统的学习新技术。
我说说我接触puppeteer到完成我需要功能的所有思维逻辑:

  1. 了解puppeteer功能/特性,结合目的判断是否满足要求。
  2. 快速实现getStart中的所有demo
  3. 二次判断puppeteer的特性,从设计者角度出发,推测puppeteer的架构。
  4. 验证架构。
  5. 通读api,了解puppeteer细节。
  6. 搜索puppeteer前置学习内容(以及前置学习内容所依赖的前置学习内容)。整理学习内容,回到1。
  7. 设计/分析/调试/……

2018年5月9日02点13分

原文地址:http://blog.51cto.com/12240152/2114237

时间: 2024-10-09 11:23:26

实例:使用puppeteer headless方式抓取JS网页的相关文章

queryList 一次抓取多个网页内容的方法--目前只有用循环 替换页码或者给出url循环进行 queryList没有像python一样的yied迭代方法 queryList 实现多个实例抓取不同网页的内容,两个实例数据互不干扰

注意: 目前只有用循环 替换页码或者给出url循环进行   queryList没有像python一样的yied迭代方法  queryList 实现多个实例抓取不同网页的内容,两个实例数据互不干扰 新技能获取: Medoo(轻量级php数据库框架:https://medoo.lvtao.net/) 实现循环采集多个页面数据: 关键代码  for ($i = 1; $i < 21; $i++) { echo "正在爬取第{$i}页\n"; $url = "http://bl

网站爬取-案例三:今日头条抓取(ajax抓取JS数据)

今日头条这类的网站制作,从数据形式,CSS样式都是通过数据接口的样式来决定的,所以它的抓取方法和其他网页的抓取方法不太一样,对它的抓取需要抓取后台传来的JSON数据,先来看一下今日头条的源码结构:我们抓取文章的标题,详情页的图片链接试一下: 看到上面的源码了吧,抓取下来没有用,那么我看下它的后台数据:' 所有的数据都在后台的JSON展示中,所以我们需要通过接口对数据进行抓取 提取网页JSON数据 执行函数结果,如果你想大量抓取记得开启多进程并且存入数据库: 看下结果: 总结一下:网上好多抓取今日

使用scrapy-selenium, chrome-headless抓取动态网页

????在使用scrapy抓取网页时, 如果遇到使用js动态渲染的页面, 将无法提取到在浏览器中看到的内容. 针对这个问题scrapy官方给出的方案是scrapy-selenium, 这是一个把selenium集成到scrapy的开源项目, 它使用selenium抓取已经渲染好(js代码已经执行完成)的动态网页. ????事实上selenium自己也没有渲染动态网页的能力,它还是得依赖浏览器, 用浏览器作为动态网页的渲染引擎. 目前主流的浏览器都能以headless模式运行, 即没有图形界面只有

Java写的抓取任意网页中email地址的小程序

/* * 从网页中抓取邮箱地址 * 正则表达式:java.util.regex.Pattern * 1.定义好邮箱的正则表达式 * 2.对正则表达式预编译 * 3.对正则和网页中的邮箱格式进行匹配 * 4.找到匹配结果 * 5.通过网络程序,打通机器和互联网的一个网站的连接 */ import java.net.*; import java.util.regex.*; import java.io.*; public class EmailAddressFetch { public static

scrapy和selenium结合抓取动态网页

1.安装python (我用的是2.7版本的) 2.安装scrapy:   详情请参考 http://blog.csdn.net/wukaibo1986/article/details/8167590 (提示,能下载源码安装的就避免用pip install **) 安装过程中遇到python扩展问题”unable to find vcvarsall.bat“的解决办法: http://blog.csdn.net/ren911/article/details/6448696 3.安装seleniu

python Beautiful Soup 抓取解析网页

Beautiful Soup is a Python library designed for quick turnaround projects like screen-scraping.总之就是一个解析xml和html之类的库,用着还算顺手. 官网地址:http://www.crummy.com/software/BeautifulSoup/ 下面来介绍下使用python和Beautiful Soup 抓取一个网页上的PM2.5数据. PM2.5 数据的网站:http://www.pm25.

基于puppeteer模拟登录抓取页面

关于热图 在网站分析行业中,网站热图能够很好的反应用户在网站的操作行为,具体分析用户的喜好,对网站进行针对性的优化,一个热图的例子(来源于ptengine) 上图中能很清晰的看到用户关注点在那,我们不关注产品中热图的功能如何,本篇文章就热图的实现做一下简单的分析和总结. 热图主流的实现方式 一般实现热图显示需要经过如下阶段: 获取网站页面 获取经过处理后的用户数据 绘制热图 本篇主要聚焦于阶段1来详细的介绍一下主流的在热图中获取网站页面的实现方式 使用iframe直接嵌入用户网站 抓取用户页面保

使用selenium抓取JS动态生成的页面

在抓取网页数据时,传统jsoup方案只能对静态页面有效,而有些网页数据往往是js生成的,所以这时候需要其它的方案. 首先的思路是分析js程序,对js的请求进行再次抓取,这适合于特定的页面抓取,要做到对不同目标URL的通用性,比较麻烦. 第二种思路,也是比较成熟的做法是利用第三方的驱动渲染页面,然后下载.这里介绍一下第二种实现思路. Selenium是一个模拟浏览器的自动化测试工具,它提供一组API可以与真实的浏览器内核交互. Java环境下的maven配置如下: <dependency> &l

抓取js动态生成数据

最近在抓数据,一般的网页数据抓取相对容易一些,今天在抓电视猫的节目单,发现有些数据时抓取不到的,Java端得到的HTML文件里面没有某一段代码,查了很多资料,发现说是js动态生成的数据,无法直接抓取,有一种解决方法是利用找到ajax请求地址和参数,重新抓取,该方法存在一个问题,就是当参数被加密过时,该方法就不好用了,所以,这里用了一个办法,就是利用HTMLunit来抓取(可以利用jsuop来处理HTML文件),jar包下载地址:http://download.csdn.net/detail/jo