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]

安装

  1. npm i puppeteer

注意先安装nodejs, 并在nodejs文件根目录下执行(npm文件同级)。

安装过程中会下载chromium,大约120M。

用两天(大约10小时)摸索,绕过了相当多的异步的坑,笔者对puppeteer和nodejs有了一定的掌握。

一张长图,抓取blog文章列表:

抓取blog文章

以csdn blog为例,文章内容需要点击“阅读全文”来获取,这就导致只能读取dom的脚本失效。

  1. /**
  2. * load blog.csdn.net article to local files
  3. **/
  4. const puppeteer = require('puppeteer');
  5. //emulate iphone
  6. 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';
  7. const workPath = './contents';
  8. const fs = require("fs");
  9. if (!fs.existsSync(workPath)) {
  10. fs.mkdirSync(workPath)
  11. }
  12. //base url
  13. const rootUrl = 'https://blog.csdn.net/';
  14. //max wait milliseconds
  15. const maxWait = 100;
  16. //max loop scroll times
  17. const makLoop = 10;
  18. (async () => {
  19. let url;
  20. let countUrl=0;
  21. const browser = await puppeteer.launch({headless: false});//set headless: true will hide chromium UI
  22. const page = await browser.newPage();
  23. await page.setUserAgent(userAgent);
  24. await page.setViewport({width:414, height:736});
  25. await page.setRequestInterception(true);
  26. //filter to block images
  27. page.on('request', request => {
  28. if (request.resourceType() === 'image')
  29. request.abort();
  30. else
  31. request.continue();
  32. });
  33. await page.goto(rootUrl);
  34. for(let i= 0; i<makLoop;i++){
  35. try{
  36. await page.evaluate(()=>window.scrollTo(0, document.body.scrollHeight));
  37. await page.waitForNavigation({timeout:maxWait,waitUntil: ['networkidle0']});
  38. }catch(err){
  39. console.log('scroll to bottom and then wait '+maxWait+'ms.');
  40. }
  41. }
  42. await page.screenshot({path: workPath+'/screenshot.png',fullPage: true, quality :100, type :'jpeg'});
  43. //#feedlist_id li[data-type="blog"] a
  44. const sel = '#feedlist_id li[data-type="blog"] h2 a';
  45. const hrefs = await page.evaluate((sel) => {
  46. let elements = Array.from(document.querySelectorAll(sel));
  47. let links = elements.map(element => {
  48. return element.href
  49. })
  50. return links;
  51. }, sel);
  52. console.log('total links: '+hrefs.length);
  53. process();
  54. async function process(){
  55. if(countUrl<hrefs.length){
  56. url = hrefs[countUrl];
  57. countUrl++;
  58. }else{
  59. browser.close();
  60. return;
  61. }
  62. console.log('processing url: '+url);
  63. try{
  64. const tab = await browser.newPage();
  65. await tab.setUserAgent(userAgent);
  66. await tab.setViewport({width:414, height:736});
  67. await tab.setRequestInterception(true);
  68. //filter to block images
  69. tab.on('request', request => {
  70. if (request.resourceType() === 'image')
  71. request.abort();
  72. else
  73. request.continue();
  74. });
  75. await tab.goto(url);
  76. //execute tap request
  77. try{
  78. await tab.tap('.read_more_btn');
  79. }catch(err){
  80. console.log('there\'s none read more button. No need to TAP');
  81. }
  82. let title = await tab.evaluate(() => document.querySelector('#article .article_title').innerText);
  83. let contents = await tab.evaluate(() => document.querySelector('#article .article_content').innerText);
  84. contents = 'TITLE: '+title+'\nURL: '+url+'\nCONTENTS: \n'+contents;
  85. const fs = require("fs");
  86. fs.writeFileSync(workPath+'/'+tab.url().substring(tab.url().lastIndexOf('/'),tab.url().length)+'.txt',contents);
  87. console.log(title + " has been downloaded to local.");
  88. await tab.close();
  89. }catch(err){
  90. console.log('url: '+tab.url()+' \n'+err.toString());
  91. }finally{
  92. process();
  93. }
  94. }
  95. })();

执行过程

录屏可以在我公众号查看,下边是截图:

执行结果

文章内容列表:

文章内容:

结束语

以前就想过既然nodejs是使用JavaScript脚本语言,那么它肯定能处理网页的JavaScript内容,但并没有发现合适的/高效率的库。直到发现puppeteer,才下定决心试水。

话说回来,nodejs的异步真的是很头疼的一件事,这上百行代码我竟然折腾了10个小时。

大家可拓展下代码中process()方法,使用async.eachSeries,我使用的递归方式并不是最优解。

