Node爬蟲相關(guān)

網(wǎng)絡爬蟲開發(fā)

第1章 課程介紹

  • 什么是爬蟲
  • 爬蟲的意義
  • 課程內(nèi)容
  • 前置知識

什么是爬蟲

可以把互聯(lián)網(wǎng)比做成一張“大網(wǎng)”,爬蟲就是在這張大網(wǎng)上不斷爬取信息的程序

所以一句話總結(jié):爬蟲是請求網(wǎng)站并提取數(shù)據(jù)的自動化程序

爬蟲的基本工作流程如下:

  1. 向指定的URL發(fā)送http請求
  2. 獲取響應(HTML旨枯、XML蹬昌、JSON、二進制等數(shù)據(jù))
  3. 處理數(shù)據(jù)(解析DOM攀隔、解析JSON等)
  4. 將處理好的數(shù)據(jù)進行存儲
timg.jpg

爬蟲的意義

爬蟲就是一個探測程序皂贩,它的基本功能就是模擬人的行為去各個網(wǎng)站轉(zhuǎn)悠栖榨,點點按鈕,找找數(shù)據(jù)明刷,或者把看到的信息背回來婴栽。就像一只蟲子在一幢樓里不知疲倦地爬來爬去。

你可以簡單地想象:每個爬蟲都是你的“分身”辈末。就像孫悟空拔了一撮汗毛愚争,吹出一堆猴子一樣。

你每天使用的百度和Google本冲,其實就是利用了這種爬蟲技術(shù):每天放出無數(shù)爬蟲到各個網(wǎng)站准脂,把他們的信息抓回來,存到數(shù)據(jù)庫中等你來檢索檬洞。

timg-1562841931899.jpg

搶票軟件狸膏,就相當于撒出去無數(shù)個分身,每一個分身都幫助你不斷刷新 12306 網(wǎng)站的火車余票添怔。一旦發(fā)現(xiàn)有票湾戳,就馬上下單,然后對你喊:大爺快來付款呀广料。

在現(xiàn)實中幾乎所有行業(yè)的網(wǎng)站都會被爬蟲所 “騷擾”砾脑,而這些騷擾都是為了方便用戶

1562640698673.png

當然,有些網(wǎng)站是不能被過分騷擾的艾杏,其中排第一的就是出行類行業(yè)韧衣。

12306之所以會出如此變態(tài)的驗證碼,就是因為被爬蟲折磨的無可奈何

timg-1562641037874.jpg
timg-31892762387168.jpg

正所謂道高一尺魔高一丈购桑,某些爬蟲工具畅铭,為了解決這種變態(tài)驗證碼,甚至推出了“打碼平臺”

原理就是爬蟲還是不斷工作勃蜘,但只要遇到二維碼硕噩,就通過打碼平臺下發(fā)任務,打碼平臺另一邊就雇傭一大堆網(wǎng)絡閑人缭贡,只要看到有驗證碼來了炉擅,就人工選一下驗證碼,完美的讓程序與人工結(jié)合阳惹!

課程內(nèi)容及目標

  1. 爬蟲簡介
  2. 制作一個自動下載圖片的小爬蟲
  3. 使用Selenium爬取動態(tài)網(wǎng)站

前置知識

  1. js基礎(chǔ)
  2. node基礎(chǔ)

第2章 爬蟲基礎(chǔ)

學習目標:

  • http://web.itheima.com/teacher.html網(wǎng)站目標為例谍失,最終目的是下載網(wǎng)站中所有老師的照片:

下載所有老師的照片,需要通過如下步驟實現(xiàn):

  1. 發(fā)送http請求莹汤,獲取整個網(wǎng)頁內(nèi)容
  2. 通過cheerio庫對網(wǎng)頁內(nèi)容進行分析
  3. 提取img標簽的src屬性
  4. 使用download庫進行批量圖片下載

發(fā)送一個HTTP請求

學習目標:

  • 發(fā)送HTTP請求并獲取相應

在學習爬蟲之前快鱼,需要對HTTP請求充分了解,因為爬蟲的原理就是發(fā)送請求到指定URL,獲取響應后并處理

node官方api

node的核心模塊 http模塊即可發(fā)送請求攒巍,摘自node官網(wǎng)api:

1562842951982.png

由此可見只需要使用http.request()方法即可發(fā)送http請求

發(fā)送http請求案例

同學們也可以使用axios庫來代替

代碼如下:

