爬虫的常见方式
- 通过模拟接口调用 获取返回数据方式
- 通过对页面标签获取 页面上面的数据值
第一种方式
相对来说爬取简单、快捷,但是对于处理过反爬虫网站来说不能很好的获取到,
这里我们以掘金社区列表为例子:
首先我们需要对接口的分析
然后建立接口连接调用接口,这里有些需要注意的是最好按照当前请求头配置 啥也不说直接上代码
const fs = require('fs')
const axios = require("axios");
const url = 'https://web-api.juejin.im/query'
const param = {
extensions: {
query: {
id: '21207e9ddb1de777adeaca7a2fb38030'
}
},
operationName: "",
query: "",
variables: {
first: 20,
after: "",
order: "POPULAR"
}
}
async function testPost() {
let response = await axios({
method: "POST",
headers: {
'X-Agent': 'Juejin/Web',
'Accept': '*/*',
'Content-Type': 'application/json',
'Host': 'web-api.juejin.im',
'Origin': 'https://juejin.im',
'Referer': 'https://juejin.im/',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-site',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
},
url: url,
data: JSON.stringify(param)
})
console.log(response.data)
writefile(response.data, 'index')
}
testPost()
writefile = function (result, fileName) {
fs.writeFile(`./${fileName}.json`, JSON.stringify(result), { 'flag': 'a' }, function(err) {
if(err) throw err
console.log('写入成功')
})
}
方式二
在实际需求中并不是所有的网站数据都是在接口中返回,或者说完全不是接口返回这时候就需要第二种方式来实现。通过网页上的标签找到对应需要的数据
这里推荐使用的是谷歌出来的 Puppeteer
什么是Puppeteer?
简而言之,这货是一个提供高级API的node库,能够通过devtool控制headless模式的
chrome或者chromium,它可以在headless模式下模拟任何的人为操作
还是以掘金列表为例,首先确定你需要爬取的字段或者需要的数据。分析下页面DOM结构。
首先起个项目安装 Puppeteer
npm install Puppeteer -s
在项目中新建的index.js
// 启动浏览器
const browers = await puppeteer.launch({
headless: false, // 打开可视化的配置
defaultViewport: { // 视口大小设置
width: 1920,
height: 1080,
},
slowMo: 300
})
// 打开一个新页面
const Page = await browers.newPage();
// 打开并进入需要爬取的页面
await Page.goto('https://juejin.im/timeline/recommended')
console.log('document loaded success')
对于第一页爬取加载出来必然是会比较好去拿,这里的页面分页是需要下拉加载获取然后动态生成上去的这时候就体现的 puppeteer 有点他可以模拟用户的 操作
// 延迟使用
const sleep = (times) => {
return new Promise((resolve)=> {
setTimeout(()=> {
resolve(1)
}, times)
})
}
await sleep(3000)
await Page.evaluate(()=>{
window.scrollBy(0, document.body.scrollHeight)
})
console.log(`1 page load success`);
await sleep(3000)
await Page.evaluate(()=>{
window.scrollBy(0, document.body.scrollHeight)
})
console.log(`2 page load success`);
通过程序去模拟用户下拉操作 window.scrollBy
然后就是对dom里面的数据进行需要性获取了
await sleep(3000)
await Page.evaluate(()=>{
let dats = []
let listSelecter = [...document.querySelectorAll('.entry-list .item .entry-box')]
for(let item of listSelecter) {
// 文章类型
let articleType = item.querySelector('.info-box .meta-row .post').innerText
// 作者名字
let articleAuthor = item.querySelector('.info-box .meta-row .username a').innerText
// 作者名称跳转对应作者详情页 路由
let author_url = item.querySelector('.info-box .meta-row .username a').href
// 文章列表标题
let articleTitle = item.querySelector('.info-box .title-row .title').innerText
// 点赞数
let giveLikeNum = item.querySelector('.info-box .action-row .like .title-box .count').innerText
// 评论数 做判断有的会不存在评论数 点赞数也一样
let commentNum = item.querySelector('.info-box .action-row .comment .count') ? item.querySelector('.info-box .action-row .comment .count').innerText : 0
// 文章详情页面路由地址
let detail_url = item.querySelector('.entry-link').href
// 列表展示图地址
let article_list_img = item.querySelector('.thumb') ? item.querySelector('.thumb').getAttribute('data-src') : ''
// 文章所属 类型标签
let tagList = [...item.querySelectorAll('.info-box .meta-row .meta-list>.tag a')]
let articleTag = []
for (let i of tagList) {
articleTag.push({
tag_url: i.href,
tag_name: i.innerText
})
}
let listInfo = {
articleType: articleType,
articleAuthor: articleAuthor,
articleTitle: articleTitle,
giveLikeNum: giveLikeNum,
commentNum: commentNum,
detail_url: detail_url,
author_url: author_url,
articleTag: articleTag,
article_list_img: article_list_img
}
dats.push(listInfo)
}
return dats
})
这里主要用到了基础的一些 puppeteer的API 跟多的 中文文档 Puppeteer
全部代码
const puppeteer = require('puppeteer');
// const { resolve } = require('path');
const pullUrlData = async function() {
// 启动浏览器
const browers = await puppeteer.launch({
headless: false,
defaultViewport: {
width: 1920,
height: 1080,
},
slowMo: 300
})
// 打开一个新页面
const Page = await browers.newPage();
await Page.goto('https://juejin.im/timeline/recommended')
console.log('document loaded success')
const sleep = (times) => {
return new Promise((resolve)=> {
setTimeout(()=> {
resolve(1)
}, times)
})
}
await sleep(3000)
await Page.evaluate(()=>{
window.scrollBy(0, document.body.scrollHeight)
})
console.log(`1 page load success`);
await sleep(3000)
await Page.evaluate(()=>{
window.scrollBy(0, document.body.scrollHeight)
})
console.log(`2 page load success`);
await sleep(3000)
let list = await Page.evaluate((Page)=>{
let dats = []
let listSelecter = [...document.querySelectorAll('.entry-list .item .entry-box')]
for (let item of listSelecter) {
let articleType = item.querySelector('.info-box .meta-row .post').innerText
let articleAuthor = item.querySelector('.info-box .meta-row .username a').innerText
let author_url = item.querySelector('.info-box .meta-row .username a').href
let articleTitle = item.querySelector('.info-box .title-row .title').innerText
let giveLikeNum = item.querySelector('.info-box .action-row .like .title-box .count').innerText
let commentNum = item.querySelector('.info-box .action-row .comment .count') ? item.querySelector('.info-box .action-row .comment .count').innerText : 0
let detail_url = item.querySelector('.entry-link').href
let article_list_img = item.querySelector('.thumb') ? item.querySelector('.thumb').getAttribute('data-src') : ''
let tagList = [...item.querySelectorAll('.info-box .meta-row .meta-list>.tag a')]
let articleTag = []
for (let i of tagList) {
articleTag.push({
tag_url: i.href,
tag_name: i.innerText
})
}
let listInfo = {
articleType: articleType,
articleAuthor: articleAuthor,
articleTitle: articleTitle,
giveLikeNum: giveLikeNum,
commentNum: commentNum,
detail_url: detail_url,
author_url: author_url,
articleTag: articleTag,
article_list_img: article_list_img
}
dats.push(listInfo)
}
return dats
})
console.log('isOk',list)
}
pullUrlData()
对比、思考
在学习中,有了解相关类似插件Crawler、cheerio、puppeteer
Crawler、cheerio
优点:比较轻量,速度快
缺点:都是只能爬取单链接页面,无法进行模拟操作。
例如:瓜子二手车直卖网 https://www.guazi.com/sh/buy/o2/#bread 页面分页有规律可行 可以进行链接修改来处理
puppeteer
优点:可以模拟用操作,爬取单页应用,插入脚本代码执行,只要浏览器能操作的都可以进行模拟
缺点:每次都需要加载chrime内核,所以会对电脑性能占用较大。
扩展
-
并不是所有的网站都需要 这种 模式来 爬取可能某些数据是需要 DOM来获取的可以相互结合爬取,
-
除了模拟用户滚动操作以外 分页若是 点击页码来做处理 page.click(selector[, options])
-
需要登录的网站来处理模拟登录操作,或者是设置 headless: false 打开调试窗口来手动操作登录、设置等待时间来完成登录操作后 在执行爬取操作。
-
附上将处理好的数据存入MySql数据库
const mysql = require('mysql') // 创建连接池 const connection = mysql.createConnection({ host: '127.0.0.1', user: 'username', password: '123456', database: 'dataName' // 数据库名称 }) // 将写入的表的字段 结构例子 const addSql = "INSERT INTO subject(id, exam_id, subject_name, subject_value) values (?,?,?,?)" let addparams = [1, 2, 3, 4] connection.query(addSql, addparams, function(err,data){ if(err){ console.log(err,"数据库连接错误"); } })