RanAI01/CHMA-API
0
1const express = require('express');2const puppeteer = require('puppeteer');3 4const app = express();5const PORT = process.env.PORT || 7860;6 7app.get('/crawl', async (req, res) => {8 const url = 'https://goodinfo.tw/tw/StockList.asp?MARKET_CAT=%E6%99%BA%E6%85%A7%E9%81%B8%E8%82%A1&INDUSTRY_CAT=%E4%B8%89%E5%A4%A7%E6%B3%95%E4%BA%BA%E9%80%A3%E8%B2%B7+%E2%80%93+%E6%97%A5%40%40%E4%B8%89%E5%A4%A7%E6%B3%95%E4%BA%BA%E9%80%A3%E7%BA%8C%E8%B2%B7%E8%B6%85%40%40%E4%B8%89%E5%A4%A7%E6%B3%95%E4%BA%BA%E9%80%A3%E7%BA%8C%E8%B2%B7%E8%B6%85+%E2%80%93+%E6%97%A5&表=%E6%B3%95%E4%BA%BA%E8%B2%B7%E8%B3%A3%5F%E4%B8%89%E5%A4%A7&SHEET2=%E6%B3%95%E4%BA%BA%E9%80%A3%E8%B2%B7%E9%80%A3%E8%B3%A3%E7%B5%B1%E8%A8%88%28%E6%97%A5%29';9 10 let browser;11 try {12 browser = await puppeteer.launch({13 args: ['--no-sandbox', '--disable-setuid-sandbox'],14 headless: true,15 });16 17 const page = await browser.newPage();18 await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36');19 await page.goto(url, { waitUntil: 'load' });20 21 // 等待表格容器出現,延長等待時間至 30 秒22 await page.waitForSelector('#tblStockList', { timeout: 30000 });23 24 // 確保表格內有資料25 await page.waitForFunction(() => {26 const table = document.querySelector('#tblStockList');27 return table && table.querySelectorAll('tbody tr').length > 0;28 }, { timeout: 30000 });29 30 const data = await page.evaluate(() => {31 const rows = Array.from(document.querySelectorAll('#tblStockList tbody tr'));32 return rows.map(row => {33 const cells = row.querySelectorAll('td');34 const aTag = cells[0]?.querySelector('a');35 // 股票編號36 const stockId = aTag?.getAttribute('href')?.match(/STOCK_ID=(\w+)/)?.[1] || '';37 // 名稱(從標題屬性解析,取出「個股詳細資料」和「日線圖」)38 const title = aTag?.getAttribute('title') || '';39 const name = title40 .replace(/^查看\s*\(\d+\)/, '')41 .replace(/個股詳細資料$/, '')42 .replace(/日線圖$/, '')43 .trim();44 45 return {46 stockId,47 name,48 date: cells[3]?.innerText.trim(),49 foreignDays: cells[4]?.innerText.trim(),50 foreignShares: cells[5]?.innerText.trim(),51 trustDays: cells[8]?.innerText.trim(),52 trustShares: cells[9]?.innerText.trim(),53 dealerDays: cells[12]?.innerText.trim(),54 dealerShares: cells[13]?.innerText.trim(),55 totalDays: cells[16]?.innerText.trim(),56 totalShares: cells[17]?.innerText.trim(),57 };58 });59 });60 61 await browser.close();62 res.json(data);63 } catch (err) {64 if (browser) {65 await browser.close();66 }67 res.status(500).json({ error: err.message, stack: err.stack });68 }69});70 71app.get('/', (req, res) => {72 res.send('爬蟲API相容中,請用 /crawl 取得資料');73});74 75app.listen(PORT, () => {76 console.log(`伺服器正在連接埠 ${PORT} 上執行`);77});