// 引入http模塊
const http = require('http')
// 創(chuàng)建請求對象
let req = http.request('http://web.itheima.com/teacher.html', res => {
  // 準備chunks
  let chunks = []
  res.on('data', chunk => {
    // 監(jiān)聽到數(shù)據(jù)就存儲
    chunks.push(chunk)
  })
  res.on('end', () => {
    // 結(jié)束數(shù)據(jù)監(jiān)聽時講所有內(nèi)容拼接
    console.log(Buffer.concat(chunks).toString('utf-8'))
  })
})
// 發(fā)送請求
req.end()

得到的結(jié)果就是整個HTML網(wǎng)頁內(nèi)容

將獲取的HTML字符串使用cheerio解析

學習目標:

  • 使用cheerio加載HTML
  • 回顧jQueryAPI
  • 加載所有的img標簽的src屬性

cheerio庫簡介

1562843968162.png

這是一個核心api按照jquery來設(shè)計,專門在服務器上使用荒勇,一個微小柒莉、快速和優(yōu)雅的實現(xiàn)

簡而言之,就是可以再服務器上用這個庫來解析HTML代碼沽翔,并且可以直接使用和jQuery一樣的api

官方demo如下:

const cheerio = require('cheerio')
const $ = cheerio.load('<h2 class="title">Hello world</h2>')
 
$('h2.title').text('Hello there!')
$('h2').addClass('welcome')
 
$.html()
//=> <html><head></head><body><h2 class="title welcome">Hello there!</h2></body></html>

同樣也可以通過jQuery的api來獲取DOM元素中的屬性和內(nèi)容

使用cheerio庫解析HTML

  1. 分析網(wǎng)頁中所有img標簽所在結(jié)構(gòu)
1562844621019.png
  1. 使用jQuery API獲取所有img的src屬性
const http = require('http')
const cheerio = require('cheerio')
let req = http.request('http://web.itheima.com/teacher.html', res => {
  let chunks = []
  res.on('data', chunk => {
    chunks.push(chunk)
  })
  res.on('end', () => {
    // console.log(Buffer.concat(chunks).toString('utf-8'))
    let html = Buffer.concat(chunks).toString('utf-8')
    let $ = cheerio.load(html)
    let imgArr = Array.prototype.map.call($('.tea_main .tea_con .li_img > img'), (item) => 'http://web.itheima.com/' + $(item).attr('src'))
    console.log(imgArr)
    // let imgArr = []
    // $('.tea_main .tea_con .li_img > img').each((i, item) => {
    //   let imgPath = 'http://web.itheima.com/' + $(item).attr('src')
    //   imgArr.push(imgPath)
    // })
    // console.log(imgArr)
  })
})

req.end()

使用download庫批量下載圖片

const http = require('http')
const cheerio = require('cheerio')
const download = require('download')
let req = http.request('http://web.itheima.com/teacher.html', res => {
  let chunks = []
  res.on('data', chunk => {
    chunks.push(chunk)
  })
  res.on('end', () => {
    // console.log(Buffer.concat(chunks).toString('utf-8'))
    let html = Buffer.concat(chunks).toString('utf-8')
    let $ = cheerio.load(html)
    let imgArr = Array.prototype.map.call($('.tea_main .tea_con .li_img > img'), (item) => encodeURI('http://web.itheima.com/' + $(item).attr('src')))
    // console.log(imgArr)

    Promise.all(imgArr.map(x => download(x, 'dist'))).then(() => {
      console.log('files downloaded!');
    });
  })
})

req.end()

注意事項:如有中文文件名兢孝,需要使用base64編碼

爬取新聞信息

爬取目標:http://www.itcast.cn/newsvideo/newslist.html

1563271100064.png

大部分新聞網(wǎng)站,現(xiàn)在都采取前后端分離的方式仅偎,也就是前端頁面先寫好模板跨蟹,等網(wǎng)頁加載完畢后,發(fā)送Ajax再獲取數(shù)據(jù)橘沥,將其渲染到模板中窗轩。所以如果使用相同方式來獲取目標網(wǎng)站的HTML頁面,請求到的只是模板座咆,并不會有數(shù)據(jù):

1563271746307.png
1563271689010.png

此時痢艺,如果還希望使用當前方法爬取數(shù)據(jù),就需要分析該網(wǎng)站的ajax請求是如何發(fā)送的介陶,可以打開network面板來調(diào)試:

1563271869472.png

分析得出對應的ajax請求后堤舒,找到其URL,向其發(fā)送請求即可

1563271962550.png

代碼如下:

// 引入http模塊
const http = require('http')

