天天看點

使用puppeteer進入網站模拟登陸然後抓取特定網頁内容

puppeteer爬蟲

目的:使用puppeteer進入網站模拟登陸然後抓取特定網頁内容

開發環境windows、npm、node

puppeteer文檔位址

  • puppeteer.launch()啟動 Chromium 執行個體
    const puppeteer = require('puppeteer'); //頭部引入puppeteer包
     const browser = await puppeteer.launch({//傳回一個浏覽器執行個體
          headless: true, //true 時不打開浏覽器false時打開浏覽器可以觀察運作情況
          slowMo: 20
      })
               
  • browser.newPage()
  • page.goto(url)
    /*
    *@url 網站 的位址
    */
    await page.goto(url);//打開特定網站
               
  • page.evaluate(pageFunction[, …args])
    • pageFunction <function|string> 要在頁面執行個體上下文中執行的方法
    • …args <…Serializable|JSHandle> 要傳給 pageFunction 的參數
    • 傳回: <Promise> pageFunction執行的結果
    我沒有使用這個方法處理頁面的DOM而是使用的 jsdom包處理的頁面DOM操作
  • page.click(selector[, options])
    • selector 要點選的元素的選擇器。 如果有多個比對的元素, 點選第一個。
    • options
      • button left, right, 或者 middle, 預設是 left。
      • clickCount 預設是 1. 檢視 UIEvent.detail。
      • delay mousedown 和 mouseup 之間停留的時間,機關是毫秒。預設是0
    • 傳回: Promise對象,比對的元素被點選。 如果沒有元素被點選,Promise對象将被rejected。

    此方法找到一個比對 selector 選擇器的元素,如果需要會把此元素滾動到可視,然後通過 page.mouse 點選它。 如果選擇器沒有比對任何元素,此方法将會報錯。

    要注意如果 click() 觸發了一個跳轉,會有一個獨立的 page.waitForNavigation() Promise對象需要等待。 正确的等待點選後的跳轉是這樣的:

    await page.click('#form-out a.hover'),
    // 當點選的是跳轉連結時
    await Promise.all([
      page.click('#denglu'),
      page.waitForNavigation()
    ]);
               
    根據我實際的測試該方法會經常報錯可以使用page.waitFor()進行跳轉等待
  • page.waitFor()
    /*
     *@time等待時間
     */
     await page.waitFor(time);
               
  • browser.pages()
    浏覽器打開時會首先打來一個空白視窗是以視窗數會算上初始化的空白視窗
  • page.waitForSelector(selector[, options])
    • selector 要等待的元素選擇器
    • options 可選參數:
      • visible 等元素出現在dom中并且可以看到, 比如。 沒有 display: none 或者 visibility: hidden 樣式。 預設是 false。
      • hidden 等元素在dom中消失或看不到, 比如。 有 display: none 或者 visibility: hidden 樣式。 預設是 false。
      • timeout 最大等待時間,機關是毫秒,預設是30000 (30 seconds),傳0表示不會逾時
    • 傳回: <Promise> Promise對象,當指定選擇器比對的元素添加到dom中時resolve
    此處我操作的是第三個浏覽器視窗是以使用pages[2]
  • page.type(selector, text[, options])
  • page.type(selector, text[, options])
  • selector (string) 要輸入内容的元素選擇器。如果有多個比對的元素,輸入到第一個比對的元素。
  • text (string) 要輸入的内容
  • options (Object)

    + delay (number) 每個字元輸入的延遲,機關是毫秒。預設是 0。

  • 傳回: Promise
    /*
    *@userName使用者名
    *@pwd密碼
    */
    await page.type('#login_string',userName)
    await page.type('#login_pass',pwd)
               
  • page.waitForNavigation()
    當操作單頁面應用時該方法會一直抛錯(等待逾時)是以我用的page.waitFor() 方法跳轉等待
  • page.content()
    • 傳回頁面的完整 html 代碼,包括 doctype
    const { JSDOM } = require("jsdom"); //安裝jsdom頭部引入
    let html=await pages[2].content();
    const {document} = new JSDOM(`${html}`).window;
    //原生JS操作DOM對象
    document.body.querySelectorAll('.xkzy_zy_kuang .xkzy_zy_info:nth-of-type(1) .xkzy_zy_xiangqing>div').forEach(item=>{
             console.log(item)
      }
    );
               
  • browser.process()
    • 傳回目前打開浏覽器的程序然後殺掉程序釋放記憶體

全部代碼

const puppeteer = require('puppeteer');
const { JSDOM } = require("jsdom");
const http = require('http');
const Back=data=>JSON.stringify({
  code:200,
  data:data
});
const ErrBack=data=>JSON.stringify({
  code:data.code,
  data:data.msg
});
const Url=str=>{
  let temp=str.split('&')
  return {
      userName:temp[0].split('=')[1],
      pwd:temp[1].split('=')[1]
  }
}
const main=async (userObj={},res)=>{
  if (!userObj.userName||!userObj.pwd) {
      res.end(ErrBack({
        code:403,
        msg:'參數缺失'
      }));
  }
  const browser = await puppeteer.launch({
        headless: true,
        slowMo: 20
  })
  console.log(browser.process().pid);
  const page = await browser.newPage();

  await page.goto('url位址');

  await page.type('#login_string',userObj.userName)
  await page.type('#login_pass',userObj.pwd)

  await Promise.all([
    page.click('#denglu'),
    page.waitForNavigation()
  ]);

  await page.waitForSelector('#form-out a.hover');
  await page.click('#form-out a.hover'),

  await page.waitFor(2000)
  let pages=await browser.pages()
  await pages[2].waitForSelector('.header-item');
  await pages[2].click('.header-item:nth-child(2) a');

  await pages[2].waitForSelector('.xkzy_zy_info');
  let html=await pages[2].content();
  const {document} = new JSDOM(`${html}`).window;
  let list=[];
  document.body.querySelectorAll('.xkzy_zy_kuang .xkzy_zy_info:nth-of-type(1) .xkzy_zy_xiangqing>div').forEach(item=>{
           list.push({
            title:item.children[0].textContent.replace(/\s+/g,""),
            content:item.children[1].textContent.replace(/\s+/g,""),
            file:item.children[2].textContent.replace(/\s+/g,"")
          })
    }
  );

  process.kill(browser.process().pid);
  res.end(Back(list));

}
//設定主機名
var hostName = 'ip';
//設定端口
var port = 8177;
//建立服務
var server = http.createServer(function(req,res){
  if(req.url == "/favicon.ico")return;
  res.setHeader('Access-Control-Allow-Origin',"*")
  res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  res.setHeader("Content-Type", "application/json;charset=utf-8");

  try {
    main(Url(req.url),res);
  } catch (e) {
    console.log(e);
    res.end(ErrBack({
      code:403,
      msg:'抓取出錯'
    }));
  }

});
server.listen(port,hostName,function(){
  console.log(`伺服器運作在http://${hostName}:${port}`);
});
// process.on('uncaughtException', err=>console.log(err));

           

繼續閱讀