├── .gitignore ├── Dockerfile ├── README.md ├── docker-compose.yml ├── imgs ├── 01fly-init.png └── 02fly-deploy.png ├── index.js ├── package-lock.json ├── package.json └── yt-views.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | #fly file 107 | fly.toml 108 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM zenika/alpine-chrome:83-with-node-12 2 | 3 | USER root 4 | ENV NODE_ENV=production 5 | WORKDIR /src 6 | 7 | COPY package*.json ./ 8 | RUN npm install 9 | 10 | COPY . . 11 | EXPOSE 8080 12 | CMD ["node" , "index.js"] 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Puppeteer JS Renderer on Fly 2 | 3 | 4 | JavaScript is the bane of a web scraper's life. Scraping is all about extracting data from a web page and JavaScript is there adding content, hiding blocks, and moving the DOM around. Reading the HTML from the server is just not enough. What you ideally want is a way to run all that JavaScript on the page so you can see what's left after that. Then you can get down to some serious scraping. 5 | 6 | There are tools to do this out there but most have their own complications or restrictions that make them difficult to deploy. Puppeteer has none of those problems and with [Fly](https://fly.io), you can deploy puppeteer based applications close to your users. At its core, this project is a puppeteer-based service. [Puppeteer](https://pptr.dev/) is a package that renders pages using a headless Chrome instance, executing the JavaScript within the page. 7 | 8 | ## Quick Start 9 | 10 | A typical YouTube page will add the views count to itself only when the JavaScript on that page executes. These are the types of use cases where Puppeteer comes in very handy. Puppeteer can execute the JavaScript on the YouTube page and, from the rendered page, extract the view count and the title of the video. 11 | 12 | Lets get started 13 | 14 | ### Install example project 15 | 16 | To install this project locally, execute the following commands: 17 | 18 | ``` 19 | git clone https://github.com/fly-examples/puppeteer-js-renderer.git 20 | cd puppeteer-js-renderer 21 | npm install 22 | ``` 23 | 24 | ### Your first scraper 25 | 26 | To quickly try the project out after installing it, run the command below: 27 | 28 | ``` 29 | node yt-views.js 30 | ``` 31 | 32 | This will show something like below: 33 | 34 | ``` 35 | Pulling views from YouTube, please wait... 36 | Luis Fonsi - Despacito ft. Daddy Yankee has 6,881,463,846 views 37 | ``` 38 | 39 | If you want to try any other video just pass the YouTube video URL as a parameter to the script like below: 40 | 41 | ``` 42 | node yt-views https://www.youtube.com/watch?v=XqZsoesa55w 43 | ``` 44 | 45 | It should give you an output like this: 46 | 47 | ``` 48 | Pulling views from YouTube, please wait... 49 | Baby Shark Dance | Sing and Dance! | @Baby Shark Official | PINKFONG Songs for Children has 6,077,338,169 views 50 | ``` 51 | 52 | ### What just happened here? 53 | 54 | To show the YouTube video views count, a small scraper written with [Axrio](https://www.npmjs.com/package/@geshan/axrio) npm package was executed. Axrio combines the popular [Axios](https://www.npmjs.com/package/axios) library and [Cheerio](https://www.npmjs.com/package/cheerio) to create a mini scraper. Axios is used to make requests and Cheerio acts like DOM navigator parsing the markup and giving us an API for traversing/manipulating the resulting data structure. You can kickstart a small scraper with Axrio too. 55 | 56 | The [yt-views.js](./yt-views) is a basic scraper which performs a GET request for the given YouTube URL with Puppeteer. This will return the final DOM after the page's JavaScript executes. Then it parses the title and views count out of the rendered page's markup and prints it on the console. 57 | 58 | It uses the puppeteer-js-renderer service that is already deployed and running on Fly.io. Have a look at he "[how to use it as a service](#how-to-use-it-as-a-service)" section for more information. To start your own instance on Fly.io jump to the "[how to deploy on fly.io](#how-to-deploy-it-on-flyio)" section. 59 | 60 | ## Run locally 61 | 62 | If you have node installed on your machine, you already cloned this repository and ran `npm install`. Now run `npm start` to get this service running locally. 63 | 64 | The next step is to navigate to `http://localhost:8080/api/render?url=https://www.youtube.com/watch?v=kJQP7kiw5Fk` on your browser to view the JavaScript rendered output. 65 | 66 | ### Run with Docker 67 | 68 | If you want to run with Docker, execute the following after you clone the repository: 69 | 70 | ``` 71 | cd puppeteer-js-renderer 72 | docker-compose up 73 | ``` 74 | 75 | Then go to `http://localhost:8080/api/render?url=https://www.youtube.com/watch?v=kJQP7kiw5Fk` in your browser to see the output. 76 | 77 | ## How to use it as a service 78 | 79 | If you want to use puppeteer-js-renderer for scraping, you can use the following URL on Fly.io: 80 | 81 | ``` 82 | https://js-renderer-fly.fly.dev/api/render?url=https://www.youtube.com/watch?v=kJQP7kiw5Fk 83 | ``` 84 | 85 | The YouTube URL is an example above, you can use any URL that needs javascript to be executed to be functional. 86 | 87 | ### Styles broken 88 | 89 | Styles and images will look broken but the HTML tags will be there. Happy Web Scraping! 90 | 91 | ## How to deploy it on fly.io 92 | 93 | [Fly.io](https://fly.io) is a platform for applications that need to run globally. It runs your code close to users and scales compute in cities where your app is busiest. Write your code, package it into a Docker image, deploy it to Fly's platform, and let that do all the work to keep your app snappy. 94 | 95 | Fly.io has great [documentation](https://fly.io/docs/) to get started. You can find a quick speed run showing how to get your app running closer to your users with this [guide](https://fly.io/docs/speedrun/). 96 | 97 | Please follow these steps to deploy your own puppeteer-js-renderer service on Fly.io: 98 | 99 | ### Prerequisites 100 | 101 | 1. [Install](https://fly.io/docs/getting-started/installing-flyctl/) the flyctl CLI command. 102 | 1. Register on fly with `flyctl auth signup`, if you already have a fly account log in with `flyctl auth login`. 103 | 104 | ### Steps 105 | 106 | 1. Clone this repo with `git clone git@github.com:fly-examples/puppeteer-js-renderer.git` if you are logged in with SSH support enabled. Otherwise try `git clone https://github.com/fly-examples/puppeteer-js-renderer.git`. 107 | 1. Then run `cd puppeteer-js-renderer`. 108 | 1. After that execute `flyctl init --dockerfile` and when asked for an app name, hit return to have one generated (unless there's a name you really want). I ran it with js-renderer-fly as the app name for this example. 109 | 1. Subsequently, you can select an organization. Usually this will be your first name-last name on the prompt. 110 | 1. It should create a fly.toml file in the project root (I have not committed it, it is in .gitignore). 111 | 1. Now run `flyctl deploy` to deploy the app. It will build the docker image, push it to the fly docker image registry and deploy it. In the process of deploying it, it will display information about the number of instances and their health. 112 | 1. You can then run `flyctl info`. This will give the details of the app including hostname. 113 | 1. You can view your app in the browser with `flyctl open`. For me, it opened `https://js-renderer-fly.fly.dev` and displays `{message: "alive"}`. 114 | 1. Add `/api/render?url=https://www.youtube.com/watch?v=kJQP7kiw5Fk` on the address bar of your browser after your puppeteer-js-renderer URL. Mine looked like `https://js-renderer-fly.fly.dev/api/render?url=https://www.youtube.com/watch?v=kJQP7kiw5Fk`. This will render the final DOM of that YouTube page after the JavaScript is executed. 115 | 1. Enjoy! 116 | 117 | The YouTube video URL is an example and it can, of course, be replaced with other web pages which need JavaScript to work. 118 | 119 | ## More fly commands 120 | 121 | You can suspend your service with `flyctl suspend`; this will pause your service until you resume it. If you try `flyctl status` after suspending it will not show any instances running. Suspending the service means all resources allocated to run the service will be deallocated, resulting in the application running nowhere. To get the instances running again execute `flyctl resume`. 122 | 123 | ### Fly default resources 124 | 125 | I wanted to see what resources were allocated to the App on Fly by default. The scale commands allowed me to find it out pretty easily like below: 126 | 127 | 1. `flyctl scale show` - showed me VM Size: micro-2x 128 | 1. `flyctl scale vm` - showed me micro-2x is a 0.25 CPU cores with 512 MB of memory. 129 | 130 | If you want to increase CPU/memory or run more instances in a particular region please refer to the official Fly docs on [scaling](https://fly.io/docs/scaling/). 131 | 132 | ### Flying the app on 3 continents 133 | 134 | Our service is running in one data center. For me, it's iad (Ashburn, Virginia) but yours will likely be different based on where you are working from. We can add instances around the world to speed up responses, let's get rolling: 135 | 136 | 1. To see the regions available run `flyctl platform regions`, I could see regions all over the world from Oregon to Sydney. 137 | 1. Let's add an instance to Australia in Sydney, to do this run `flyctl regions add syd`, yes it is that easy. 138 | 1. Now check `flyctl status` and you will see an instance running in Sydney 139 | 1. Let’s add one more in Europe in Amsterdam with `flyctl regions add ams`. So now we are mostly covered with the app running in 3 continents. 140 | 1. Of course, you can run `flyctl status` again to see your app shining on 3 continents. 141 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | services: 3 | js-renderer: 4 | build: . 5 | volumes: 6 | - .:/src 7 | ports: 8 | - '8080:8080' 9 | command: npm start 10 | environment: 11 | NODE_ENV: dev 12 | PORT: 8080 13 | -------------------------------------------------------------------------------- /imgs/01fly-init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fly-apps/puppeteer-js-renderer/a0a87b06114513882b3b460caeb1a257a20b4e42/imgs/01fly-init.png -------------------------------------------------------------------------------- /imgs/02fly-deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fly-apps/puppeteer-js-renderer/a0a87b06114513882b3b460caeb1a257a20b4e42/imgs/02fly-deploy.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const puppeteer = require('puppeteer-core'); 3 | 4 | const app = express(); 5 | 6 | app.get('/', (req, res) => { 7 | res.json({ message: 'alive' }); 8 | }); 9 | 10 | app.get('/api/render', async (req, res) => { 11 | const url = req.query.url; 12 | if (!url) { 13 | return res.send('please provide url'); 14 | } 15 | try { 16 | const browser = await puppeteer.launch( 17 | { 18 | executablePath: process.env.CHROME_BIN, 19 | args: [ 20 | // Required for Docker version of Puppeteer 21 | '--no-sandbox', 22 | '--disable-setuid-sandbox', 23 | // This will write shared memory files into /tmp instead of /dev/shm, 24 | // because Docker’s default for /dev/shm is 64MB 25 | '--disable-dev-shm-usage' 26 | ], 27 | }); 28 | 29 | const page = await browser.newPage(); 30 | await page.goto(url, {waitUntil: 'networkidle0'}); 31 | const pageContent = await page.content(); 32 | console.log(`Response first 200 chars from ${url} : ${pageContent.substring(0, 200)}`); 33 | await browser.close(); 34 | 35 | res.send(pageContent); 36 | } catch (err) { 37 | console.log(`Error while fetching ${url} `, err); 38 | res.send(`Error fetching ${url}`); 39 | } 40 | }); 41 | 42 | const PORT = process.env.PORT || 8080; 43 | app.listen(PORT, () => { 44 | console.log(`Listening on port ${PORT}`); 45 | }); 46 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-renderer", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@geshan/axrio": { 8 | "version": "1.0.1", 9 | "resolved": "https://registry.npmjs.org/@geshan/axrio/-/axrio-1.0.1.tgz", 10 | "integrity": "sha512-DShXU5m8EOL9+snG34ApXOI1mOMFL+1ZCsX1GeLmHFTa1uNtK+hIXp6dvimJJUKfT1AMuT6ZHVegzBd/OEy8GQ==", 11 | "requires": { 12 | "axios": "^0.19.0", 13 | "cheerio": "^1.0.0-rc.3" 14 | } 15 | }, 16 | "@types/node": { 17 | "version": "14.0.24", 18 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.24.tgz", 19 | "integrity": "sha512-btt/oNOiDWcSuI721MdL8VQGnjsKjlTMdrKyTcLCKeQp/n4AAMFJ961wMbp+09y8WuGPClDEv07RIItdXKIXAA==" 20 | }, 21 | "@types/yauzl": { 22 | "version": "2.9.1", 23 | "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", 24 | "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", 25 | "optional": true, 26 | "requires": { 27 | "@types/node": "*" 28 | } 29 | }, 30 | "accepts": { 31 | "version": "1.3.7", 32 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 33 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 34 | "requires": { 35 | "mime-types": "~2.1.24", 36 | "negotiator": "0.6.2" 37 | } 38 | }, 39 | "agent-base": { 40 | "version": "5.1.1", 41 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", 42 | "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==" 43 | }, 44 | "array-flatten": { 45 | "version": "1.1.1", 46 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 47 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 48 | }, 49 | "axios": { 50 | "version": "0.19.2", 51 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", 52 | "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", 53 | "requires": { 54 | "follow-redirects": "1.5.10" 55 | } 56 | }, 57 | "balanced-match": { 58 | "version": "1.0.0", 59 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 60 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 61 | }, 62 | "base64-js": { 63 | "version": "1.3.1", 64 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", 65 | "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" 66 | }, 67 | "bl": { 68 | "version": "4.0.3", 69 | "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", 70 | "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", 71 | "requires": { 72 | "buffer": "^5.5.0", 73 | "inherits": "^2.0.4", 74 | "readable-stream": "^3.4.0" 75 | }, 76 | "dependencies": { 77 | "inherits": { 78 | "version": "2.0.4", 79 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 80 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 81 | } 82 | } 83 | }, 84 | "body-parser": { 85 | "version": "1.19.0", 86 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 87 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 88 | "requires": { 89 | "bytes": "3.1.0", 90 | "content-type": "~1.0.4", 91 | "debug": "2.6.9", 92 | "depd": "~1.1.2", 93 | "http-errors": "1.7.2", 94 | "iconv-lite": "0.4.24", 95 | "on-finished": "~2.3.0", 96 | "qs": "6.7.0", 97 | "raw-body": "2.4.0", 98 | "type-is": "~1.6.17" 99 | } 100 | }, 101 | "boolbase": { 102 | "version": "1.0.0", 103 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", 104 | "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" 105 | }, 106 | "brace-expansion": { 107 | "version": "1.1.11", 108 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 109 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 110 | "requires": { 111 | "balanced-match": "^1.0.0", 112 | "concat-map": "0.0.1" 113 | } 114 | }, 115 | "buffer": { 116 | "version": "5.6.0", 117 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", 118 | "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", 119 | "requires": { 120 | "base64-js": "^1.0.2", 121 | "ieee754": "^1.1.4" 122 | } 123 | }, 124 | "buffer-crc32": { 125 | "version": "0.2.13", 126 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", 127 | "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" 128 | }, 129 | "bytes": { 130 | "version": "3.1.0", 131 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 132 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 133 | }, 134 | "cheerio": { 135 | "version": "1.0.0-rc.3", 136 | "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", 137 | "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", 138 | "requires": { 139 | "css-select": "~1.2.0", 140 | "dom-serializer": "~0.1.1", 141 | "entities": "~1.1.1", 142 | "htmlparser2": "^3.9.1", 143 | "lodash": "^4.15.0", 144 | "parse5": "^3.0.1" 145 | } 146 | }, 147 | "chownr": { 148 | "version": "1.1.4", 149 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 150 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 151 | }, 152 | "concat-map": { 153 | "version": "0.0.1", 154 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 155 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 156 | }, 157 | "content-disposition": { 158 | "version": "0.5.3", 159 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 160 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 161 | "requires": { 162 | "safe-buffer": "5.1.2" 163 | } 164 | }, 165 | "content-type": { 166 | "version": "1.0.4", 167 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 168 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 169 | }, 170 | "cookie": { 171 | "version": "0.4.0", 172 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 173 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 174 | }, 175 | "cookie-signature": { 176 | "version": "1.0.6", 177 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 178 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 179 | }, 180 | "css-select": { 181 | "version": "1.2.0", 182 | "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", 183 | "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", 184 | "requires": { 185 | "boolbase": "~1.0.0", 186 | "css-what": "2.1", 187 | "domutils": "1.5.1", 188 | "nth-check": "~1.0.1" 189 | } 190 | }, 191 | "css-what": { 192 | "version": "2.1.3", 193 | "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", 194 | "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" 195 | }, 196 | "debug": { 197 | "version": "2.6.9", 198 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 199 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 200 | "requires": { 201 | "ms": "2.0.0" 202 | } 203 | }, 204 | "depd": { 205 | "version": "1.1.2", 206 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 207 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 208 | }, 209 | "destroy": { 210 | "version": "1.0.4", 211 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 212 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 213 | }, 214 | "dom-serializer": { 215 | "version": "0.1.1", 216 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", 217 | "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", 218 | "requires": { 219 | "domelementtype": "^1.3.0", 220 | "entities": "^1.1.1" 221 | } 222 | }, 223 | "domelementtype": { 224 | "version": "1.3.1", 225 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", 226 | "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" 227 | }, 228 | "domhandler": { 229 | "version": "2.4.2", 230 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", 231 | "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", 232 | "requires": { 233 | "domelementtype": "1" 234 | } 235 | }, 236 | "domutils": { 237 | "version": "1.5.1", 238 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", 239 | "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", 240 | "requires": { 241 | "dom-serializer": "0", 242 | "domelementtype": "1" 243 | } 244 | }, 245 | "ee-first": { 246 | "version": "1.1.1", 247 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 248 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 249 | }, 250 | "encodeurl": { 251 | "version": "1.0.2", 252 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 253 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 254 | }, 255 | "end-of-stream": { 256 | "version": "1.4.4", 257 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 258 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 259 | "requires": { 260 | "once": "^1.4.0" 261 | } 262 | }, 263 | "entities": { 264 | "version": "1.1.2", 265 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", 266 | "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" 267 | }, 268 | "escape-html": { 269 | "version": "1.0.3", 270 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 271 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 272 | }, 273 | "etag": { 274 | "version": "1.8.1", 275 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 276 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 277 | }, 278 | "express": { 279 | "version": "4.17.1", 280 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 281 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 282 | "requires": { 283 | "accepts": "~1.3.7", 284 | "array-flatten": "1.1.1", 285 | "body-parser": "1.19.0", 286 | "content-disposition": "0.5.3", 287 | "content-type": "~1.0.4", 288 | "cookie": "0.4.0", 289 | "cookie-signature": "1.0.6", 290 | "debug": "2.6.9", 291 | "depd": "~1.1.2", 292 | "encodeurl": "~1.0.2", 293 | "escape-html": "~1.0.3", 294 | "etag": "~1.8.1", 295 | "finalhandler": "~1.1.2", 296 | "fresh": "0.5.2", 297 | "merge-descriptors": "1.0.1", 298 | "methods": "~1.1.2", 299 | "on-finished": "~2.3.0", 300 | "parseurl": "~1.3.3", 301 | "path-to-regexp": "0.1.7", 302 | "proxy-addr": "~2.0.5", 303 | "qs": "6.7.0", 304 | "range-parser": "~1.2.1", 305 | "safe-buffer": "5.1.2", 306 | "send": "0.17.1", 307 | "serve-static": "1.14.1", 308 | "setprototypeof": "1.1.1", 309 | "statuses": "~1.5.0", 310 | "type-is": "~1.6.18", 311 | "utils-merge": "1.0.1", 312 | "vary": "~1.1.2" 313 | } 314 | }, 315 | "extract-zip": { 316 | "version": "2.0.1", 317 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", 318 | "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", 319 | "requires": { 320 | "@types/yauzl": "^2.9.1", 321 | "debug": "^4.1.1", 322 | "get-stream": "^5.1.0", 323 | "yauzl": "^2.10.0" 324 | }, 325 | "dependencies": { 326 | "debug": { 327 | "version": "4.1.1", 328 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 329 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 330 | "requires": { 331 | "ms": "^2.1.1" 332 | } 333 | }, 334 | "ms": { 335 | "version": "2.1.2", 336 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 337 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 338 | } 339 | } 340 | }, 341 | "fd-slicer": { 342 | "version": "1.1.0", 343 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", 344 | "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", 345 | "requires": { 346 | "pend": "~1.2.0" 347 | } 348 | }, 349 | "finalhandler": { 350 | "version": "1.1.2", 351 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 352 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 353 | "requires": { 354 | "debug": "2.6.9", 355 | "encodeurl": "~1.0.2", 356 | "escape-html": "~1.0.3", 357 | "on-finished": "~2.3.0", 358 | "parseurl": "~1.3.3", 359 | "statuses": "~1.5.0", 360 | "unpipe": "~1.0.0" 361 | } 362 | }, 363 | "follow-redirects": { 364 | "version": "1.5.10", 365 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", 366 | "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", 367 | "requires": { 368 | "debug": "=3.1.0" 369 | }, 370 | "dependencies": { 371 | "debug": { 372 | "version": "3.1.0", 373 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 374 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 375 | "requires": { 376 | "ms": "2.0.0" 377 | } 378 | } 379 | } 380 | }, 381 | "forwarded": { 382 | "version": "0.1.2", 383 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 384 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 385 | }, 386 | "fresh": { 387 | "version": "0.5.2", 388 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 389 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 390 | }, 391 | "fs-constants": { 392 | "version": "1.0.0", 393 | "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", 394 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" 395 | }, 396 | "fs.realpath": { 397 | "version": "1.0.0", 398 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 399 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 400 | }, 401 | "get-stream": { 402 | "version": "5.1.0", 403 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", 404 | "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", 405 | "requires": { 406 | "pump": "^3.0.0" 407 | } 408 | }, 409 | "glob": { 410 | "version": "7.1.6", 411 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 412 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 413 | "requires": { 414 | "fs.realpath": "^1.0.0", 415 | "inflight": "^1.0.4", 416 | "inherits": "2", 417 | "minimatch": "^3.0.4", 418 | "once": "^1.3.0", 419 | "path-is-absolute": "^1.0.0" 420 | } 421 | }, 422 | "htmlparser2": { 423 | "version": "3.10.1", 424 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", 425 | "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", 426 | "requires": { 427 | "domelementtype": "^1.3.1", 428 | "domhandler": "^2.3.0", 429 | "domutils": "^1.5.1", 430 | "entities": "^1.1.1", 431 | "inherits": "^2.0.1", 432 | "readable-stream": "^3.1.1" 433 | } 434 | }, 435 | "http-errors": { 436 | "version": "1.7.2", 437 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 438 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 439 | "requires": { 440 | "depd": "~1.1.2", 441 | "inherits": "2.0.3", 442 | "setprototypeof": "1.1.1", 443 | "statuses": ">= 1.5.0 < 2", 444 | "toidentifier": "1.0.0" 445 | } 446 | }, 447 | "https-proxy-agent": { 448 | "version": "4.0.0", 449 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", 450 | "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", 451 | "requires": { 452 | "agent-base": "5", 453 | "debug": "4" 454 | }, 455 | "dependencies": { 456 | "debug": { 457 | "version": "4.1.1", 458 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 459 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 460 | "requires": { 461 | "ms": "^2.1.1" 462 | } 463 | }, 464 | "ms": { 465 | "version": "2.1.2", 466 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 467 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 468 | } 469 | } 470 | }, 471 | "iconv-lite": { 472 | "version": "0.4.24", 473 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 474 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 475 | "requires": { 476 | "safer-buffer": ">= 2.1.2 < 3" 477 | } 478 | }, 479 | "ieee754": { 480 | "version": "1.1.13", 481 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 482 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" 483 | }, 484 | "inflight": { 485 | "version": "1.0.6", 486 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 487 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 488 | "requires": { 489 | "once": "^1.3.0", 490 | "wrappy": "1" 491 | } 492 | }, 493 | "inherits": { 494 | "version": "2.0.3", 495 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 496 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 497 | }, 498 | "ipaddr.js": { 499 | "version": "1.9.1", 500 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 501 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 502 | }, 503 | "lodash": { 504 | "version": "4.17.21", 505 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 506 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 507 | }, 508 | "media-typer": { 509 | "version": "0.3.0", 510 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 511 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 512 | }, 513 | "merge-descriptors": { 514 | "version": "1.0.1", 515 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 516 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 517 | }, 518 | "methods": { 519 | "version": "1.1.2", 520 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 521 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 522 | }, 523 | "mime": { 524 | "version": "1.6.0", 525 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 526 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 527 | }, 528 | "mime-db": { 529 | "version": "1.44.0", 530 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 531 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 532 | }, 533 | "mime-types": { 534 | "version": "2.1.27", 535 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 536 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 537 | "requires": { 538 | "mime-db": "1.44.0" 539 | } 540 | }, 541 | "minimatch": { 542 | "version": "3.0.4", 543 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 544 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 545 | "requires": { 546 | "brace-expansion": "^1.1.7" 547 | } 548 | }, 549 | "mkdirp-classic": { 550 | "version": "0.5.3", 551 | "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", 552 | "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" 553 | }, 554 | "ms": { 555 | "version": "2.0.0", 556 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 557 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 558 | }, 559 | "negotiator": { 560 | "version": "0.6.2", 561 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 562 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 563 | }, 564 | "nth-check": { 565 | "version": "1.0.2", 566 | "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", 567 | "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", 568 | "requires": { 569 | "boolbase": "~1.0.0" 570 | } 571 | }, 572 | "on-finished": { 573 | "version": "2.3.0", 574 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 575 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 576 | "requires": { 577 | "ee-first": "1.1.1" 578 | } 579 | }, 580 | "once": { 581 | "version": "1.4.0", 582 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 583 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 584 | "requires": { 585 | "wrappy": "1" 586 | } 587 | }, 588 | "parse5": { 589 | "version": "3.0.3", 590 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", 591 | "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", 592 | "requires": { 593 | "@types/node": "*" 594 | } 595 | }, 596 | "parseurl": { 597 | "version": "1.3.3", 598 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 599 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 600 | }, 601 | "path-is-absolute": { 602 | "version": "1.0.1", 603 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 604 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 605 | }, 606 | "path-to-regexp": { 607 | "version": "0.1.7", 608 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 609 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 610 | }, 611 | "pend": { 612 | "version": "1.2.0", 613 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 614 | "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" 615 | }, 616 | "progress": { 617 | "version": "2.0.3", 618 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 619 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" 620 | }, 621 | "proxy-addr": { 622 | "version": "2.0.6", 623 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 624 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 625 | "requires": { 626 | "forwarded": "~0.1.2", 627 | "ipaddr.js": "1.9.1" 628 | } 629 | }, 630 | "proxy-from-env": { 631 | "version": "1.1.0", 632 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 633 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 634 | }, 635 | "pump": { 636 | "version": "3.0.0", 637 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 638 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 639 | "requires": { 640 | "end-of-stream": "^1.1.0", 641 | "once": "^1.3.1" 642 | } 643 | }, 644 | "puppeteer-core": { 645 | "version": "3.3.0", 646 | "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-3.3.0.tgz", 647 | "integrity": "sha512-hynQ3r0J/lkGrKeBCqu160jrj0WhthYLIzDQPkBxLzxPokjw4elk1sn6mXAian/kfD2NRzpdh9FSykxZyL56uA==", 648 | "requires": { 649 | "debug": "^4.1.0", 650 | "extract-zip": "^2.0.0", 651 | "https-proxy-agent": "^4.0.0", 652 | "mime": "^2.0.3", 653 | "progress": "^2.0.1", 654 | "proxy-from-env": "^1.0.0", 655 | "rimraf": "^3.0.2", 656 | "tar-fs": "^2.0.0", 657 | "unbzip2-stream": "^1.3.3", 658 | "ws": "^7.2.3" 659 | }, 660 | "dependencies": { 661 | "debug": { 662 | "version": "4.1.1", 663 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 664 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 665 | "requires": { 666 | "ms": "^2.1.1" 667 | } 668 | }, 669 | "mime": { 670 | "version": "2.4.6", 671 | "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", 672 | "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" 673 | }, 674 | "ms": { 675 | "version": "2.1.2", 676 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 677 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 678 | } 679 | } 680 | }, 681 | "qs": { 682 | "version": "6.7.0", 683 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 684 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 685 | }, 686 | "range-parser": { 687 | "version": "1.2.1", 688 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 689 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 690 | }, 691 | "raw-body": { 692 | "version": "2.4.0", 693 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 694 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 695 | "requires": { 696 | "bytes": "3.1.0", 697 | "http-errors": "1.7.2", 698 | "iconv-lite": "0.4.24", 699 | "unpipe": "1.0.0" 700 | } 701 | }, 702 | "readable-stream": { 703 | "version": "3.6.0", 704 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 705 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 706 | "requires": { 707 | "inherits": "^2.0.3", 708 | "string_decoder": "^1.1.1", 709 | "util-deprecate": "^1.0.1" 710 | } 711 | }, 712 | "rimraf": { 713 | "version": "3.0.2", 714 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 715 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 716 | "requires": { 717 | "glob": "^7.1.3" 718 | } 719 | }, 720 | "safe-buffer": { 721 | "version": "5.1.2", 722 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 723 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 724 | }, 725 | "safer-buffer": { 726 | "version": "2.1.2", 727 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 728 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 729 | }, 730 | "send": { 731 | "version": "0.17.1", 732 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 733 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 734 | "requires": { 735 | "debug": "2.6.9", 736 | "depd": "~1.1.2", 737 | "destroy": "~1.0.4", 738 | "encodeurl": "~1.0.2", 739 | "escape-html": "~1.0.3", 740 | "etag": "~1.8.1", 741 | "fresh": "0.5.2", 742 | "http-errors": "~1.7.2", 743 | "mime": "1.6.0", 744 | "ms": "2.1.1", 745 | "on-finished": "~2.3.0", 746 | "range-parser": "~1.2.1", 747 | "statuses": "~1.5.0" 748 | }, 749 | "dependencies": { 750 | "ms": { 751 | "version": "2.1.1", 752 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 753 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 754 | } 755 | } 756 | }, 757 | "serve-static": { 758 | "version": "1.14.1", 759 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 760 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 761 | "requires": { 762 | "encodeurl": "~1.0.2", 763 | "escape-html": "~1.0.3", 764 | "parseurl": "~1.3.3", 765 | "send": "0.17.1" 766 | } 767 | }, 768 | "setprototypeof": { 769 | "version": "1.1.1", 770 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 771 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 772 | }, 773 | "statuses": { 774 | "version": "1.5.0", 775 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 776 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 777 | }, 778 | "string_decoder": { 779 | "version": "1.3.0", 780 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 781 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 782 | "requires": { 783 | "safe-buffer": "~5.2.0" 784 | }, 785 | "dependencies": { 786 | "safe-buffer": { 787 | "version": "5.2.1", 788 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 789 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 790 | } 791 | } 792 | }, 793 | "tar-fs": { 794 | "version": "2.1.0", 795 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz", 796 | "integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==", 797 | "requires": { 798 | "chownr": "^1.1.1", 799 | "mkdirp-classic": "^0.5.2", 800 | "pump": "^3.0.0", 801 | "tar-stream": "^2.0.0" 802 | } 803 | }, 804 | "tar-stream": { 805 | "version": "2.1.3", 806 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz", 807 | "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==", 808 | "requires": { 809 | "bl": "^4.0.1", 810 | "end-of-stream": "^1.4.1", 811 | "fs-constants": "^1.0.0", 812 | "inherits": "^2.0.3", 813 | "readable-stream": "^3.1.1" 814 | } 815 | }, 816 | "through": { 817 | "version": "2.3.8", 818 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 819 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" 820 | }, 821 | "toidentifier": { 822 | "version": "1.0.0", 823 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 824 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 825 | }, 826 | "type-is": { 827 | "version": "1.6.18", 828 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 829 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 830 | "requires": { 831 | "media-typer": "0.3.0", 832 | "mime-types": "~2.1.24" 833 | } 834 | }, 835 | "unbzip2-stream": { 836 | "version": "1.4.3", 837 | "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", 838 | "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", 839 | "requires": { 840 | "buffer": "^5.2.1", 841 | "through": "^2.3.8" 842 | } 843 | }, 844 | "unpipe": { 845 | "version": "1.0.0", 846 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 847 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 848 | }, 849 | "util-deprecate": { 850 | "version": "1.0.2", 851 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 852 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 853 | }, 854 | "utils-merge": { 855 | "version": "1.0.1", 856 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 857 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 858 | }, 859 | "vary": { 860 | "version": "1.1.2", 861 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 862 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 863 | }, 864 | "wrappy": { 865 | "version": "1.0.2", 866 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 867 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 868 | }, 869 | "ws": { 870 | "version": "7.4.6", 871 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", 872 | "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" 873 | }, 874 | "yauzl": { 875 | "version": "2.10.0", 876 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", 877 | "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", 878 | "requires": { 879 | "buffer-crc32": "~0.2.3", 880 | "fd-slicer": "~1.1.0" 881 | } 882 | } 883 | } 884 | } 885 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-renderer", 3 | "version": "1.0.0", 4 | "description": "A online puppeteer service to render pages with javascript (js). Mainly useful for web scraping (not using splash).", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node index.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/geshan/js-renderer.git" 13 | }, 14 | "keywords": [ 15 | "javascript", 16 | "nodejs", 17 | "puppeteer" 18 | ], 19 | "author": "Geshan Manandhar", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/geshan/js-renderer/issues" 23 | }, 24 | "homepage": "https://github.com/geshan/js-renderer#readme", 25 | "dependencies": { 26 | "@geshan/axrio": "^1.0.1", 27 | "express": "^4.17.1", 28 | "puppeteer-core": "^3.3.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /yt-views.js: -------------------------------------------------------------------------------- 1 | const axrio = require('@geshan/axrio'); 2 | 3 | (async function run() { 4 | console.log(`Pulling views from YouTube, please wait...`); 5 | try { 6 | const ytVideoUrl = process.argv[2] ? process.argv[2] : 'https://www.youtube.com/watch?v=kJQP7kiw5Fk'; 7 | const $ = await axrio.getPage(`https://js-renderer-fly.fly.dev/api/render?url=${ytVideoUrl}`, 12000); 8 | const title = $('h1.title>yt-formatted-string').text(); 9 | const views = $('span.view-count').text(); 10 | console.log(`${title} has ${views}`); 11 | } catch(e) { 12 | console.log(`Error while fetching views: `, e); 13 | } 14 | process.exit(0); 15 | })(); 16 | --------------------------------------------------------------------------------