// 創(chuàng)建請求對象 (此時未發(fā)送http請求)
const url = 'http://www.itcast.cn/news/json/f1f5ccee-1158-49a6-b7c4-f0bf40d5161a.json'
let req = http.request(url, res => {
  // 異步的響應
  // console.log(res)
  let chunks = []
  // 監(jiān)聽data事件,獲取傳遞過來的數(shù)據(jù)片段
  // 拼接數(shù)據(jù)片段
  res.on('data', c => chunks.push(c))

  // 監(jiān)聽end事件,獲取數(shù)據(jù)完畢時觸發(fā)
  res.on('end', () => {
    // 拼接所有的chunk,并轉(zhuǎn)換成字符串 ==> html字符串
    // console.log(Buffer.concat(chunks).toString('utf-8'))
    let result = Buffer.concat(chunks).toString('utf-8')
    console.log(JSON.parse(result))
  })
})

// 將請求發(fā)出去
req.end()

如果遇到請求限制哺呜,還可以模擬真實瀏覽器的請求頭:

// 引入http模塊
const http = require('http')
const cheerio = require('cheerio')
const download = require('download')

// 創(chuàng)建請求對象 (此時未發(fā)送http請求)
const url = 'http://www.itcast.cn/news/json/f1f5ccee-1158-49a6-b7c4-f0bf40d5161a.json'
let req = http.request(url, {
  headers: {
    "Host": "www.itcast.cn",
    "Connection": "keep-alive",
    "Content-Length": "0",
    "Accept": "*/*",
    "Origin": "http://www.itcast.cn",
    "X-Requested-With": "XMLHttpRequest",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",
    "DNT": "1",
    "Referer": "http://www.itcast.cn/newsvideo/newslist.html",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
    "Cookie": "UM_distinctid=16b8a0c1ea534c-0c311b256ffee7-e343166-240000-16b8a0c1ea689c; bad_idb2f10070-624e-11e8-917f-9fb8db4dc43c=8e1dcca1-9692-11e9-97fb-e5908bcaecf8; parent_qimo_sid_b2f10070-624e-11e8-917f-9fb8db4dc43c=921b3900-9692-11e9-9a47-855e632e21e7; CNZZDATA1277769855=1043056636-1562825067-null%7C1562825067; cid_litiancheng_itcast.cn=TUd3emFUWjBNV2syWVRCdU5XTTRhREZs; PHPSESSID=j3ppafq1dgh2jfg6roc8eeljg2; CNZZDATA4617777=cnzz_eid%3D926291424-1561388898-http%253A%252F%252Fmail.itcast.cn%252F%26ntime%3D1563262791; Hm_lvt_0cb375a2e834821b74efffa6c71ee607=1561389179,1563266246; qimo_seosource_22bdcd10-6250-11e8-917f-9fb8db4dc43c=%E7%AB%99%E5%86%85; qimo_seokeywords_22bdcd10-6250-11e8-917f-9fb8db4dc43c=; href=http%3A%2F%2Fwww.itcast.cn%2F; bad_id22bdcd10-6250-11e8-917f-9fb8db4dc43c=f2f41b71-a7a4-11e9-93cc-9b702389a8cb; nice_id22bdcd10-6250-11e8-917f-9fb8db4dc43c=f2f41b72-a7a4-11e9-93cc-9b702389a8cb; openChat22bdcd10-6250-11e8-917f-9fb8db4dc43c=true; parent_qimo_sid_22bdcd10-6250-11e8-917f-9fb8db4dc43c=fc61e520-a7a4-11e9-94a8-01dabdc2ed41; qimo_seosource_b2f10070-624e-11e8-917f-9fb8db4dc43c=%E7%AB%99%E5%86%85; qimo_seokeywords_b2f10070-624e-11e8-917f-9fb8db4dc43c=; accessId=b2f10070-624e-11e8-917f-9fb8db4dc43c; pageViewNum=2; nice_idb2f10070-624e-11e8-917f-9fb8db4dc43c=20d2a1d1-a7a8-11e9-bc20-e71d1b8e4bb6; openChatb2f10070-624e-11e8-917f-9fb8db4dc43c=true; Hm_lpvt_0cb375a2e834821b74efffa6c71ee607=1563267937"
  }
}, res => {
  // 異步的響應
  // console.log(res)
  let chunks = []
  // 監(jiān)聽data事件,獲取傳遞過來的數(shù)據(jù)片段
  // 拼接數(shù)據(jù)片段
  res.on('data', c => chunks.push(c))

  // 監(jiān)聽end事件,獲取數(shù)據(jù)完畢時觸發(fā)
  res.on('end', () => {
    // 拼接所有的chunk,并轉(zhuǎn)換成字符串 ==> html字符串
    // console.log(Buffer.concat(chunks).toString('utf-8'))
    let result = Buffer.concat(chunks).toString('utf-8')
    console.log(JSON.parse(result))
  })
})

