background
In mobile services, there is often a need for fission, which requires dynamic poster generation. Generally, posters need to include the authorization information of the user and the TWO-DIMENSIONAL code of the landing page. Generally, most posters generated by H5 end are canvas dynamic paintings and html2Canvas library generation. However, these two kinds are basically based on canvas, and the pictures generated by Canvas are basically fuzzy to pictures with dark colors. Therefore, when using this method to generate pictures, I usually communicate with the designer in advance to make some relatively shallow pictures. After several such requests, I always wanted to find a better way. One day, when I was taking screenshots on my mobile phone, I thought, can I use screenshots to generate posters? Then THE idea got out of control. I thought of the command of using Chrome to capture long page pictures before, so I searched whether Google provided relevant SDK. This search leads to the protagonist of today’s Puppeteer,chrome’s open source headless browser, plus a similar headless browser used to write crawlers before, and instantly the idea opens up and begins to play.
First try
I quickly read the Puppeteer Documentation in Chinese and thought about the implementation logic:
2. Write a Node service to provide a screenshot interface where the interface logic: 1. Create a page instance. 3. Open the specified poster page. 5. Screenshot 6. Close the browser instanceCopy the code
The main logic code is very simple. The first example given by the official is to rewrite it:
const puppeteer = require('puppeteer')
const router = express.Router();
const devices = require('puppeteer/DeviceDescriptors')
const iPhone = devices['iPhone 8']
const GetPoster = async (url, pageOptions, posterOptions) => {
// Create a browser instance
// Args is a set of browser performance parameters that are explained in the official documentation
const browser = await puppeteer.launch({
args: ['--no-sandbox'.'--disable-setuid-sandbox'.'- - disable - gpu'.'- - disable - dev - SHM - usage'.'- - no - first - run'.'- - no - zygote'.'- - single - the process']});let screenshot = ' '
try {
// Create a Page instance
const page = await browser.newPage();
let path = 'example.jpg'
// Set the model
await page.emulate(iPhone)
/ wide/high
await page.setViewport({
isMobile: true.width: 414.height: 736. pageOptions })// Open the poster page
await page.goto(url, {
waitUntil: ['load'.'domcontentloaded']});// Wait for the tag to finish rendering the poster
await page.waitForSelector('.load-complete')
/ / screenshots
screenshot = await page.screenshot({
path: path, ... posterOptions });// Close the browser instance
await browser.close();
} catch (err) {
console.log(err)
await browser.close()
}
return screenshot
}
Copy the code
After writing the code, try it quickly. Local found that the perfect screenshot was OK and the HD had no defects, so I quickly requested deployment with the operation and maintenance boss excitedly. Ideal into reality, the shadow is often terrible to see.
When the ops got the code, they started a Cenos 6.x server, and the install dependencies were broken. So I continued to search for a solution, and I found one.
Then try again, this time the picture is cut out, the result of the picture is a piece of gibberish, search, it is because the server does not install Chinese fonts, install.
Finally I can take a screenshot, but the screenshot is different from the local one, there is a layer of white mask. However at that time as a result of business more rush, did not continue to delve into, on the line.
Then the tragedy came, not because of the white mask, but the service was suspended after two days. Fortunately, the operation and maintenance boss helped to make the daemon script, and the service could restart. However, operations said that memory kept exploding, so I reviewed the code and imagined the browser mechanism.
I found that there were too many requests, so I built a browser instance every time, and each browser instance was about 250M, and the server burst as soon as it was connected. At that time, I wanted to open one or several browser instances, and only opened the browser TAB page to take screenshots every time I opened the poster page.
The same day I found an article on how to use generic-pool to optimize puppeteer concurrency. So quickly optimize:
const pool = require('.. /bin/pool')
const browserPool = pool.InitPool({ // The global should only be initialized once
puppeteerArgs: {
ignoreHTTPSErrors: true.headless: true.// Whether to enable headless page
timeout: 0.pipe: true.// Do not use websocket
args: ['--no-sandbox'.'--disable-setuid-sandbox'.'- - disable - gpu'.'- - disable - dev - SHM - usage'.'- - no - first - run'.'- - no - zygote'.'- - single - the process']}})const GetPoster = async (url) => {
let screenshot = ' '
try {
const page = await browserPool.use(async (instance) => {
const page = await instance.newPage()
await page.emulate(iPhone)
await page.goto(url, {
waitUntil: ['load'.'domcontentloaded']});await page.waitForSelector('.load-complete')
return page
})
screenshot = await page.screenshot({
fullPage: true.quality: 100.type: 'jpeg'
});
page.close()
} catch (err) {
console.log(err)
}
return screenshot
}
Copy the code
Problem solving
Second attempt
If the hidden danger is not solved, there will always be a day to break out. After a year of fermentation, the problem of blurring posters finally came back to me. This time, we will make fission 2.0, which is a good opportunity to solve the problem completely. At first, I suspected that there was something wrong with the server, so I applied for another test server to operation and maintenance, deployed the code, built the environment, and requested. The result showed that the screenshot was very clear, and there was no gray mask.
So I told the operation and maintenance brother that we should not change another server for deployment. The operation and maintenance brother agreed, and the deployment was completed soon. The request was still covered with white layer.
Head large ~ then suddenly found a test server screenshots request 1s, and server screenshots as long as 200ms. So it seems to find the reason, is it because the browser opened too fast, not to show the full screenshots, all have white mask. So I wrote a sleep:
const sleep = (microSec) = > new Promise((res, rej) = > setTimeout(res, microSec));
await sleep(100)
Copy the code
Pause for 100ms before taking a screenshot. Then deploy and find the problem solved.
The remaining problems
The rest of the problem is the image storage problem, because the poster can be pre-generated in advance, so the poster generation is actually not high on the request performance requirements, but also try to control within 1s, not sure will encounter a variety of scenes.
Our pictures are uploaded to Qiniu Cloud storage, which actually takes a lot of time to upload, so the following optimization can be carried out:
- If the image resource is stored on its own server, please do CDN forwarding
- If you are using a cloud server, increase the bandwidth of your service
- At the code level, caching can be done, because the request address of the poster usually corresponds to one user. In the case of not considering the frequent change of user information, redis caching can be done on the path of the generated poster. After the user generates the poster for the first time, the next request will directly return the screenshot of the last time. Of course, this can also be handed over to the requester rather than put on the poster server
- This poster can be cross-platform and can be used on any side of the app h5 small program can be very convenient
Related codes