puppeteer / server.js
vikarshana's picture
Update server.js
5710d2f verified
const express = require('express');
const path = require('path');
const fs = require('fs');
const puppeteer = require('puppeteer-core');
const app = express();
const PORT = 7860;
// 中间件
app.use(express.json({ limit: '40mb' }));
app.use(express.static('.'));
// CORS支持
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
if (req.method === 'OPTIONS') {
res.sendStatus(200);
} else {
next();
}
});
async function redr(url, method = 'GET', postData = null) {
const browser = await puppeteer.launch({
executablePath: "/opt/google/chrome/chrome",
headless: true,
});
const page = await browser.newPage();
await page.setExtraHTTPHeaders({
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'accept-encoding': 'gzip, deflate, br, zstd',
'accept-language': 'en-US,en;q=0.9',
'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Mobile Safari/537.36'
});
try {
let response;
if (method === 'POST' && postData) {
response = await page.goto(url, {
waitUntil: 'load',
timeout: 0,
method: 'POST',
postData: JSON.stringify(postData),
headers: {
'Content-Type': 'application/json',
}
});
} else {
response = await page.goto(url, { waitUntil: 'load', timeout: 10000 });
}
const finalUrl = page.url();
console.log('Page URL after redirect (if any):', finalUrl);
const content = await page.content();
return content;
} catch (error) {
console.error('Error scraping page:', error);
return `Error: ${error.message}`;
} finally {
await browser.close();
}
}
app.get('/location', async (req, res) => {
const url = req.query.url;
const method = req.query.method || 'GET';
if (!url) {
return res.status(400).send('URL query parameter is required');
}
try {
const pageContent = await redr(url, method);
res.send(pageContent);
} catch (error) {
res.status(500).send('Failed to scrape the page');
}
});
async function captureGDrivePayloadAndCookie(url, opts = {}) {
const {
chromePath = '/opt/google/chrome/chrome',
headless = 'new',
buttonSelector = '#gdriveButton',
fallbackSelector = '.google-download',
waitForButtonMs = 60000,
waitForPostMs = 20000,
} = opts;
let browser = null;
let page = null;
try {
browser = await puppeteer.launch({
executablePath: chromePath,
headless,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-web-security',
'--disable-gpu',
'--no-zygote',
],
});
page = await browser.newPage();
await page.setViewport({ width: 1280, height: 900 });
await page.setUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
'(KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'
);
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'webdriver', { get: () => false });
Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3] });
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
window.chrome = { runtime: {} };
});
await page.setRequestInterception(true);
page.on('request', (req) => {
const rt = req.resourceType();
if (['image', 'stylesheet', 'font', 'media'].includes(rt)) req.abort();
else req.continue();
});
await page.goto(url, { waitUntil: 'load', timeout: 60000 });
await page.waitForFunction(() => document.readyState === 'complete', { timeout: 30000 }).catch(() => { /* ignore */ });
let foundSelector = null;
try {
console.log('⏳ Waiting for primary selector:', buttonSelector);
await page.waitForSelector(buttonSelector, { visible: true, timeout: waitForButtonMs });
foundSelector = buttonSelector;
} catch (errPrimary) {
console.log('primary selector not found, trying fallback:', fallbackSelector);
try {
await page.waitForSelector(fallbackSelector, { visible: true, timeout: Math.max(5000, waitForButtonMs / 4) });
foundSelector = fallbackSelector;
} catch (errFallback) {
throw new Error(`Button not found using selectors "${buttonSelector}" or "${fallbackSelector}"`);
}
}
console.log('✅ Found button selector:', foundSelector);
const postRequestPromise = page.waitForRequest(
(req) => {
try {
if (req.method() !== 'POST') return false;
const body = req.postData() || '';
if (body.includes('"gdrive"') || body.includes('gdrive') || /token\s*[:=]/i.test(body)) return true;
} catch (e) {
}
return false;
},
{ timeout: waitForPostMs }
);
await page.evaluate((sel) => {
const el = document.querySelector(sel);
if (el) {
el.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
}
}, foundSelector);
await page.click(foundSelector);
let matchedRequest;
try {
matchedRequest = await postRequestPromise;
} catch (err) {
// no post captured
throw new Error('Timed out waiting for POST request after clicking the button');
}
const headers = matchedRequest.headers() || {};
const cookieHeader = headers.cookie || null;
const rawBody = matchedRequest.postData() || '';
let parsedPayload = null;
try {
parsedPayload = JSON.parse(rawBody);
} catch (e) {
try {
const params = new URLSearchParams(rawBody);
if ([...params].length > 0) {
parsedPayload = {};
for (const [k, v] of params.entries()) parsedPayload[k] = v;
} else {
parsedPayload = { raw: rawBody };
}
} catch (e2) {
parsedPayload = { raw: rawBody };
}
}
await page.close();
await browser.close();
return {
success: true,
cookies: cookieHeader,
token: parsedPayload.token,
requestUrl: matchedRequest.url(),
};
} catch (err) {
console.error('captureGDrivePayloadAndCookie error:', err.message || err);
if (page) {
try { await page.close(); } catch {}
}
if (browser) {
try { await browser.close(); } catch {}
}
return { success: false, error: String(err.message || err) };
}
}
app.get('/api/bypass', async (req, res) => {
const { url } = req.query;
if (!url) return res.status(400).json({ error: 'URL is required' });
try {
const result = await captureGDrivePayloadAndCookie(url, {
chromePath: '/opt/google/chrome/chrome',
headless: 'new',
buttonSelector: '#gdriveButton',
fallbackSelector: '.google-download',
waitForButtonMs: 60000,
waitForPostMs: 20000,
});
if (!result.success) {
return res.status(500).json({ error: result.error || 'failed' });
}
return res.json({
cookie: result.cookieHeader,
payload: result.payload,
requestUrl: result.requestUrl,
});
} catch (err) {
console.error('/api/bypass error:', err);
return res.status(500).json({ error: 'Internal error' });
}
});
app.get('/api/cinex', async (req, res) => {
const { url } = req.query;
if (!url) {
return res.status(400).json({ error: 'URL is required' });
}
const result = await captureGDrivePayloadAndCookie(url, {
chromePath: '/opt/google/chrome/chrome',
headless: 'new',
buttonSelector: '#gdriveButton',
fallbackSelector: '.google-download',
waitForButtonMs: 60000,
waitForPostMs: 20000,
});
if (!result.success) {
return res.status(500).json({ error: result.error || 'failed' });
}
return res.json({
cookie: result.cookieHeader,
payload: result.payload,
requestUrl: result.requestUrl,
});
});
// 健康检查端点
app.get('/', (req, res) => {
res.json({ status: 'hii', timestamp: new Date().toISOString() });
});
// 启动服务器
app.listen(PORT, () => {
console.log(`Puppeteer服务器运行在 http://localhost:${PORT}`);
console.log('API端点:');
console.log(' POST /api/generate-pdf - 生成PDF');
console.log(' POST /api/generate-images - 生成图片');
console.log(' GET /api/health - 健康检查');
});
module.exports = app;