1.Puppetter介绍
    Puppeteer是一个由google提供的node.js库,它提供高级API,通过DevTools 协议控制 Chromium 或 Chrome,可以模拟大多数的浏览器操作,puppeteer有以下作用:

    • 生成网页截图和PDF;

    • 自动化表单提交,UI测试,键盘输入等;

    • 创建一个最新的自动化测试环境。使用最新的 JavaScript 和浏览器功能,可以直接在最新版本的 Chrome 中运行测试;

    • 捕获站点的时间线跟踪,以帮助诊断性能问题;

    • 爬取网页内容;

    2.安装

    1. npm install --save-dev puppeteer

    由于安装包需要执行node install.js来安装Chromium内核,由于国内封网,所以可能出现如下情况:
    ERROR: Failed to download Chromium r599821! Set “PUPPETEER_SKIP_CHROMIUM_DOWNLOAD” env variable to skip download.
    出现这种情况表示下载失败,可以翻墙或者使用第二种方式,通过设置环境变量或者npm config中的PUPPETEER_SKIP_CHROMIUM_DOWNLOAD跳过下载(npm config set puppeteer_skip_chromium_download = 1),手动下载chromium,网址是:链接
    然后再使用的时候手动引入

    1. const browser =await puppeteer.launch({
    2. headless:false,
    3. executablePath:'./Chromium.app/Contents/MacOS/Chromium',
    4. });

    3.初始化项目

    1. const puppeteer=require('puppeteer');
    2. //使用puppeteer打开浏览器
    3. (
    4. async ()=>{
    5. const browser =await puppeteer.launch({
    6. headless:true,//表示是否以headless模式打开,false的时候会打开浏览器进行操作
    7. executablePath:'./Chromium.app/Contents/MacOS/Chromium',
    8. });
    9. }
    10. )

    4.新建页面,开始模拟操作,抓取内容

    1. //新建一个页面
    2. const page = await browser.newPage();
    3. //跳转到对应的页面
    4. await page.goto("https://v.qq.com/");
    5. //此处停留1s,等待页面完全加载完
    6. //官网有page.waitForNavigation(options)方法,但是打开新页面时次发发放无效
    7. await page.waitFor(1000)
    8. //往选中元素中注入文本内容
    9. await page.type('#keywords',"斗破苍穹");
    10. /模拟说表操作
    11. await page.click('.search_btn');
    12. //等待跳转到新的页面
    13. await page.waitFor(2000);
    14. await page.screenshot({ //生网页截图
    15. path: 'example.png'
    16. })

    如上操作会有一个奇怪的现象,先打开了腾讯视频首页,然后在搜索框中输入’斗破苍穹’,单机页面后会跳转到另一个页面,然后我们在进行截取网页操作,但是打开example.png一看,截到的图却是腾讯视频的首页,并不是搜索的结果,这时候我们修改一下headless

    1. const browser =await puppeteer.launch({
    2. headless:false,
    3. executablePath:'./Chromium.app/Contents/MacOS/Chromium',
    4. });

    这时候发现浏览器先打开了腾讯视频首页,输入内容后点击搜索后跳转到了新搜索页,但是我们开始截图时又调回了首页,查询API发现这时浏览已经存在多个page,我们需要拿到自己需要的page

    1. let pages= await browser.pages();
    2. //由于浏览器会打开一个空白的页签,所以索引值为2
    3. let content = await pages[2].content();//拿到页面内容

    5.分析内容,这里使用cheerio库进行分析,相对jquery,cheerio对内容要求更松,适合进行页面分析

    1. const cheerio = require('cheerio');
    2. let content = await pages[2].content();
    3. let $ = cheerio.load(content);
    4. //因为有多部搜索结果,所以需要先获取每个搜索结果的ID,以便后面请求结果
    5. let result = $('.result_item_v');
    6. let movie = { id: $(element).data('id') };
    7. //获取每个结果的海报图片
    8. $(element).children('._infos').each((i, el) => {
    9. movie.poster = $(el).find('img').attr('src');
    10. //获取评分
    11. $(el).find('.result_title').each((ii, ele) => {
    12. $(ele).find('a').each((iii, v) => {
    13. movie.name = $(v).text().trim();
    14. })
    15. });
    16. movie.score = $(el).find('.result_score').text().trim();
    17. //爬取剧情结果
    18. try {
    19. let url = `https://s.video.qq.com/get_playsource?id=${movie.id}&plat=2&type=4&range=1-1000`;
    20. https.get(url, (res) => {
    21. res.on('data', (chunk) => {
    22. movieHtml += chunk;
    23. });
    24. res.on('end', () => {
    25. movieHtml+='</html>'
    26. let $m = cheerio.load(movieHtml);
    27. resolve3($m);
    28. })
    29. })
    30. } catch (error) {
    31. reject3(error);
    32. }
    33. });
    34. //提取结果
    35. promise3.then(($m) => {
    36. let videoList = [];
    37. console.log($m('videoPlayList').length);
    38. $m('videoPlayList').each((index, ele) => {
    39. let video = {
    40. id: $m(ele).find('id').text().trim(),
    41. title: $m(ele).find('title').text().trim(),
    42. number: $m(ele).find('episode_number').text().trim(),
    43. pic: $m(ele).find('pic').text().trim(),
    44. playUrl: $m(ele).find('playUrl').text().trim()
    45. }
    46. videoList.push(video);
    47. })
    48. movie.videoList = videoList;
    49. movies.push(movie);
    50. resole2();
    51. })

    6.编写http服务器,把结果反馈给前端

    1. const search=async (req,res)=>{
    2. let pathName = url.parse(req.url).pathname;
    3. console.log(pathName);
    4. //允许跨越
    5. res.setHeader('Access-Control-Allow-Origin', '*');
    6. res.setHeader("Access-Control-Allow-Methods", "*");
    7. //必须使用,因为复杂的请求浏览器会先发一次OPTIONS的试探性请求
    8. res.setHeader("Access-Control-Allow-Headers", 'Content-Type');
    9. //拦截试探性请求
    10. if (req.method =='OPTIONS'){
    11. res.end('ok');
    12. return;
    13. }
    14. //简单的路由
    15. if (pathName !='/search'){
    16. res.writeHead(200, { "Content-Type": "text/plain;charset=utf-8" });
    17. res.write('请求错误');
    18. res.end();
    19. return ;
    20. }
    21. res.writeHead(200, { "Content-Type": "application/json;charset=utf-8" });
    22. res.write(JSON.stringify(movies));
    23. res.end();
    24. }
    25. http.createServer(search).listen(3000);

    7.编写前端页面

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <meta charset="utf-8" />
    5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
    6. <title>Page Title</title>
    7. <meta name="viewport" content="width=device-width, initial-scale=1">
    8. <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    9. </head>
    10. <style>
    11. .videoList{
    12. border: 1px solid #eee;
    13. }
    14. .video-box{position: absolute;display: none;width: 800px;height: 400px;left: 50%;top: 30%;margin-left: -400px;}
    15. </style>
    16. <body style='min-height:100%;min-width:100%'>
    17. <div id='video'></div>
    18. <div class="video-box"></div>
    19. <script>
    20. let promise = new Promise((reslove,reject)=>{
    21. $.ajax({
    22. type: 'get',
    23. url: 'http://localhost:3000/search',
    24. dataType: "json",
    25. charset: "utf-8",
    26. contentType: "application/json",
    27. success: data => {
    28. reslove(data);
    29. },
    30. error: err => {
    31. reject(err);
    32. }
    33. });
    34. });
    35. let video=[];
    36. promise.then((data)=>{
    37. if(Array.isArray(data)){
    38. data.forEach((v,index)=>{
    39. if(v.videoList.length>0){
    40. video.push(v);
    41. }
    42. });
    43. }
    44. console.log(video);
    45. video.forEach((vi)=>{
    46. $('#video').append(`<div class="videoList">
    47. <img style="display:inline-block" src=${vi.poster}></img>
    48. <ul id=${vi.id} style="height:370px;display:inline-block;vertical-align:top;overflow:auto">共${vi.videoList.length}集</ul>
    49. </div>`);
    50. vi.videoList.forEach((v, index) => {
    51. $(`#${vi.id}`).append(`<li><p style="text-decoration:underline;cursor:pointer" onclick=start(\'${v.playUrl}\')>${v.number}</p></li>`)
    52. })
    53. })
    54. });
    55. function start(url){
    56. var videoSrc='http://api.greatchina56.com/?url='+url;
    57. $('#video').css('display', 'none');
    58. $('.video-box').css('display','block');
    59. var iframe=document.createElement('iframe');
    60. iframe.setAttribute('src', videoSrc);
    61. iframe.setAttribute('style', "width:800px;height:400px;");
    62. $('.video-box').append(iframe);
    63. }
    64. </script>
    65. </body>
    66. </html>

    8效果图
    Node.js Puppetter开发免费视频网站 - 图1

    可以看VIP视频和跳过广告哦
    Node.js Puppetter开发免费视频网站 - 图2