// 將請求發(fā)出去
req.end()

注意:請求頭的內(nèi)容舌缤,可以先通過真正的瀏覽器訪問一次后獲取

封裝爬蟲基礎(chǔ)庫

以上代碼重復的地方非常多,可以考慮以面向?qū)ο蟮乃枷脒M行封裝某残,進一步的提高代碼復用率国撵,為了方便開發(fā),保證代碼規(guī)范驾锰,建議使用TypeScript進行封裝

以下知識點為擴展內(nèi)容卸留,需要對面向?qū)ο蠛蚑ypeScript有一定了解!

執(zhí)行tsc --init初始化項目椭豫,生成ts配置文件

TS配置:

{
  "compilerOptions": {
    /* Basic Options */
    "target": "es2015", 
    "module": "commonjs", 
    "outDir": "./bin", 
    "rootDir": "./src", 
    "strict": true,
    "esModuleInterop": true 
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "**/*.spec.ts"
  ]
}

Spider抽象類:

// 引入http模塊
const http = require('http')
import SpiderOptions from './interfaces/SpiderOptions'

export default abstract class Spider {
  options: SpiderOptions;
  constructor(options: SpiderOptions = { url: '', method: 'get' }) {
    this.options = options
    this.start()
  }
  start(): void {
    // 創(chuàng)建請求對象 (此時未發(fā)送http請求)
    let req = http.request(this.options.url, {
      headers: this.options.headers,
      method: this.options.method
    }, (res: any) => {
      // 異步的響應
      // console.log(res)
      let chunks: any[] = []
      // 監(jiān)聽data事件,獲取傳遞過來的數(shù)據(jù)片段
      // 拼接數(shù)據(jù)片段
      res.on('data', (c: any) => chunks.push(c))

      // 監(jiān)聽end事件,獲取數(shù)據(jù)完畢時觸發(fā)
      res.on('end', () => {
        // 拼接所有的chunk,并轉(zhuǎn)換成字符串 ==> html字符串
        let htmlStr = Buffer.concat(chunks).toString('utf-8')
        this.onCatchHTML(htmlStr)
      })
    })

    // 將請求發(fā)出去
    req.end()

  }
  abstract onCatchHTML(result: string): any
}

export default Spider

SpiderOptions接口:

export default interface SpiderOptions {
  url: string,
  method?: string,
  headers?: object
}

PhotoListSpider類:

import Spider from './Spider'
const cheerio = require('cheerio')
const download = require('download')
export default class PhotoListSpider extends Spider {
  onCatchHTML(result: string) {
    // console.log(result)
    let $ = cheerio.load(result)
    let imgs = Array.prototype.map.call($('.tea_main .tea_con .li_img > img'), item => 'http://web.itheima.com/' + encodeURI($(item).attr('src')))
    Promise.all(imgs.map(x => download(x, 'dist'))).then(() => {
      console.log('files downloaded!');
    });
  }
}

NewsListSpider類:

import Spider from "./Spider";

export default class NewsListSpider extends Spider {
  onCatchHTML(result: string) {
    console.log(JSON.parse(result))
  }
}

測試類:

import Spider from './Spider'
import PhotoListSpider from './PhotoListSpider'
import NewsListSpider from './NewsListSpider'

let spider1: Spider = new PhotoListSpider({
  url: 'http://web.itheima.com/teacher.html'
})

