網(wǎng)絡爬蟲開發(fā)
第1章 課程介紹
- 什么是爬蟲
- 爬蟲的意義
- 課程內(nèi)容
- 前置知識
什么是爬蟲
可以把互聯(lián)網(wǎng)比做成一張“大網(wǎng)”,爬蟲就是在這張大網(wǎng)上不斷爬取信息的程序
所以一句話總結(jié):爬蟲是請求網(wǎng)站并提取數(shù)據(jù)的自動化程序
爬蟲的基本工作流程如下:
- 向指定的URL發(fā)送http請求
- 獲取響應(HTML旨枯、XML蹬昌、JSON、二進制等數(shù)據(jù))
- 處理數(shù)據(jù)(解析DOM攀隔、解析JSON等)
- 將處理好的數(shù)據(jù)進行存儲
爬蟲的意義
爬蟲就是一個探測程序皂贩,它的基本功能就是模擬人的行為去各個網(wǎng)站轉(zhuǎn)悠栖榨,點點按鈕,找找數(shù)據(jù)明刷,或者把看到的信息背回來婴栽。就像一只蟲子在一幢樓里不知疲倦地爬來爬去。
你可以簡單地想象:每個爬蟲都是你的“分身”辈末。就像孫悟空拔了一撮汗毛愚争,吹出一堆猴子一樣。
你每天使用的百度和Google本冲,其實就是利用了這種爬蟲技術(shù):每天放出無數(shù)爬蟲到各個網(wǎng)站准脂,把他們的信息抓回來,存到數(shù)據(jù)庫中等你來檢索檬洞。
搶票軟件狸膏,就相當于撒出去無數(shù)個分身,每一個分身都幫助你不斷刷新 12306 網(wǎng)站的火車余票添怔。一旦發(fā)現(xiàn)有票湾戳,就馬上下單,然后對你喊:大爺快來付款呀广料。
在現(xiàn)實中幾乎所有行業(yè)的網(wǎng)站都會被爬蟲所 “騷擾”砾脑,而這些騷擾都是為了方便用戶
當然,有些網(wǎng)站是不能被過分騷擾的艾杏,其中排第一的就是出行類行業(yè)韧衣。
12306之所以會出如此變態(tài)的驗證碼,就是因為被爬蟲折磨的無可奈何
正所謂道高一尺魔高一丈购桑,某些爬蟲工具畅铭,為了解決這種變態(tài)驗證碼,甚至推出了“打碼平臺”
原理就是爬蟲還是不斷工作勃蜘,但只要遇到二維碼硕噩,就通過打碼平臺下發(fā)任務,打碼平臺另一邊就雇傭一大堆網(wǎng)絡閑人缭贡,只要看到有驗證碼來了炉擅,就人工選一下驗證碼,完美的讓程序與人工結(jié)合阳惹!
課程內(nèi)容及目標
- 爬蟲簡介
- 制作一個自動下載圖片的小爬蟲
- 使用Selenium爬取動態(tài)網(wǎng)站
前置知識
- js基礎(chǔ)
- node基礎(chǔ)
第2章 爬蟲基礎(chǔ)
學習目標:
- 以
http://web.itheima.com/teacher.html
網(wǎng)站目標為例谍失,最終目的是下載網(wǎng)站中所有老師的照片:
下載所有老師的照片,需要通過如下步驟實現(xiàn):
- 發(fā)送http請求莹汤,獲取整個網(wǎng)頁內(nèi)容
- 通過cheerio庫對網(wǎng)頁內(nèi)容進行分析
- 提取img標簽的src屬性
- 使用download庫進行批量圖片下載
發(fā)送一個HTTP請求
學習目標:
- 發(fā)送HTTP請求并獲取相應
在學習爬蟲之前快鱼,需要對HTTP請求充分了解,因為爬蟲的原理就是發(fā)送請求到指定URL,獲取響應后并處理
node官方api
node的核心模塊 http模塊即可發(fā)送請求攒巍,摘自node官網(wǎng)api:
由此可見只需要使用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庫簡介
這是一個核心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
- 分析網(wǎng)頁中所有img標簽所在結(jié)構(gòu)
- 使用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
大部分新聞網(wǎng)站,現(xiàn)在都采取前后端分離的方式仅偎,也就是前端頁面先寫好模板跨蟹,等網(wǎng)頁加載完畢后,發(fā)送Ajax再獲取數(shù)據(jù)橘沥,將其渲染到模板中窗轩。所以如果使用相同方式來獲取目標網(wǎng)站的HTML頁面,請求到的只是模板座咆,并不會有數(shù)據(jù):
此時痢艺,如果還希望使用當前方法爬取數(shù)據(jù),就需要分析該網(wǎng)站的ajax請求是如何發(fā)送的介陶,可以打開network面板來調(diào)試:
分析得出對應的ajax請求后堤舒,找到其URL,向其發(fā)送請求即可
代碼如下:
// 引入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的基本使用
根據(jù)平臺下載需要的webdriver
項目中安裝selenium-webdriver包
根據(jù)官方文檔寫一個小demo
根據(jù)平臺選擇webdriver
瀏覽器 | webdriver |
---|---|
Chrome | chromedriver(.exe) |
Internet Explorer | IEDriverServer.exe |
Edge | MicrosoftWebDriver.msi |
Firefox | geckodriver(.exe) |
Safari | safaridriver |
選擇版本和平臺:
下載后放入項目根目錄
安裝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)爬蟲昙啄?
- 自動打開拉勾網(wǎng)并搜索"前端"
- 獲取所有列表項
- 獲取其中想要的信息數(shù)據(jù)
為什么要用Selenium來做爬蟲
目前的大流量網(wǎng)站穆役,都會有些對應的反爬蟲機制
例如在拉勾網(wǎng)上搜索傳智播客:
找到對應的ajax請求地址,使用postman來測試數(shù)據(jù):
前幾次可能會獲取到數(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
如需設(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() 查找元素
WebElement
- getText() 獲取文本內(nèi)容
- sendKeys() 發(fā)送一些按鍵指令
-
click() 點擊該元素
自動打開拉勾網(wǎng)搜索"前端"
使用driver打開拉勾網(wǎng)主頁
找到全國站并點擊一下
輸入“前端”并回車
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)
})
})();
自動翻頁
思路如下:
- 定義初始頁碼
- 獲取數(shù)據(jù)后愕贡,獲取頁面上的總頁碼,定義最大頁碼
- 開始獲取數(shù)據(jù)時打印當前正在獲取的頁碼數(shù)
- 獲取完一頁數(shù)據(jù)后巷屿,當前頁碼自增颂鸿,然后判斷是否達到最大頁碼
- 查找下一頁按鈕并調(diào)用點擊api,進行自動翻頁
- 翻頁后遞歸調(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)