You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Express+Puppeteer通过Webhooks实现Tradovate交易按钮自动点击?

问题:Webhook触发的Tradovate自动化交易无响应

我正在做一个项目,通过模拟点击按钮,把Webhook传来的交易信号自动同步到Tradovate平台。预期流程是:

  • 运行JS应用
  • 打开Chrome并登录Tradovate账户
  • 控制台打印“Awaiting...”和“Node.js server started on port 5000.”
  • 用Insomnia模拟Webhook请求
  • 向Ngrok转发的URL发送POST请求(JSON格式),返回200 OK
  • 根据order_action的值,自动点击买入或卖出按钮

现在Ngrok已经在转发5000端口,但买入/卖出操作完全没触发,也没有报错。我怀疑问题出在app.post( '/', async function( req, res )这部分代码,因为我对Puppeteer不太熟,希望能得到帮助。

相关代码如下:

import puppeteer from 'puppeteer';
import express from 'express';

const app = express();
app.use( express.json() );

(async () => {
    // Launch the browser and open a new blank page
    const browser = await puppeteer.launch(
      {headless: false}
    );
    const page = await browser.newPage();
  
    // Navigate the page to a URL
    await page.goto('https://trader.tradovate.com/welcome');
  
    // Set screen size
    await page.setViewport({width: 1080, height: 1024});
  
    // Type into search box
    await page.type('#name-input', 'XXXXX');
    await page.type('#password-input', 'XXXXX');
  
  
    const login = '.MuiButtonBase-root.MuiButton-root';
    //const login = '#onboarding-view-div > div.MuiContainer-root.onboarding-content.main-container.MuiContainer-maxWidthLg > div > div.mb-60 > div > div:nth-child(1) > div > form > button';
    await page.waitForSelector(login);
    await page.click(login);
    //await page.keyboard.press('Enter');
    await page.waitForNavigation({
      waitUntil: "networkidle0",
    })
  
    await page.click("#tm_options > div:nth-child(2) > div > div:nth-child(2) > button");
  

    console.log( 'Awaiting...' )
    app.post( '/', async function( req, res )  {
      if (req.body.strategy.order_action == 'buy')
      {
          await page.click("#content > div > div.app-modules > div > div.gm-scroll-view > div > div > div > div:nth-child(1) > div:nth-child(1) > div.lm_items > div > div > div > div.module.chart.chart-wrapper > div.header > div > div > div.gm-scroll-view > div:nth-child(6) > div:nth-child(2) > div:nth-child(1) > div")
          await page.click("#none > div.popover-content > div.btn-group-justified.btn-group > div:nth-child(1) > div")
          console.log('Buy initiated!')
      }
      else if (req.body.strategy.order_action == 'sell')
      {
          await page.click("#content > div > div.app-modules > div > div.gm-scroll-view > div > div > div > div:nth-child(1) > div:nth-child(1) > div.lm_items > div > div > div > div.module.chart.chart-wrapper > div.header > div > div > div.gm-scroll-view > div:nth-child(6) > div:nth-child(2) > div:nth-child(2) > div")
          await page.click( "#none > div.popover-content > div.btn-group-justified.btn-group > div:nth-child(1) > div" )
          console.log('Sell initiated!')
      }
    
    
        res.sendStatus( 200 );
    } );
    
    app.listen( 5000, () => console.log( 'Node.js server started on port 5000.' ) );

  })();

可能的问题及解决方法

1. 路由注册时机不合理

你把app.post路由注册放在了Puppeteer的异步启动函数内部,虽然能运行,但容易导致上下文混乱,也不符合Express的常规写法。建议把路由注册移到外层,只将page变量暴露给路由处理函数。

2. DOM选择器过于脆弱

你使用的是超长的绝对路径选择器,只要页面结构有微小变动(比如元素位置调整、类名修改)就会失效。解决办法:

  • 优先使用平台提供的语义化选择器,比如元素的data-*属性、特定功能类名
  • 在点击前先等待选择器加载完成,避免元素未就绪就执行操作

3. 异步操作未做错误捕获

路由里的Puppeteer操作是异步的,但如果没有错误捕获逻辑,报错会被直接吞掉,导致你看不到问题所在。必须加上try/catch块来捕获并打印错误。

4. 请求体解析验证

虽然你用了app.use(express.json()),但要确保Insomnia发送请求时的Content-Type头是application/json,否则req.body会是undefined,导致判断条件不成立。可以在路由开头打印req.body确认数据是否正确接收。


修改后的完整代码示例

import puppeteer from 'puppeteer';
import express from 'express';

const app = express();
app.use(express.json());

let page; // 外层定义page变量,供路由调用

// 初始化Puppeteer
(async () => {
  const browser = await puppeteer.launch({ headless: false });
  page = await browser.newPage();

  await page.setViewport({ width: 1080, height: 1024 });
  await page.goto('https://trader.tradovate.com/welcome');

  // 登录操作
  await page.type('#name-input', 'XXXXX');
  await page.type('#password-input', 'XXXXX');

  const loginSelector = '.MuiButtonBase-root.MuiButton-root';
  await page.waitForSelector(loginSelector);
  await page.click(loginSelector);
  await page.waitForNavigation({ waitUntil: "networkidle0" });

  await page.click("#tm_options > div:nth-child(2) > div > div:nth-child(2) > button");

  console.log('Awaiting...');
})();

// 注册Webhook路由
app.post('/', async function(req, res) {
  try {
    console.log('收到请求数据:', req.body);
    
    // 检查Puppeteer页面是否初始化完成
    if (!page) {
      console.error('Puppeteer页面未初始化');
      return res.sendStatus(503);
    }

    const orderAction = req.body.strategy?.order_action;
    if (!orderAction) {
      console.error('请求中缺少order_action字段');
      return res.sendStatus(400);
    }

    if (orderAction === 'buy') {
      // 等待买入按钮加载完成再点击
      const buyBtnSelector = "#content > div > div.app-modules > div > div.gm-scroll-view > div > div > div > div:nth-child(1) > div:nth-child(1) > div.lm_items > div > div > div > div.module.chart.chart-wrapper > div.header > div > div > div.gm-scroll-view > div:nth-child(6) > div:nth-child(2) > div:nth-child(1) > div";
      await page.waitForSelector(buyBtnSelector, { timeout: 5000 });
      await page.click(buyBtnSelector);
      
      // 等待确认按钮加载完成
      const confirmBuySelector = "#none > div.popover-content > div.btn-group-justified.btn-group > div:nth-child(1) > div";
      await page.waitForSelector(confirmBuySelector, { timeout: 5000 });
      await page.click(confirmBuySelector);
      
      console.log('Buy initiated!');
    } else if (orderAction === 'sell') {
      const sellBtnSelector = "#content > div > div.app-modules > div > div.gm-scroll-view > div > div > div > div:nth-child(1) > div:nth-child(1) > div.lm_items > div > div > div > div.module.chart.chart-wrapper > div.header > div > div > div.gm-scroll-view > div:nth-child(6) > div:nth-child(2) > div:nth-child(2) > div";
      await page.waitForSelector(sellBtnSelector, { timeout: 5000 });
      await page.click(sellBtnSelector);
      
      const confirmSellSelector = "#none > div.popover-content > div.btn-group-justified.btn-group > div:nth-child(1) > div";
      await page.waitForSelector(confirmSellSelector, { timeout: 5000 });
      await page.click(confirmSellSelector);
      
      console.log('Sell initiated!');
    } else {
      console.error('未知的order_action:', orderAction);
      return res.sendStatus(400);
    }

    res.sendStatus(200);
  } catch (err) {
    console.error('操作执行失败:', err);
    res.sendStatus(500);
  }
});

app.listen(5000, () => console.log('Node.js server started on port 5000.'));

内容的提问来源于stack exchange,提问作者IscA

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 14:53:09