let spider2: Spider = new NewsListSpider({
  url: 'http://www.itcast.cn/news/json/f1f5ccee-1158-49a6-b7c4-f0bf40d5161a.json',
  method: 'post',
  headers: {
    "Host": "www.itcast.cn",
    "Connection": "keep-alive",
    "Content-Length": "0",
    "Accept": "*/*",
    "Origin": "http://www.itcast.cn",
    "X-Requested-With": "XMLHttpRequest",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",
    "DNT": "1",
    "Referer": "http://www.itcast.cn/newsvideo/newslist.html",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
    "Cookie": "UM_distinctid=16b8a0c1ea534c-0c311b256ffee7-e343166-240000-16b8a0c1ea689c; bad_idb2f10070-624e-11e8-917f-9fb8db4dc43c=8e1dcca1-9692-11e9-97fb-e5908bcaecf8; parent_qimo_sid_b2f10070-624e-11e8-917f-9fb8db4dc43c=921b3900-9692-11e9-9a47-855e632e21e7; CNZZDATA1277769855=1043056636-1562825067-null%7C1562825067; cid_litiancheng_itcast.cn=TUd3emFUWjBNV2syWVRCdU5XTTRhREZs; PHPSESSID=j3ppafq1dgh2jfg6roc8eeljg2; CNZZDATA4617777=cnzz_eid%3D926291424-1561388898-http%253A%252F%252Fmail.itcast.cn%252F%26ntime%3D1563262791; Hm_lvt_0cb375a2e834821b74efffa6c71ee607=1561389179,1563266246; qimo_seosource_22bdcd10-6250-11e8-917f-9fb8db4dc43c=%E7%AB%99%E5%86%85; qimo_seokeywords_22bdcd10-6250-11e8-917f-9fb8db4dc43c=; href=http%3A%2F%2Fwww.itcast.cn%2F; bad_id22bdcd10-6250-11e8-917f-9fb8db4dc43c=f2f41b71-a7a4-11e9-93cc-9b702389a8cb; nice_id22bdcd10-6250-11e8-917f-9fb8db4dc43c=f2f41b72-a7a4-11e9-93cc-9b702389a8cb; openChat22bdcd10-6250-11e8-917f-9fb8db4dc43c=true; parent_qimo_sid_22bdcd10-6250-11e8-917f-9fb8db4dc43c=fc61e520-a7a4-11e9-94a8-01dabdc2ed41; qimo_seosource_b2f10070-624e-11e8-917f-9fb8db4dc43c=%E7%AB%99%E5%86%85; qimo_seokeywords_b2f10070-624e-11e8-917f-9fb8db4dc43c=; accessId=b2f10070-624e-11e8-917f-9fb8db4dc43c; pageViewNum=2; nice_idb2f10070-624e-11e8-917f-9fb8db4dc43c=20d2a1d1-a7a8-11e9-bc20-e71d1b8e4bb6; openChatb2f10070-624e-11e8-917f-9fb8db4dc43c=true; Hm_lpvt_0cb375a2e834821b74efffa6c71ee607=1563267937"
  }
})

封裝后耻瑟,如果需要寫新的爬蟲,則可以直接繼承Spider類后赏酥,在測試類中進行測試即可喳整,僅需實現(xiàn)具體的爬蟲類onCatchHTML方法,測試時傳入url和headers即可裸扶。

而且全部爬蟲的父類均為Spider框都,后期管理起來也非常方便!

第3章 爬蟲高級

學習目標:

  • 使用Selenium庫爬取前端渲染的網(wǎng)頁
  • 反反爬蟲技術(shù)

Selenium簡介

官方原文介紹:

Selenium automates browsers. That's it! What you do with that power is entirely up to you. Primarily, it is for automating web applications for testing purposes, but is certainly not limited to just that. Boring web-based administration tasks can (and should!) be automated as well.

Selenium has the support of some of the largest browser vendors who have taken (or are taking) steps to make Selenium a native part of their browser. It is also the core technology in countless other browser automation tools, APIs and frameworks.

百度百科介紹:

