[ Day 04 ]用 Puppeteer 來做自動化機器人吧 (三) : 應用篇


上次做了一些基礎的講解後,這次就來利用 Puppeteer 實作一個自動化機器人吧!
這次要做的應用是在 LINE 官方帳號的貼文串自動排程及發文,由於 LINE 官方帳號的貼文串功能並無提供 API,剛好能用 Puppeteer 來實作。

先附上 GitHub 連結:https://github.com/AlanSyue/line-timeline-bot

簡單說明一下實作的過程,主要分成 6 個步驟:

  1. 登入 ( 輸入帳號、密碼 )
  2. 跳轉至建立貼文頁面
  3. 輸入預約貼文的指定日期時間
  4. 上傳照片
  5. 撰寫貼文
  6. 按下預約按鈕

大部分的步驟上篇的基礎應用都有說過,這次主要講解兩個較特殊的步驟:

輸入預約貼文的指定日期時間

這裡我是用比較特殊的方法,將日期跟時間拆成一個一個存在 Array,然後用 Puppeteer 模擬鍵盤輸入一個一個打上去。我有試過用 evaluate 這個 API 讓他直接賦值到 date picker,不過不確定為什麼失敗,有可能是那時給的日期是過去的時間,date picker 會抓不到。後來試了直接一個一個輸入就成功了。

上傳照片

這裡我是用 fileChooser 這個 api,他的使用方式是必須先幫他點開到出現檔案選擇器( 下圖 ),然後提供檔案路徑 ( 放在 Array 裡面,如果有多個就會一次上傳多個 ),這邊主要踩到的一個雷是 Chromium 的版本,造成無法上傳,有興趣可以參考 Puppeteer 使用 FileChooser 無法上傳檔案 | 開發經驗分享

總結

這邊附上完整的程式碼,有興趣可以拿回去使用看看,就可以做出第一隻自動化的機器人,節省重複的工作,那我們下篇再見囉,謝謝!

const puppeteer = require('puppeteer');

(async () => {
  const user = {
    "email" : `${line email}`, // LINE 登入帳號
    "password" : `${line password}` // LINE 登入密碼
    "lineId"   : `${line ID}` // LINE 官方帳號 id
  }

  const postData = {
    "imagePath" : `${imgPathArray}`, // 圖檔路徑 ( 需下載下來才能上傳 ) 格式為 Array
    "date" : `2020/10/20`, // 預約發文日期
    "time" : `10:20`, // 預約發文時間
    "content" : "testesteewewefwfwfewfewf" // 文字內容
  }

  // 開啟 browser
  const browser = await puppeteer.launch({
        headless: false
  });
  // 新增分頁
  const page = await browser.newPage();
  // 監聽 dialog 事件
  page.on('dialog', async dialog => {
    await dialog.accept();
  });

  await page.goto(`https://manager.line.biz/account/${user.lineId}`);

  await page.click('input[type="submit"]');

  await page.waitFor(2000); // stop 2s prevent too fast to redirect
  // login
  await page.waitForSelector('input[name="tid"]')
  await page.type("input[name='tid']", user.email)
  await page.waitFor(4);
  await page.waitForSelector('input[name="tpasswd"]')
  await page.type('input[name="tpasswd"]', user.password)
  await page.waitFor(2000);
  await page.click('button[type="submit"]')
  await page.waitFor(5000);
  await page.goto(`https://manager.line.biz/account/${user.lineId}/timeline/create`);
  await page.waitForSelector("i[class='las la-image fa-3x']", {timeout:8000});

  // 輸入日期與時間
  await page.waitForSelector('input[name="datepicker"]')
  await page.click("input[name='datepicker']");
  await keyInDate(page, postData.date);
  await page.click("input[name='timepicker']");
  await keyInTime(page, postData.time)

  // 上傳照片
  await page.click("i[class='las la-image fa-3x']");
  await page.waitForSelector('label[class="custom-file-label text-left cursor-pointer"]')
  await uploadImage(postData.imagePath, page);

  // 建立文字
  await page.waitForSelector("div[class='form-control editor']");
  await page.type("div[class='form-control editor']", postData.content);
  console.log("type success");

  // 預約貼文
  await page.waitFor(2000);
  await page.click("button[class='btn btn-lg btn-primary px-5 mx-1']"); // 點擊貼文按鈕
  await page.waitForSelector("button[class='btn btn-lg rounded-0 flex-1 btn-primary']");
  await page.click("button[class='btn btn-lg rounded-0 flex-1 btn-primary']"); // 點擊預約按鈕

  browser.close();
  process.exit();
})();

// use fileChooser to upload images
const uploadImage = async (images, page) => {
  const [fileChooser] = await Promise.all([
    page.waitForFileChooser(),
    page.click('label[class="custom-file-label text-left cursor-pointer"]'),
  ]);

  await fileChooser.accept(images);
}

// key in post date in datepicker
const keyInDate = async (page, date) => {
  date.split("").forEach(async (number, index) => {
    await page.keyboard.press(number);
    await page.keyboard.up(number);
    await page.waitFor(5);
  })
}

// key in post time in timepicker
const keyInTime = async (page, time) => {
  time.split("").forEach(async (number, index) => {
    await page.keyboard.press(number);
    await page.keyboard.up(number);
    await page.waitFor(5);
  })
}
#node.js #Puppeteer







你可能感興趣的文章

Linkedin Java 認證題庫

Linkedin Java 認證題庫

[ HTML  / CSS ] - 表單的各種設定

[ HTML / CSS ] - 表單的各種設定

2 專案作品 - Vite建立React專案及安裝套件

2 專案作品 - Vite建立React專案及安裝套件






留言討論