事实上,逐一处理并不高效,原本我写了一个异步的关闭browser方法:

  1. let tryCloseBrowser = setInterval(function(){
  2. console.log("check if any process running...")
  3. if(countDown<=0){
  4. clearInterval(tryCloseBrowser);
  5. console.log("none process running, close.")
  6. browser.close();
  7. }
  8. },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分

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

  1. 抓取Js动态生成数据且以滚动页面方式分页的网页

    代码也可以从我的开源项目HtmlExtractor中获取. 当我们在进行数据抓取的时候,如果目标网站是以Js的方式动态生成数据且以滚动页面的方式进行分页,那么我们该如何抓取呢? 如类似今日头条这样的网 ...

  2. python3.4学习笔记(十三) 网络爬虫实例代码,使用pyspider抓取多牛投资吧里面的文章信息,抓取政府网新闻内容

    python3.4学习笔记(十三) 网络爬虫实例代码,使用pyspider抓取多牛投资吧里面的文章信息PySpider:一个国人编写的强大的网络爬虫系统并带有强大的WebUI,采用Python语言编写 ...

  3. 如何用python抓取js生成的数据 - SegmentFault

    如何用python抓取js生成的数据 - SegmentFault 如何用python抓取js生成的数据 1赞 踩 收藏 想写一个爬虫,但是需要抓去的的数据是js生成的,在源代码里看不到,要怎么才能抓 ...

  4. 【转载】ASP.NET以Post方式抓取远程网页内容类似爬虫功能

    使用HttpWebRequest等Http相关类,可以在应用程序中或者网站中模拟浏览器发送Post请求,在请求带入相应的Post参数值,而后请求回远程网页信息.实现这一功能也很简单,主要是依靠Http ...

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

    今日头条这类的网站制作,从数据形式,CSS样式都是通过数据接口的样式来决定的,所以它的抓取方法和其他网页的抓取方法不太一样,对它的抓取需要抓取后台传来的JSON数据,先来看一下今日头条的源码结构:我们 ...

  6. selenium抓取动态网页数据

    1.selenium抓取动态网页数据基础介绍 1.1 什么是AJAX AJAX(Asynchronouse JavaScript And XML:异步JavaScript和XML)通过在后台与服务器进 ...

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

        在使用scrapy抓取网页时, 如果遇到使用js动态渲染的页面, 将无法提取到在浏览器中看到的内容. 针对这个问题scrapy官方给出的方案是scrapy-selenium, 这是一个把sel ...

  8. python网络爬虫抓取动态网页并将数据存入数据库MySQL

    简述以下的代码是使用python实现的网络爬虫,抓取动态网页 http://hb.qq.com/baoliao/ .此网页中的最新.精华下面的内容是由JavaScript动态生成的.审查网页元素与网页 ...

  9. 利用wget 抓取 网站网页 包括css背景图片

    利用wget 抓取 网站网页 包括css背景图片 wget是一款非常优秀的http/ftp下载工具,它功能强大,而且几乎所有的unix系统上都有.不过用它来dump比较现代的网站会有一个问题:不支持c ...

随机推荐

  1. B - 取(2堆)石子游戏

    有两堆石子,数量任意,可以不同.游戏开始由两个人轮流取石子.游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子:二是可以在两堆中同时取走相同数量的石子.最后把石子全部取完者为胜者. ...

  2. I - A/B

    要求(A/B)%9973,但由于A很大,我们只给出n(n=A%9973)(我们给定的A必能被B整除,且gcd(B,9973) = 1). Input 数据的第一行是一个T,表示有T组数据. 每组数据有 ...

  3. springMVC 复选框带有选择项记忆功能的处理

    前言:由于jsp管理页面经常会遇到复选框提交到JAVA后台,后台处理逻辑完成后又返回到jsp页面,此时需要记住jsp页面提交时复选框的选择状态,故编写此功能! 一.复选框的初始化 1.1.jsp页面 ...

  4. 证书文件(pfx)读取时报 “指定的网络密码不正确”

    实际情况: 1.本地测试正确,发布到windows server 2003 iis6 可以正常运行 发布到 windows server 2008 上 II7就报 “指定的网络密码不正确” 日志报错为 ...

  5. got positional argument after named arguments.原因

  6. Codeforces 1132D - Stressful Training - [二分+贪心+优先队列]

    题目链接:https://codeforces.com/contest/1132/problem/D 题意: 有 $n$ 个学生,他们的电脑有初始电量 $a[1 \sim n]$,他们的电脑每分钟会耗 ...

  7. POJ 1655 - Balancing Act - [DFS][树的重心]

    链接:http://poj.org/problem?id=1655 Time Limit: 1000MS Memory Limit: 65536K Description Consider a tre ...

  8. Exception 05 : Could not instantiate id generator

    异常名称: Could not instantiate id generator 异常截图: 异常原因:Sequence不支持mysql数据库 Sequence支持的是有序列的数据库,此时可以将ora ...

  9. 多文件上传(.net)

    找了很长时间,终于找到了: 前台: <html xmlns="http://www.w3.org/1999/xhtml"> <head id="Head ...

  10. oplog

    参考资料:https://www.cnblogs.com/ruizhang3/p/6539730.html http://www.jb51.net/article/113432.htm :insert ...