基于nodejs与Selenium&Puppeteer实现爬虫

一、什么是爬虫

爬虫是一种自动化程序,用于在互联网上收集信息。它可以模拟人类用户的行为,访问网页并提取其中的数据,这些数据可以用于分析、展示或其他应用。可以把互联网比做成一张“大网”,爬虫就是在这张大网上不断爬取信息的程序。

二、手动爬虫

手动爬虫需要人工编写代码来请求网页、解析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自动化测试和爬虫的工具,它们之间有一些区别:

  1. 语言支持
    • Selenium:支持多种编程语言,如Java、Python、JavaScript等。
    • Puppeteer:主要支持JavaScript,因为是基于Node.js开发的。
  2. 浏览器支持
    • Selenium:支持多种浏览器,如Chrome、Firefox、Safari等。
    • Puppeteer:主要支持Chrome浏览器,因为是由Chrome团队开发的。
  3. 性能
    • Puppeteer通常比Selenium更快,因为Puppeteer是直接操作Chrome浏览器而不是通过WebDriver协议来控制浏览器。
  4. 功能
    • Puppeteer提供了更多高级功能,如截图、PDF生成、网络请求拦截等,而Selenium相对来说功能较为基础。
  5. 部署
    • Puppeteer相对来说更容易部署,因为它是一个Node.js库,安装和配置相对简单。
    • Selenium需要下载相应的浏览器驱动,并配置环境变量,相对复杂一些。

总的来说,如果需要更高级的功能和更好的性能,可以选择Puppeteer;如果需要跨浏览器支持或使用其他编程语言,可以选择Selenium。

2、Selenium的使用

  1. 根据平台下载需要的webdriver,下载后放入项目根目录,注意: 下载的webdriver要和当前浏览器的版本一致
浏览器webdriver
Chromehttps://getwebdriver.com/chromedriver#stable
InternetExplorerhttp://selenium-release.storage.googleapis.com/index.html
Edgehttp://go.microsoft.com/fwlink/?LinkId=619687
Firefoxhttps://github.com/mozilla/geckodriver/releases/
Safarihttps://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)打开指定的 URLawait 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()获取当前页面的 URLlet 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)切换到指定的 iframeawait 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)导航到指定的 URLawait 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)将页面保存为 PDFawait 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()获取页面的 URLconst url = await page.url();
page.waitForTimeout(milliseconds)等待指定的时间await page.waitForTimeout(5000);
page.waitForNavigation(options)等待页面导航await page.waitForNavigation();
page.waitForFunction(pageFunction, options, ...args)等待指定的函数返回 trueawait 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/

四、爬虫的危害

虽然爬虫在数据收集和分析方面有许多合法和有益的应用,但不当使用爬虫也会带来一系列的危害。

  1. 服务器负载增加

    • 大量的爬虫请求会对目标网站的服务器造成过大的负载,可能导致服务器性能下降,甚至崩溃。这不仅影响网站的正常运营,还可能导致用户体验变差。
  2. 数据隐私和安全问题

    • 爬虫可能会获取到敏感信息或个人数据,导致数据泄露和隐私问题。这种行为可能违反数据保护法律和法规,给网站所有者和用户带来法律风险。
  3. 知识产权侵权

    • 爬虫可能会未经授权地复制和使用网站上的内容,侵犯版权和其他知识产权。这种行为可能导致法律纠纷和经济损失。

    等。。。

为了避免上述危害,使用爬虫时应遵循以下原则:

  • 遵守网站的robots.txt文件和使用条款。
  • 避免频繁访问,设置合理的访问间隔。
  • 不获取和使用敏感信息或个人数据。
  • 遵守相关法律法规,尊重知识产权。

通过合理和合法地使用爬虫技术,可以有效地获取所需数据,同时避免对目标网站和其他相关方造成不良影响。

  • 30
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值