一、什么是爬虫
爬虫是一种自动化程序,用于在互联网上收集信息。它可以模拟人类用户的行为,访问网页并提取其中的数据,这些数据可以用于分析、展示或其他应用。可以把互联网比做成一张“大网”,爬虫就是在这张大网上不断爬取信息的程序。
二、手动爬虫
手动爬虫需要人工编写代码来请求网页、解析HTML并提取信息。
自动化爬虫则使用程序自动执行这些任务,通常会使用工具如Selenium或Puppeteer来模拟浏览器行为,从而实现自动化的数据收集。
在爬虫中,cheerio是一个类似于jQuery的库,用于在服务器端解析HTML文档。它提供了一种简单而强大的方式来对HTML文档进行操作和提取数据,可以通过选择器来定位和提取所需的信息,非常适合用于网页数据的抓取和处理。
案例一:爬取图片
const http = require("https");
const cheerio = require("cheerio");
const download = require("download");
let req = http.request("https://www.csdn.net/", (res) => {
let chunks = [];
res.on("data", (chunk) => {
chunks.push(chunk);
});
res.on("end", () => {
let html = Buffer.concat(chunks).toString("utf-8");
let $ = cheerio.load(html);
let imgArr = Array.prototype.map.call(
$(".ContentBlock .ContentBlockItem img"),
(item) => encodeURI($(item).attr("src"))
);
console.log("imgArr", imgArr);
Promise.all(imgArr.map((x) => download(x, "dist"))).then(() => {
console.log("files downloaded!");
});
});
});
req.end();
案例二:爬取文案 - 从接口获取
const https = require("https");
let url = "https://img-home.csdnimg.cn/data_json/toolbar/toolbar1105.json";
let req = https.request(url, { method: "get" }, (res) => {
let chunks = [];
res.on("data", (chunk) => chunks.push(chunk));
res.on("end", () => {
let result = Buffer.concat(chunks).toString("utf-8");
console.log(JSON.parse(result));
});
});
req.end();
如果遇到请求限制,可以模拟真实浏览器的请求头:
const https = require("https");
let url = "https://img-home.csdnimg.cn/data_json/toolbar/toolbar1105.json";
let req = https.request(url, {
method: "get",
headers: {
"Host": "www.csdn.net",
"Connection": "keep-alive",
"Content-Length": "0",
"Accept": "*/*",
"Origin": "https://www.csdn.net/",
"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": "https://www.csdn.net/?spm=1001.2101.3001.4476",
"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........ "
}
}, (res) => {
let chunks = [];
res.on("data", (chunk) => chunks.push(chunk));
res.on("end", () => {
let result = Buffer.concat(chunks).toString("utf-8");
console.log(JSON.parse(result));
});
});
req.end();
案例三:爬取图片+文本
const http = require("https");
const fs = require("fs");
const cheerio = require("cheerio");
const download = require("download");
const path = require("path");
let req = http.request("https://www.csdn.net/", (res) => {
let chunks = [];
res.on("data", (chunk) => {
chunks.push(chunk);
});
res.on("end", () => {
let html = Buffer.concat(chunks).toString("utf-8");
let $ = cheerio.load(html);
let itemArr = Array.prototype.map.call(
$(".ContentBlock .ContentBlockItem"),
(item) => {
return {
title: $(item).find(".title").text(),
img: $(item).find("img").attr("src"),
};
}
);
let jsonData = JSON.stringify(itemArr, null, 2);
const distDir = path.join(__dirname, "dist");
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir);
}
fs.writeFile("dist/data.json", jsonData, (err) => {
if (err) throw err;
});
Promise.all(itemArr.map((x) => download(x.img, "dist"))).then(() => {
console.log("Files downloaded!");
});
});
});
req.end();
三、自动化爬虫
Web应用的自动化测试框架,可以创建回归测试来检验软件功能和用户需求,通过框架可以编写代码来启动浏览器进行自动化测试,换言之,用于做爬虫就可以使用代码启动浏览器,让真正的浏览器去打开网页,然后去网页中获取想要的信息,无惧反爬虫手段。
1、Selenium与puppeteer的对比
Selenium和Puppeteer都是用于Web自动化测试和爬虫的工具,它们之间有一些区别:
- 语言支持:
- Selenium:支持多种编程语言,如Java、Python、JavaScript等。
- Puppeteer:主要支持JavaScript,因为是基于Node.js开发的。
- 浏览器支持:
- Selenium:支持多种浏览器,如Chrome、Firefox、Safari等。
- Puppeteer:主要支持Chrome浏览器,因为是由Chrome团队开发的。
- 性能:
- Puppeteer通常比Selenium更快,因为Puppeteer是直接操作Chrome浏览器而不是通过WebDriver协议来控制浏览器。
- 功能:
- Puppeteer提供了更多高级功能,如截图、PDF生成、网络请求拦截等,而Selenium相对来说功能较为基础。
- 部署:
- Puppeteer相对来说更容易部署,因为它是一个Node.js库,安装和配置相对简单。
- Selenium需要下载相应的浏览器驱动,并配置环境变量,相对复杂一些。
总的来说,如果需要更高级的功能和更好的性能,可以选择Puppeteer;如果需要跨浏览器支持或使用其他编程语言,可以选择Selenium。
2、Selenium的使用
- 根据平台下载需要的webdriver,下载后放入项目根目录,注意: 下载的webdriver要和当前浏览器的版本一致
浏览器 | webdriver |
---|---|
Chrome | https://getwebdriver.com/chromedriver#stable |
InternetExplorer | http://selenium-release.storage.googleapis.com/index.html |
Edge | http://go.microsoft.com/fwlink/?LinkId=619687 |
Firefox | https://github.com/mozilla/geckodriver/releases/ |
Safari | https://developer.apple.com/library/prerelease/content/releasenotes/General/WhatsNewInSafari/Articles/Safari_10_0.html#//apple_ref/doc/uid/TP40014305-CH11-DontLinkElementID_28 |
2、安装selenium-webdriver的包
npm i selenium-webdriver
3、自动化采集文本及图片
const download = require('download')
const fs = require('fs')
const path = require("path");
const { Builder, By, Key, until } = require('selenium-webdriver');
function changeExtension(filename, newExtension) {
// 使用正则表达式找到文件名的扩展部分,并将其替换为新的扩展名
return filename.replace(/\.[^/.]+$/, '.' + newExtension);
}
(async function start() {
let driver = await new Builder().forBrowser('chrome').build();
await driver.get('https://www.juejin.cn/');
await driver.findElement(By.css('.search-input')).sendKeys('前端', Key.ENTER);
let elements = await driver.wait(until.elementsLocated(By.className('content-box')), 10000);
const infos = await Promise.all(elements.map(async item => {
const userName = await item.findElement(By.css('.meta-list .user-popover')).getText()
const time = await item.findElement(By.css('.meta-list li:nth-child(2)')).getText()
const type = await item.findElement(By.css('.meta-list li:nth-child(3)')).getText()
const title = await item.findElement(By.css('.title')).getText()
let img = '';
const imgDom = await item.findElements(By.css('.thumb'));
if(imgDom.length > 0){
const src = await imgDom[0].getAttribute('src') || '';
if(src){
img = changeExtension(src, 'png');
}
}
return {
userName: userName,
time: time,
type: type,
title: title,
img: img
};
}));
let jsonData = JSON.stringify(infos, null, 2);
const distDir = path.join(__dirname, "dist");
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir);
}
fs.writeFile("dist/data.json", jsonData, (err) => {
if (err) throw err;
});
Promise.all(infos.map(info => {
if(info.img){
download(info.img).then(data=>{
fs.writeFileSync(`dist/${info.userName}.png`,data)
})
}
})).then(() => {
console.log('files downloaded!');
});
console.log('info9', infos)
})();
4、滚动加载更多数据并采集
const download = require('download');
const fs = require('fs');
const path = require("path");
const { Builder, By, Key, until } = require('selenium-webdriver');
function changeExtension(filename, newExtension) {
// 使用正则表达式找到文件名的扩展部分,并将其替换为新的扩展名
return filename.replace(/\.[^/.]+$/, '.' + newExtension);
}
(async function start() {
let driver = await new Builder().forBrowser('chrome').build();
await driver.get('https://www.juejin.cn/');
await driver.findElement(By.css('.search-input')).sendKeys('前端', Key.ENTER);
const collectData = async () => {
let elements = await driver.wait(until.elementsLocated(By.className('content-box')), 10000);
const infos = await Promise.all(elements.map(async item => {
const userName = await item.findElement(By.css('.meta-list .user-popover')).getText();
const time = await item.findElement(By.css('.meta-list li:nth-child(2)')).getText();
const type = await item.findElement(By.css('.meta-list li:nth-child(3)')).getText();
const title = await item.findElement(By.css('.title')).getText();
let img = '';
const imgDom = await item.findElements(By.css('.thumb'));
if(imgDom.length > 0){
const src = await imgDom[0].getAttribute('src') || '';
if(src){
img = changeExtension(src, 'png');
}
}
return {
userName: userName,
time: time,
type: type,
title: title,
img: img
};
}));
return infos;
};
let allInfos = [];
const maxScrolls = 6; // 最大滚动次数
let scrolls = 0;
let lastInfosLength = 0;
while (scrolls < maxScrolls) {
let infos = await collectData();
allInfos = allInfos.concat(infos);
// 滚动到页面底部
await driver.executeScript('window.scrollTo(0, document.body.scrollHeight)');
await driver.sleep(2000); // 等待页面加载
// 检查是否有更多内容加载,如果没有则退出循环
if (infos.length === lastInfosLength) break;
lastInfosLength = infos.length;
scrolls++;
}
let jsonData = JSON.stringify(allInfos, null, 2);
const distDir = path.join(__dirname, "dist");
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir);
}
fs.writeFile("dist/data.json", jsonData, (err) => {
if (err) throw err;
});
Promise.all(allInfos.map(info => {
if(info.img){
download(info.img).then(data=>{
fs.writeFileSync(`dist/${info.userName}.png`,data)
})
}
})).then(() => {
console.log('files downloaded!');
});
console.log('info', allInfos);
})();
3、selenium常用api的列举
API | 说明 | 示例 |
---|---|---|
driver.get(url) | 打开指定的 URL | await driver.get("https://www.example.com"); |
driver.findElement(By) | 查找并返回匹配的单个 Web 元素 | let element = await driver.findElement(By.id("element_id")); |
driver.findElements(By) | 查找并返回匹配的所有 Web 元素,返回一个列表 | let elements = await driver.findElements(By.className("element_class")); |
driver.wait(condition, timeout) | 等待某个条件成立,最多等待指定的时间 | let element = await driver.wait(until.elementLocated(By.id("element_id")), 10000); |
driver.executeScript(script, *args) | 执行 JavaScript 脚本 | let title = await driver.executeScript("return document.title;"); |
driver.sleep(seconds) | 使当前线程休眠指定的秒数 | await driver.sleep(5000); |
driver.getTitle() | 获取当前页面的标题 | let title = await driver.getTitle(); |
driver.getCurrentUrl() | 获取当前页面的 URL | let currentUrl = await driver.getCurrentUrl(); |
driver.close() | 关闭当前窗口 | await driver.close(); |
driver.quit() | 关闭所有窗口并退出 WebDriver 会话 | await driver.quit(); |
element.click() | 点击元素 | await element.click(); |
element.sendKeys(keys) | 向元素发送键盘输入 | await element.sendKeys("text to input"); |
element.clear() | 清除元素中的内容 | await element.clear(); |
element.getText() | 获取元素的文本内容 | let text = await element.getText(); |
element.isDisplayed() | 检查元素是否显示 | let isDisplayed = await element.isDisplayed(); |
element.isEnabled() | 检查元素是否可用 | let isEnabled = await element.isEnabled(); |
element.isSelected() | 检查元素是否被选中 | let isSelected = await element.isSelected(); |
driver.switchTo.frame(frame_reference) | 切换到指定的 iframe | await driver.switchTo().frame("frame_name"); |
driver.switchTo.defaultContent() | 切换回主文档 | await driver.switchTo().defaultContent(); |
4、Puppeteer的使用
1、puppereer的安装
npm i puppeteer
# or "yarn add puppeteer"
当你安装 Puppeteer 时,它会下载最新版本的Chromium(~170MB Mac,~282MB Linux,~280MB Win),以保证可以使用 API。
2、puppeteer截图
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({width: 1920, height: 2000});
//waitUntil: 'networkidle2' 会等待页面的网络连接在500毫秒内没有超过2个请求时,认为页面加载完成
await page.goto('https://www.4399.com/', { waitUntil: 'networkidle2' });
await page.screenshot({ path: '4399.png' });
await browser.close();
})();
3、自动化滚动并采集
const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');
const download = require('download');
(async () => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://www.juejin.cn/');
// 输入搜索关键字并提交
await page.type('.search-input', '后端');
await page.keyboard.press('Enter');
await page.waitForSelector('.content-box', { timeout: 10000 });
// 滚动并采集数据
const infos = [];
let previousHeight;
let scrollCount = 0;
const maxScrolls = 15; // 最大滚动次数
while (scrollCount < maxScrolls) {
const newInfos = await page.evaluate(() => {
function changeExtension(filename, newExtension) {
return filename.replace(/\.[^/.]+$/, '.' + newExtension);
}
const items = document.querySelectorAll('.content-box');
return Array.from(items).map(item => {
const userName = item.querySelector('.meta-list .user-popover')?.innerText || '';
const time = item.querySelector('.meta-list li:nth-child(2)')?.innerText || '';
const type = item.querySelector('.meta-list li:nth-child(3)')?.innerText || '';
const title = item.querySelector('.title')?.innerText || '';
let img = '';
const imgDom = item.querySelector('.thumb');
if (imgDom) {
const src = imgDom.getAttribute('src');
if (src) {
img = changeExtension(src, 'png');
}
}
return { userName, time, type, title, img };
});
});
infos.push(...newInfos);
previousHeight = await page.evaluate('document.body.scrollHeight');
await page.evaluate('window.scrollTo(0, document.body.scrollHeight)');
await new Promise(resolve => setTimeout(resolve, 2000)); // 等待加载新的内容
const newHeight = await page.evaluate('document.body.scrollHeight');
if (newHeight === previousHeight) {
break;
}
scrollCount++;
console.log(`开始采集${scrollCount}页数据`)
}
// 去重处理
const uniqueInfos = Array.from(new Set(infos.map(a => JSON.stringify(a)))).map(b => JSON.parse(b));
const jsonData = JSON.stringify(uniqueInfos, null, 2);
const distDir = path.join(__dirname, "dist");
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir);
}
fs.writeFileSync(path.join(distDir, "data.json"), jsonData);
await Promise.all(uniqueInfos.map(async (info) => {
if (info.img) {
const data = await download(info.img);
fs.writeFileSync(path.join(distDir, `${info.userName}.png`), data);
}
}));
console.log('采集完成');
console.log('info:', uniqueInfos);
await browser.close();
})();
Puppeteer是一个由Google开发的无头浏览器库,可以在后台执行浏览器操作,不会弹出可见的浏览器窗口,因此可以更高效地进行网页自动化和爬虫操作。也可以通过设置headless 为false,来打开浏览器查看采集过程:await puppeteer.launch({ headless: false});
5、puppeteer常用api的列举
API | 说明 | 示例 |
---|---|---|
puppeteer.launch(options) | 启动一个新的浏览器实例 | const browser = await puppeteer.launch({ headless: true }); |
browser.newPage() | 创建一个新的页面 | const page = await browser.newPage(); |
page.goto(url) | 导航到指定的 URL | await page.goto('https://www.example.com'); |
page.waitForSelector(selector) | 等待指定的选择器出现在页面中 | await page.waitForSelector('#element_id'); |
page.$(selector) | 查找并返回匹配的单个元素 | const element = await page.$('#element_id'); |
page.$$(selector) | 查找并返回匹配的所有元素,返回一个数组 | const elements = await page.$$('.element_class'); |
page.evaluate(pageFunction, ...args) | 在页面上下文中执行 JavaScript 代码 | const title = await page.evaluate(() => document.title); |
page.click(selector) | 点击指定的元素 | await page.click('#element_id'); |
page.type(selector, text) | 在指定的元素中输入文本 | await page.type('#input_id', 'text to input'); |
page.screenshot(options) | 截取页面截图 | await page.screenshot({ path: 'screenshot.png' }); |
page.pdf(options) | 将页面保存为 PDF | await page.pdf({ path: 'page.pdf', format: 'A4' }); |
page.setViewport(viewport) | 设置页面视口大小 | await page.setViewport({ width: 1280, height: 800 }); |
page.content() | 获取页面的 HTML 内容 | const content = await page.content(); |
page.title() | 获取页面的标题 | const title = await page.title(); |
page.url() | 获取页面的 URL | const url = await page.url(); |
page.waitForTimeout(milliseconds) | 等待指定的时间 | await page.waitForTimeout(5000); |
page.waitForNavigation(options) | 等待页面导航 | await page.waitForNavigation(); |
page.waitForFunction(pageFunction, options, ...args) | 等待指定的函数返回 true | await page.waitForFunction(() => document.querySelector('#element_id') !== null); |
elementHandle.click() | 点击元素 | await element.click(); |
elementHandle.type(text) | 在元素中输入文本 | await element.type('text to input'); |
elementHandle.evaluate(pageFunction, ...args) | 在元素上下文中执行 JavaScript 代码 | const text = await element.evaluate(el => el.textContent); |
browser.close() | 关闭浏览器实例 | await browser.close(); |
5、技术文档
selenium中文文档: https://www.selenium.dev/zh-cn/documentation/webdriver/getting_started/using_selenium/
puppeteet中文文档:https://puppeteer.bootcss.com/
四、爬虫的危害
虽然爬虫在数据收集和分析方面有许多合法和有益的应用,但不当使用爬虫也会带来一系列的危害。
-
服务器负载增加:
- 大量的爬虫请求会对目标网站的服务器造成过大的负载,可能导致服务器性能下降,甚至崩溃。这不仅影响网站的正常运营,还可能导致用户体验变差。
-
数据隐私和安全问题:
- 爬虫可能会获取到敏感信息或个人数据,导致数据泄露和隐私问题。这种行为可能违反数据保护法律和法规,给网站所有者和用户带来法律风险。
-
知识产权侵权:
- 爬虫可能会未经授权地复制和使用网站上的内容,侵犯版权和其他知识产权。这种行为可能导致法律纠纷和经济损失。
等。。。
为了避免上述危害,使用爬虫时应遵循以下原则:
- 遵守网站的robots.txt文件和使用条款。
- 避免频繁访问,设置合理的访问间隔。
- 不获取和使用敏感信息或个人数据。
- 遵守相关法律法规,尊重知识产权。
通过合理和合法地使用爬虫技术,可以有效地获取所需数据,同时避免对目标网站和其他相关方造成不良影响。