天天看點

puppeteer搭建代理轉發請求

使用方法

tnpm install -g @ali/tuzki
tuzki set -a 你的賬号
tuzki set -p 你的密碼
tuzki start           

tuzki set

-a --account ,設定域賬号

-p --passowrd ,設定域賬号密碼

tuzki config

輸出目前配置的域賬号和密碼

tuzki start

運作代理,預設是 5000 端口,如果你在 start 後指定了一個端口則會在指定端口運作

tuzki start -p 9000 // 在9000端口運作           

開發過程

koa

Koa 是一個新的 web 架構,由 Express 幕後的原班人馬打造, 緻力于成為 web 應用和 API 開發領域中的一個更小、更富有表現力、更健壯的基石。 通過利用 async 函數,Koa 幫你丢棄回調函數,并有力地增強錯誤處理。 Koa 并沒有捆綁任何中間件, 而是提供了一套優雅的方法,幫助您快速而愉快地編寫服務端應用程式。

當然,這段話是我抄的。

https://koa.bootcss.com/

puppeteer

Puppeteer是一個Node庫,它提供了進階API來通過

DevTools協定

控制Chrome或Chromium 。

當然,這句話也是我抄的。

https://github.com/puppeteer/puppeteer

。簡單的說就是Puppeteer提供了一個可供node環境使用的一個無界面、代碼控制的谷歌浏覽器。可以通過調用api來實作一個使用者可能有的所有操作。說實話我用完以後覺得這個拿來當爬蟲用真的杠杠的。。

方案

基本是圍繞着怎麼生成cookie,怎麼請求接口進行。

讓puppeteer代替使用者登陸平台,請求接口,拿到響應以後再用koa響應本地的前端請求。emm,大概就是下面這條線。

puppeteer搭建代理轉發請求

代碼實作

代碼實作思路大緻分兩部分,第一部分是讓koa去識别和響應前端請求,第二部分是puppeteer收到koa拆解出協定和參數後,收發請求。

koa部分

用koa生成一個http server,開始監聽某個端口的請求。這裡使用了koa-log4幫助記錄日志。

const Koa = require("koa");
const log4js = require("koa-log4");
const logger = log4js.getLogger("app");
const { PORT } = require("./config");
require("../log");
const bodyParser = require("koa-bodyparser");
const { instance: lubanProxy } = require("./lubanProxy");
async function server() {
  // init app & proxy
  const app = new Koa();
  app.use(bodyParser());
  app.use(log4js.koaLogger(log4js.getLogger("http"), { level: "auto" }));
  await lubanProxy.loginLuban();
  // add logger to a request
  app.use(async (ctx, next) => {
    const start = new Date();
    await next();
    const ms = new Date() - start;
    logger.info(`${start} ${ctx.method} ${ctx.url} - ${ms}ms`);
  });
  // send request to lb-test
  app.use(async ctx => {
    if (ctx.method === "GET") {
      let res = await lubanProxy.get(ctx.url);
      ctx.body = res;
    } else if (ctx.method === "POST") {
      let res = await lubanProxy.post(ctx.url, ctx.request.body);
      ctx.body = res;
    }
  });
  app.on("error", (err, ctx) => {
    logger.error("server error", err, ctx);
  });
  app.listen(PORT);
}
server();           

puppeteer部分

生成一個浏覽器,登陸平台,向koa提供get和post方法。

登陸部分

模拟真實使用者的登陸操作即可。

loginLuban = async () => {
  this.browser = await puppeteer.launch();
  const page = await this.browser.newPage();
  await page
    .goto(
      "https://login.abc.com/login.html“
    )
    .catch(async e => {
      await page.waitFor(2000);
      logger.error(`load login page timeout,retrying...`);
    });
  await page
    .evaluate(() => {
      document.querySelector("input[name='account']").focus();
    })
    .catch(async _ => {
      logger.error(`Cannot get login form`);
    });
  await page.keyboard.type(ACCOUNT);
  await page
    .evaluate(() => {
      document.querySelector("input[name='password']").focus();
    })
    .catch(async _ => {
      logger.error(`Cannot get login form`);
    });
  await page.keyboard.type(PASSWORD);
  await Promise.all([page.waitForNavigation(), page.click(".submit")])
    .then(async () => {
      await page.goto("http://luban.abc.net/manage.htm");
      console.log("proxy ready...");
    })
    .catch(async _ => {
      logger.error(
        `Navigation timeout of 30000 ms exceeded,restarting browser...`
      );
      await this.restartBrowser();
    });
};           

get請求

puppeteer隻能用來模拟使用者操作,無法自如的發送ajax,是以get請求的思路是用page.goto來模拟。

get = async url => {
  const page = await this.browser.newPage();
  let res = "";
  await page
    .goto("http://luban.abc.net" + url)
    .then(async () => {
      res = await page.content();
      res = res.slice(res.indexOf("{"), res.lastIndexOf("}") + 1);
    })
    .catch(_ => {
      logger.error(`get request got net::ERR_CONNECTION_RESET error`);
      res = {
        code: -1,
        message: "Got a net error, try again?"
      };
    });
  await page.close();
  return res;
};           

post請求

post請求比get複雜一些,因為get可以通過頁面跳轉,而post無法模拟。

/**
   * 用注入一段js的方式發送post請求
   * 打開lb-test jarvis的index頁面,同時注入一段js代碼,将參數填充進js代碼段中,
   * 将接口傳回的内容寫入div#root的innerHTML中,wait()接口請求時間後,擷取div#root的innerHTML即為接口傳回内容。
   * 為每個請求單獨開辟一個标簽頁,防止上一個請求沒結束時即被新的請求覆寫,
   * 當請求結束時主動關閉标簽頁,釋放占用的記憶體
   * 若此過程成功,則傳回json字元串,若失敗,傳回失敗原因
   * @param {string} url 請求的位址
   * @param {object} param 請求的參數
   * @return {string} json字元串
   */
  post = async (url, param) => {
    const page = await this.browser.newPage();
    let res = "";
    await page.goto(
      "http://lb-test.alibaba.net/project/jarvis/page/index.html"
    );
    await page.addScriptTag({
      content: `
      $.post('http://lb-test.alibaba.net'+'${url}', {data: ${JSON.stringify(
        param.data
      )}}, function (result) {
        document.querySelector('#root').innerHTML  = JSON.stringify(result)
      });`
    });
    await page.waitFor(500);
    res = await page.evaluate(() => {
      return document.querySelector("#root").innerHTML;
    });
    await page.close();
    return res;
  };           

繼續閱讀