Selenium [1] 是一個用于Web應用程序測試的工具呵晨。Selenium測試直接運行在瀏覽器中魏保,就像真正的用戶在操作一樣熬尺。支持的瀏覽器包括IE(7, 8, 9, 10, 11),[Mozilla Firefox](https://baike.baidu.com/item/Mozilla Firefox/3504923)谓罗,Safari粱哼,Google Chrome,Opera等檩咱。這個工具的主要功能包括:測試與瀏覽器的兼容性——測試你的應用程序看是否能夠很好得工作在不同瀏覽器和操作系統(tǒng)之上揭措。測試系統(tǒng)功能——創(chuàng)建回歸測試檢驗軟件功能和用戶需求。支持自動錄制動作和自動生成 .Net刻蚯、Java绊含、Perl等不同語言的測試腳本。

簡單總結(jié):

Selenium是一個Web應用的自動化測試框架炊汹,可以創(chuàng)建回歸測試來檢驗軟件功能和用戶需求躬充,通過框架可以編寫代碼來啟動瀏覽器進行自動化測試,換言之讨便,用于做爬蟲就可以使用代碼啟動瀏覽器麻裳,讓真正的瀏覽器去打開網(wǎng)頁,然后去網(wǎng)頁中獲取想要的信息器钟!從而實現(xiàn)真正意義上無懼反爬蟲手段津坑!

Selenium的基本使用

  1. 根據(jù)平臺下載需要的webdriver

  2. 項目中安裝selenium-webdriver包

  3. 根據(jù)官方文檔寫一個小demo

根據(jù)平臺選擇webdriver

瀏覽器 webdriver
Chrome chromedriver(.exe)
Internet Explorer IEDriverServer.exe
Edge MicrosoftWebDriver.msi
Firefox geckodriver(.exe)
Safari safaridriver

選擇版本和平臺:

1563344131390.png

下載后放入項目根目錄

安裝selenium-webdriver的包

npm i selenium-webdriver

自動打開百度搜索“黑馬程序員“

const { Builder, By, Key, until } = require('selenium-webdriver');

(async function example() {
  let driver = await new Builder().forBrowser('chrome').build();
  // try {
  await driver.get('https://www.baidu.com');
  await driver.findElement(By.id('kw')).sendKeys('黑馬程序員', Key.ENTER);
  console.log(await driver.wait(until.titleIs('黑馬程序員_百度搜索'), 1000))
  // } finally {
  //   await driver.quit();
  // }
})();

使用Selenium實現(xiàn)爬蟲

在使用Selenium實現(xiàn)爬蟲之前,需要搞清楚一個問題:

  • 為什么要用Selenium來做爬蟲傲霸?

了解完后疆瑰,還需要知道,如何實現(xiàn)爬蟲昙啄?

  1. 自動打開拉勾網(wǎng)并搜索"前端"
  2. 獲取所有列表項
  3. 獲取其中想要的信息數(shù)據(jù)

為什么要用Selenium來做爬蟲

目前的大流量網(wǎng)站穆役,都會有些對應的反爬蟲機制

例如在拉勾網(wǎng)上搜索傳智播客:

1563347543424.png

找到對應的ajax請求地址,使用postman來測試數(shù)據(jù):

1563347512632.png

前幾次可能會獲取到數(shù)據(jù)梳凛,但多幾次則會出現(xiàn)操作頻繁請稍后再試的問題

而通過Selenium可以操作瀏覽器耿币,打開某個網(wǎng)址,接下來只需要學習其API韧拒,就能獲取網(wǎng)頁中需要的內(nèi)容了淹接!

反爬蟲技術(shù)只是針對爬蟲的,例如檢查請求頭是否像爬蟲叛溢,檢查IP地址的請求頻率(如果過高則封殺)等手段

而Selenium打開的就是一個自動化測試的瀏覽器塑悼,和用戶正常使用的瀏覽器并無差別,所以再厲害的反爬蟲技術(shù)楷掉,也無法直接把它干掉厢蒜,除非這個網(wǎng)站連普通用戶都想放棄掉(12306曾經(jīng)迫于無奈這樣做過)

Selenium API學習

  • 其實就是npm上面的Documentation的Selenium project的連接
    Selenium project

核心對象:

  • Builder

  • WebDriver

  • WebElement

輔助對象:

  • By
  • Key

Builder

用于構(gòu)建WebDriver對象的構(gòu)造器

 let driver = new webdriver.Builder()
     .forBrowser('chrome')
     .build();

其他API如下:

可以獲取或設(shè)置一些Options

1563349592653.png

如需設(shè)置Chrome的Options,需要先導入Options:

const { Options } = require('selenium-webdriver/chrome');
const options = new Options()
options.addArguments('Cookie=user_trace_token=20191130095945-889e634a-a79b-4b61-9ced-996eca44b107; X_HTTP_TOKEN=7470c50044327b9a2af2946eaad67653; _ga=GA1.2.2111156102.1543543186; _gid=GA1.2.1593040181.1543543186; LGUID=20181130095946-9c90e147-f443-11e8-87e4-525400f775ce; sajssdk_2015_cross_new_user=1; JSESSIONID=ABAAABAAAGGABCB5E0E82B87052ECD8CED0421F1D36020D; index_location_city=%E5%85%A8%E5%9B%BD; Hm_lvt_4233e74dff0ae5bd0a3d81c6ccf756e6=1543543186,1543545866; LGSID=20181130104426-da2fc57f-f449-11e8-87ea-525400f775ce; PRE_UTM=; PRE_HOST=www.cnblogs.com; PRE_SITE=https%3A%2F%2Fwww.cnblogs.com%2F; PRE_LAND=https%3A%2F%2Fwww.lagou.com%2Fjobs%2Flist_%25E5%2589%258D%25E7%25AB%25AF%25E5%25BC%2580%25E5%258F%2591%3Fkd%3D%25E5%2589%258D%25E7%25AB%25AF%25E5%25BC%2580%25E5%258F%2591%26spc%3D1%26pl%3D%26gj%3D%26xl%3D%26yx%3D%26gx%3D%26st%3D%26labelWords%3Dlabel%26lc%3D%26workAddress%3D%26city%3D%25E5%2585%25A8%25E5%259B%25BD%26requestId%3D%26pn%3D1; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%221676257e1bd8cc-060451fc44d124-9393265-2359296-1676257e1be898%22%2C%22%24device_id%22%3A%221676257e1bd8cc-060451fc44d124-9393265-2359296-1676257e1be898%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_referrer%22%3A%22%22%2C%22%24latest_referrer_host%22%3A%22%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%7D%7D; ab_test_random_num=0; _putrc=30FD5A7177A00E45123F89F2B170EADC; login=true; unick=%E5%A4%A9%E6%88%90; hasDeliver=0; gate_login_token=3e9da07186150513b28b29e8e74f485b86439e1fd26fc4939d32ed2660e8421a; _gat=1; SEARCH_ID=334cf2a080f44f2fb42841f473719162; LGRID=20181130110855-45ea2d22-f44d-11e8-87ee-525400f775ce; Hm_lpvt_4233e74dff0ae5bd0a3d81c6ccf756e6=1543547335; TG-TRACK-CODE=search_code')
    .addArguments('user-agent="Mozilla/5.0 (iPod; U; CPU iPhone OS 2_1 like Mac OS X; ja-jp) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5F137 Safari/525.20')

WebDriver

通過構(gòu)造器創(chuàng)建好WebDriver后就可以使用API查找網(wǎng)頁元素和獲取信息了:

  • findElement() 查找元素
1563349913348.png

WebElement

  • getText() 獲取文本內(nèi)容
  • sendKeys() 發(fā)送一些按鍵指令
  • click() 點擊該元素


    1563351143458.png

自動打開拉勾網(wǎng)搜索"前端"

  1. 使用driver打開拉勾網(wǎng)主頁

  2. 找到全國站并點擊一下

  3. 輸入“前端”并回車

const { Builder, By, Key } = require('selenium-webdriver');

(async function start() {
  let driver = await new Builder().forBrowser('chrome').build();
  await driver.get('https://www.lagou.com/');
  await driver.findElement(By.css('#changeCityBox .checkTips .tab.focus')).click();
  await driver.findElement(By.id('search_input')).sendKeys('前端', Key.ENTER);
})();

獲取需要的數(shù)據(jù)

使用driver.findElement()找到所有條目項,根據(jù)需求分析頁面元素斑鸦,獲取其文本內(nèi)容即可:

const { Builder, By, Key } = require('selenium-webdriver');

(async function start() {
  let driver = await new Builder().forBrowser('chrome').build();
  await driver.get('https://www.lagou.com/');
  await driver.findElement(By.css('#changeCityBox .checkTips .tab.focus')).click();
  await driver.findElement(By.id('search_input')).sendKeys('前端', Key.ENTER);
  let items = await driver.findElements(By.className('con_list_item'))
  items.forEach(async item => {
    // 獲取崗位名稱
    let title = await item.findElement(By.css('.position h3')).getText()
    // 獲取工作地點
    let position = await item.findElement(By.css('.position em')).getText()
    // 獲取發(fā)布時間
    let time = await item.findElement(By.css('.position .format-time')).getText()
    // 獲取公司名稱
    let companyName = await item.findElement(By.css('.company .company_name')).getText()
    // 獲取公司所在行業(yè)
    let industry = await item.findElement(By.css('.company .industry')).getText()
    // 獲取薪資待遇
    let money = await item.findElement(By.css('.p_bot .money')).getText()
    // 獲取需求背景
    let background = await item.findElement(By.css('.p_bot .li_b_l')).getText()
    // 處理需求背景
    background = background.replace(money, '')
    console.log(title, position, time, companyName, industry, money, background)
  })
})();

自動翻頁

思路如下:

  1. 定義初始頁碼
  2. 獲取數(shù)據(jù)后愕贡,獲取頁面上的總頁碼,定義最大頁碼
  3. 開始獲取數(shù)據(jù)時打印當前正在獲取的頁碼數(shù)
  4. 獲取完一頁數(shù)據(jù)后巷屿,當前頁碼自增颂鸿,然后判斷是否達到最大頁碼
  5. 查找下一頁按鈕并調(diào)用點擊api,進行自動翻頁
  6. 翻頁后遞歸調(diào)用獲取數(shù)據(jù)的函數(shù)
const { Builder, By, Key, until } = require('selenium-webdriver');
let currentPage = 1
let maxPage;
(async function example() {
    let driver = await new Builder().forBrowser('chrome').build();
    try {
        await driver.get('https://www.lagou.com');
        await driver.findElement(By.css('#changeCityBox .checkTips .tab.focus')).click();
        await driver.findElement(By.id('search_input')).sendKeys('node', Key.RETURN);
        maxPage = await driver.findElement(By.className('totalNum')).getText();
        await getData(driver)
    } finally {

    }
})();

async function getData(driver) {
    //此處while循環(huán)是因為攒庵,例如翻頁的時候,進入下一頁败晴,dom還沒渲染浓冒,就去執(zhí)行操作,反而報錯尖坤,利用此方式解決該問題
    while (true) {
        let flag = true
        try {
            let items = await driver.findElements(By.css('.item_con_list .con_list_item'));
            let results = [];
            for (let i = 0; i < items.length; i++) {
                const item = items[i];
                // console.log(await item.getText());
                let title = await item.findElement(By.css('.position h3')).getText();
                let address = await item.findElement(By.css('.position .add em')).getText();
                let time = await item.findElement(By.css('.position .format-time')).getText();
                let money = await item.findElement(By.css('.position .money')).getText();
                let background = await item.findElement(By.css('.position .li_b_l')).getText();
                background = background.replace(money, '');
                let companyName = await item.findElement(By.css('.company .company_name')).getText();
                let companyLink = await item.findElement(By.css('.company .company_name a')).getAttribute('href');
                let industry = await item.findElement(By.css('.company .industry')).getText();
                let tag = await item.findElement(By.css('.list_item_bot .li_b_l')).getText();
                results.push({
                    title,
                    address,
                    time,
                    money,
                    background,
                    companyName,
                    companyLink,
                    industry,
                    tag
                })

            }
            console.log(results);
            
            currentPage++;
            if (currentPage <= maxPage) {
                //翻頁
                await driver.findElement(By.className('pager_next')).click();
                console.log('asdsa',currentPage);
                //查詢下一頁
                getData(driver);
            }
        } catch (error) {
            if (error) flag = false
            //此處是因為翻頁時候可能頁面數(shù)據(jù)還沒加載出來就查詢稳懒,例如第一頁到第二頁時候就已經(jīng)出錯了
            console.log(error.message);
        } finally {
            if (flag) break
        }
    }
}

第4章 總結(jié)

爬蟲神通廣大,用途非常廣泛慢味,主要的目標是為了實現(xiàn)自動化程序场梆,解放程序員的雙手

幫助程序員自動獲取一些數(shù)據(jù),測試一些軟件纯路,甚至自動操作瀏覽器做很多事情

也不乏有些不法分子拿爬蟲做一些違法的事情或油,在此希望大家學會爬蟲使用在正道上,獲取一些我們需要的數(shù)據(jù)來進行分析

同時驰唬,在爬取目標網(wǎng)站之前顶岸,建議大家瀏覽該網(wǎng)站的robots.txt,來確保自己爬取的數(shù)據(jù)在對方允許范圍之內(nèi)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叫编,一起剝皮案震驚了整個濱河市辖佣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌搓逾,老刑警劉巖卷谈,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異霞篡,居然都是意外死亡世蔗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門朗兵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凸郑,“玉大人,你說我怎么就攤上這事矛市≤搅ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長而昨。 經(jīng)常有香客問我救氯,道長,這世上最難降的妖魔是什么歌憨? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任着憨,我火速辦了婚禮,結(jié)果婚禮上务嫡,老公的妹妹穿的比我還像新娘甲抖。我一直安慰自己,他們只是感情好心铃,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布准谚。 她就那樣靜靜地躺著,像睡著了一般去扣。 火紅的嫁衣襯著肌膚如雪柱衔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天愉棱,我揣著相機與錄音唆铐,去河邊找鬼。 笑死奔滑,一個胖子當著我的面吹牛艾岂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播朋其,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼澳盐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了令宿?” 一聲冷哼從身側(cè)響起叼耙,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎粒没,沒想到半個月后筛婉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡癞松,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年爽撒,在試婚紗的時候發(fā)現(xiàn)自己被綠了谷扣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片移怯。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖汪茧,靈堂內(nèi)的尸體忽然破棺而出枫甲,到底是詐尸還是另有隱情源武,我是刑警寧澤扼褪,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站粱栖,受9級特大地震影響话浇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜闹究,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一幔崖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧渣淤,春花似錦赏寇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至刻伊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間椒功,已是汗流浹背捶箱。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留动漾,地道東北人丁屎。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像旱眯,于是被迫代替她去往敵國和親晨川。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容