手把手带你Node爬虫入门

什么是爬虫

网络爬虫(又被称为网页蜘蛛,网络机器人)其实就是模拟浏览器发送网络请求,接收请求响应,一种按照一定的规则,自动地抓取互联网信息的程序。原则上,只要是浏览器(客户端)能做的事情,爬虫都能够做。另外它还有一些不常使用的名字比如蚂蚁、自动索引、模拟程序或蠕虫等。

爬虫的基本原理

一句话总结爬虫从原理上简单来讲,其实就是发送http/https请求+解析html页面,获取页面上的目标数据。

爬虫的流程

  1. 确定目标:首先,你需要明确你想要爬取的网页或者是需要提取数据的目标网站。
  2. 发起HTTP请求:使用js请求库发送HTTP请求到目标网页的URL,并获取网页的内容。
  3. 解析网页:可以使用HTML解析库(JavaScript的Cheerio)来解析网页的原始HTML代码。
  4. 数据提取:通过选择器、正则表达式或者其他方法,从解析后的网页中提取出你感兴趣的数据。这些数据可以是文本、链接、图片地址等各种形式。
  5. 数据处理:对于提取到的数据,进一步的处理和清洗。比如转换数据的格式,或者进行数据的筛选。
  6. 存储数据:将处理后的数据保存到一个文件或者数据库。

爬虫实现

简单的爬虫

想要爬取网页的内容,首先我们需要查找页面元素。 然后通过发送http请求网站地址,获取html字符串进行解析。以www.zhihu.com/billboard(知乎热榜列表)为例。

js 复制代码
const got = require('got')
const request = async () => {
    const res = await got.get('https://www.zhihu.com/billboard');
    const html = res.body
    console.log(html)
    /**
        <!doctype html>
        <html lang="zh" ><head><meta charSet="utf-8"/>
        <title data-rh="true">知乎热榜 - 知乎</title>
        ...
    */
    const regex = /<div class="HotList-itemTitle">(.*?)</div>/gs;
    const contentList = []
    let match;
    while ((match = regex.exec(html)) !== null) {
        const tag = match[0];
        const content = match[1];
        console.log(content);
        contentList.push(content)
    }
    console.log(content)
    /**
      '武汉大学学生夫妻可申请合宿,须持有结婚证,且均为在籍全日制学生,如何看待这一举措?',
      '在高铁上,有一个站票的人想和你换坐票,他态度恶劣并且甩给你一万块,你会怎么做?',
      '美国白宫高官称拜登愿无条件会见金正恩,如何看待该表态?当前东北亚局势如何评估?',
      ...
    */
}
request()

这种直接通过request请求的优点,简单只需要写少量的代码,缺点也很明显解析内容复杂时需要写大量的正则并且只能处理简单且是后端直出的网页爬取,然而现在大部分web端都是通过react或者vue等框架编写,编译出来都是html+静态资源的spa应用,并不是html直出,那么获取内容就需要执行js渲染页面内容,该如何处理?

Cheerio爬虫必备

什么是Cheerio

Cheerio是一个针对服务器端开发特别定制的库,它是一个快速、灵活且易于实施的jQuery核心实现。Cheerio的目标是在服务器环境下提供类似于jQuery的功能,使开发者能够使用熟悉的jQuery语法来解析、操作和遍历HTML文档。Cheerio特别适用于网页抓取、数据分析和服务器端渲染等任务。

试例代码(改造上面👆)

js 复制代码
// 和jquery一样简单便捷,避免了写大量正则
const got = require('got')
const cheerio = require('cheerio')

const request = async () => {
    const res = await got.get('https://www.zhihu.com/billboard');
    const html = res.body
    const $ = cheerio.load(html); // 加载文档
    $('.HotList-itemTitle').each((index, element) => {
        const text = $(element).text();
        console.log(text);
    });
}

request()

银弹(Headless Browser)

什么是无头浏览器

无头浏览器(Headless Browser)是一种浏览器程序,没有图形用户界面(GUI),但能够执行与普通浏览器相似的功能。无头浏览器能够加载和解析网页,执行JavaScript代码,处理网页事件,并提供对DOM(文档对象模型)的访问和操作能力。

与传统浏览器相比,无头浏览器的主要区别在于其没有可见的窗口或用户界面。这使得它在后台运行时,不会显示实际的浏览器窗口,从而节省了系统资源,并且可以更高效地执行自动化任务。

无头浏览器很多,包括PhantomJS, 基于 Webkit,SlimerJS, 基于 Gecko等。

如何使用(Headless Chrome)

Mac 上 Chrome 59 beta 版本与 Linux 上的 Chrome 57+ 已经开始支持 headless 特性,可以直接通过指定的命令行参数调用。相比于出道较早的 PhantomJS,SlimerJS 等,Headless Chrome 则更加贴近浏览器环境。chrome启动参数大全

js 复制代码
const { spawn } = require('child_process');

// 执行无头浏览器命令
const browserProcess = spawn(
  '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome', 
  ['--headless', '--disable-gpu', '--screenshot', '--window-size=1280,1696', 'https://juejin.cn/'
]);

// 监听进程结束, 完成之后会在同目录下生成截屏
browserProcess.on('close', (code) => {
  console.log(`无头浏览器进程退出`);
});

我们不需要任何插件仅仅需要node和浏览器,就可以调用无头浏览器。但是可以看出来,多且繁杂的启动参数命令使得原生无头浏览器难以使用。因此我们需要使用一些无头浏览器的库简化操作。

Puppeteer

Puppeteer 是 Chrome 开发团队在 2017 年发布的一个 Node.js 包,它提供了一个高层次的API,用于控制和操作Chrome或Chromium浏览器。Puppeteer 是用js封装操控浏览器的功能,能使我们更容易地调用浏览器的功能,对前端开发者非常友好。

与PhantomJS的区别

Puppeteer基于Chrome浏览器,使用Chrome DevTools Protocol进行通信和控制浏览器实例。而PhantomJS是一个独立的浏览器引擎,不需要依赖外部浏览器。

如何使用

js 复制代码
// 截图功能
const puppeteer = require('puppeteer');
const path = require('path');

(async () => {
  const browser = await puppeteer.launch({
    headless: true, // 标记无头模式
  })
  const page = await browser.newPage(); // 创建一个新页面
  await page.goto('https://juejin.cn'); // 新页面跳转到知道地址
  await page.screenshot({ // 调用截图功能
    path: path.resolve(__dirname, 'assets', `${ Date.now() }.png`)
  })
  browser.close();
})()

爬取电影网站(小试牛刀)

puppeteer提供了两个包puppeteer 会包含 Chromium 浏览器二进制文件,安装包较大。puppeteer-core 不包含具体的浏览器二进制文件,只包含与浏览器通信的核心库。我们可以通过指定浏览器的地址不需要安装Chromium。

js 复制代码
const puppeteer = require('puppeteer-core')
const cheerio = require('cheerio');

class Start {
  config = {
    executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
    headless: false,
    timeout: 100000
  }

  constructor() {
    this.InitBrowser()
  }

  // 初始化浏览器实例
  async InitBrowser() {
    const browser = await puppeteer.launch(this.config)
    this.page = await browser.newPage()
    this.InitPage()
  }

  // 初始化页面
  async InitPage() {
    await this.page.setViewport({ width: 1024, height: 768 })
    await this.page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36');
    await this.page.goto('https://ddys.pro/', {
      waitUntil: 'networkidle2',
    })
    await this.page.setRequestInterception(true);
    this.page.on('request', this.RequestChange);
    this.Crawling()
  }

  // 爬取页面之前
  async Crawling() {
    await this.page.waitForSelector('.post-box-image');  // 等待目标内容加载完成
    this.currentMovieSize = (await this.page.$$('.post-box-image')).length
    console.log(this.currentMovieSize)
    this.MovieCrawling()
  }

  // 爬取ing
  async MovieCrawling() {
    for (let i = 0; i <= this.currentMovieSize; i++) {
        await this.page.waitForSelector('.post-box-image');  // 等待目标内容加载完成
        const movieList = await this.page.$$('.post-box-image')
        const currentMovie = movieList[i];
        await Promise.all([
          currentMovie.click(),
          this.page.waitForNavigation({ timeout: 100000 }),
        ]);
        await this.page.waitForSelector('.vjs-big-play-button');  // 等待播放按钮出现
        await this.page.click('.vjs-big-play-button') // 点击播放按钮
        await this.sleep(3000) // 等三秒确保video的src出现
        const detailHtml = await this.page.content();
        const $ = cheerio.load(detailHtml);
        console.log($('.post-title').text(), $('video').attr('src'))
        this.page.goBack({ waitUntil: 'networkidle0' })
    }
  }

  RequestChange = async (request) => {
    const cookies = await this.page.cookies();
    await this.page.deleteCookie(...cookies);
    await request.continue();
  }

  async sleep(sm) {
    return await new Promise((resolve) => setTimeout(() => resolve(), sm))
  }

  async closeBrowser() {
    await browser.close()
  }
}

new Start()

将无头浏览器设置为有头看下具体的自动化执行过程

被反爬403

puppeteer在无头模式下UA会默认带headlessChrome标识,被识别为爬虫或机器人所以被拒绝访问了,需要重新设置UA。

js 复制代码
 await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36');

验证码场景

绝大多数的应用程序在涉及到用户信息安全的操作时,都会弹出验证码让用户进行识别,以确保该操作为人类行为,而不是大规模运行的机器。常见的验证码有几种形式,例如:数字字母验证码、滑块验证码、算数验证码、图片识别验证码等等,不同的方式带来的用户体验和防御能力是不同的。

数字字母验证码

是由随机字符加入杂点、干扰线后组成的一张图片,用户只需将图片中的字符输入到文本框中即可。这种验证码利用了人类的视觉识别能力,将没有识别能力的机器拒之门外。

如何破解

基于OCR进行识别,JS版本的OCR库比较少其中使用比较多的也就Tesseract.js了,另外还有一些不太出名的比如OCRad.jsTextract.js等。

js 复制代码
const ocr = async () => {
  const ImagePath = './assets/736399-20200108170302307-1377487770.jpg'
  Tesseract.recognize(
    ImagePath,
    'eng', // 标记语言
  ).then(({ data: { text } }) => {
    console.log(text);
  })
}
ocr()

因为一些干扰元素比如一些杂乱的线条和背景的点导致识别错误,需要进行对图像进行二值化处理。

图像的二值化,就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的只有黑和白的视觉效果。常用的灰度值计算方法有以下两种。

  1. 平均灰度法:将像素点的红、绿、蓝三个分量的值相加,然后除以3,得到平均值即为灰度值。公式如下:灰度值 = (R + G + B) / 3
  2. 加权平均灰度法:将像素点的红、绿、蓝三个分量的值分别乘以不同的权重,并相加,得到加权平均值即为灰度值。一种常用的加权方法是人眼对红、绿、蓝三个颜色的感知权重较高,分别取0.2989、0.5870和0.1140作为权重。公式如下:灰度值 = 0.2989 * R + 0.5870 * G + 0.1140 * B

JS可以使用canvas处理图像,getImageData是canvas提供的一个非常强大的接口,它可以获取canvas的所有的像素点的值,是一个超长的数组,像素点信息的排列规律如下。

更多的内容可以参考这篇文章segmentfault.com/a/119000001...

js 复制代码
const Tesseract = require('tesseract.js');
const { createCanvas, loadImage } = require('canvas')
const binaryzation = async (ImagePath) => {
  const Image = await loadImage(ImagePath)
  const canvas = createCanvas(Image.width, Image.height)
  const c = canvas.getContext('2d')
  c.drawImage(Image, 0, 0, canvas.width, canvas.height);
  var ImgData = c.getImageData(0, 0, canvas.width, canvas.height);
  var threshold = 160
  for (var i = 0; i < ImgData.data.length; i += 4) {
      var R = ImgData.data[i]; // R(0-255)
      var G = ImgData.data[i + 1]; // G(0-255)
      var B = ImgData.data[i + 2]; // B(0-255)
      var Alpha = ImgData.data[i + 3]; // Alpha(0-255)
      /**
        RGB相加并取平均值,可以获得一个在0到255范围内的灰度值。
        这个值可以代码彩色像素的明亮程度。
      */
      var sum = (R + G + B) / 3;
      if (sum > threshold) {
          ImgData.data[i] = 255;
          ImgData.data[i + 1] = 255;
          ImgData.data[i + 2] = 255;
          ImgData.data[i + 3] = Alpha;
      } else {
          ImgData.data[i] = 0;
          ImgData.data[i + 1] = 0;
          ImgData.data[i + 2] = 0;
          ImgData.data[i + 3] = Alpha;
      }
  }
  c.putImageData(ImgData, 0, 0);
  const ImageDataURL = canvas.toDataURL();
  return ImageDataURL
}
const ocr = async () => {
  const ImagePath = './assets/736399-20200108170302307-1377487770.jpg'
  const binaryzationImage = await binaryzation(ImagePath)
  Tesseract.recognize(
    ImagePath,
    'eng',
  ).then(({ data: { text } }) => {
    console.log(text);
  })
}
ocr()

二值化处理后的图像数字形态变得很清晰,所以识别率也提升了很多。

不过针对一些更加复杂的图片可能还需要进行更多处理,图像灰度,图片降噪等。

滑块验证码

它是一种基于用户操作行为的验证码形式,通过要求用户在图形界面中拖动滑块至指定位置,实现用户身份的验证。通过分析用户的滑动轨迹、滑动速度、加速度等行为数据,有效地区分真实用户与机器人。

如何破解

这类验证码最主要就是获取滑块距离,然后再通过无头浏览器进行模拟滑动,实现思路主要通过Canvas和Opencv.js实现,先找出上面验证码的几何图像的规律可以看到几何图像都有一个白色边框,可以从这里下手。使用canvas将所有的杂乱颜色给干掉,只保留白色,这可以就可以得到一个只有轮廓边框的图形,便于进行轮廓检测。

js 复制代码
const { createCanvas, loadImage } = require('canvas')
const coverImage = async (ImagePath) => {
    const Image = await loadImage(ImagePath)
    const canvas = createCanvas(Image.width, Image.height)
    const c = canvas.getContext('2d')
    c.drawImage(Image, 0, 0, canvas.width, canvas.height);
    var ImgData = c.getImageData(0, 0, canvas.width, canvas.height);
    const data = ImgData.data 
    // 遍历每个像素,将非白色的色值变为黑色
    for (var i = 0; i < data.length; i += 4) {
        var red = data[i];
        var green = data[i + 1];
        var blue = data[i + 2];
        // 判断是否为白色
        if (red == 255 && green == 255 && blue == 255) {
            data[i] = 225;     // 红色通道
            data[i + 1] = 0;   // 绿色通道
            data[i + 2] = 0;   // 蓝色通道
            // 是白色,变为红色
        } else {
            // 非白色,设置为黑色
            data[i] = 0;       // 红色通道
            data[i + 1] = 0;   // 绿色通道
            data[i + 2] = 0;   // 蓝色通道
        }
    }
    c.putImageData(ImgData, 0, 0);
    const ImageDataURL = canvas.toDataURL();
    return canvas
}
const action = async () => {
    const data = await coverImage('./assets/do1.png')
    console.log(data)
}
action()

得到上面图之后就可以通过opencv进行轮廓检测,node端使用opencv需要装一定的依赖,opencv内部依赖了dom直接运行会报错需要安装jsdom来模拟dom环境,更详细的可以看这里

js 复制代码
const cv = require('@techstark/opencv-js')
const { Canvas, createCanvas, Image, ImageData, loadImage } = require('canvas')
const { JSDOM } = require('jsdom')

const action = async () => {
    installDOM()
    const canvas = await coverImage('./assets/do1.png')
    cv.onRuntimeInitialized = () => {
        const src = cv.imread(canvas);
        const gray = new cv.Mat();
        cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY);
        const contours = new cv.MatVector();
        const hierarchy = new cv.Mat();
        // 参数含义cv.RETR_EXTERNAL只检测外轮廓,cv.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对象线方向的元素,只保留该方向的终点坐标。例如,在极端的情况下,一个矩形只需要用4个点来保存轮廓信息
        cv.findContours(gray, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE);
        for (let i = 0; i < contours.size(); i++) {
          const contour = contours.get(i);
          const area = cv.contourArea(contour);
          // 定义图形面积的伐值,过滤掉文字的轮廓
          if (area > 80) {
            const rect = cv.boundingRect(contour);
            const x = rect.x;
            const y = rect.y;
            const width = rect.width;
            const height = rect.height;
            console.log(`第${i + 1}个矩形: 左上角坐标(${x}, ${y}), 宽度${width}, 高度${height}`);
          }
        }
        src.delete();
        gray.delete();
    }
}
action()

// opencvjs内部依赖的dom
function installDOM() {
    const dom = new JSDOM();
    global.document = dom.window.document;
    global.Image = Image;
    global.HTMLCanvasElement = Canvas;
    global.ImageData = ImageData;
    global.HTMLImageElement = Image;
}

运行之后就得到了三个几何图形的宽高以及坐标信息等,将x轴绘制出来,识别是非常准的。

简单滑块破解

简单场景的滑块验证码,比如腾讯防水墙或者下面的彩色图片,这类验证码一般只有一个缺口,并且缺块的背景色基本都是偏向黑色,可以基于canvas实现,先将图片进行二值化。

📌 二值化的代码直接复用图片验证码的代码binaryzation函数,阀值根据图片效果进行适当调整,我这里调到了85以后得到以下效果。

二值化之后得到一张只有黑白的的图片,然后从像素下手,将黑白的的像素用0和1代替存入新数组。

js 复制代码
const pixels = [];
// 将像素信息存到pixels数组里
for (var i = 0; i < imgData.data.length; i += 4) {
    var R = imgData.data[i]; // R(0-255)
    var G = imgData.data[i + 1]; // G(0-255)
    var B = imgData.data[i + 2]; // B(0-255)
    var Alpha = imgData.data[i + 3]; // Alpha(0-255)
    var sum = (R + G + B) / 3;
    pixels.push(sum > index ? 0 : 1)
}

然后将pixels一维数组按照转成二维数组来表示图片的像素信息也就是排成行和列,然后可以用表格打印表示出来。

js 复制代码
function splitArray(arr, chunkSize) {
    var result = [];
    for (var i = 0; i < arr.length; i += chunkSize) {
        var chunk = arr.slice(i, i + chunkSize);
        result.push(chunk);
    }
    return result;
}
const table = document.createElement('table');
splitArray(pixels, canvas.width).forEach(y => {
    const row = document.createElement('tr');
    y.forEach(x => {
        const cell = document.createElement('td');
        cell.textContent = x;
        row.appendChild(cell)
    })
    table.appendChild(row);
})
document.body.appendChild(table);

得到一张由0和1排列照片之后接着找规律,滑块处可以看出出现了大量的由0变化到1的列,可以横向扫描当前列和下一列由0到1的变化像素最多的可以推出是滑块的大致位置。

js 复制代码
function findMaxChangeColumn(arr) {
    let maxChanges = 20; // 像素最大值超过20判定边界
    let maxChangeColumn = -1;
    arr[0].forEach(function(element, i) {
        let changes = 0;
        arr.forEach(function(row) {
            if (row[i] === 0 && row[i + 1] === 1) {
                changes++;
            }
        });
        if (changes > maxChanges) {
            maxChanges = changes;
            maxChangeColumn = i;
        }
    });
    return maxChangeColumn;
}

console.log(findMaxChangeColumn(splitArray(pixels, canvas.width)))

接下来验证效果随机取了几张图,用绿线把结果标记出来,看着识别率还是略高一点的。

模拟滑动还没来得及实现,这里贴个codesandbox完整的代码,感兴趣的可以自行实现一下。

使⽤在线打码

google搜索 验证码在线识别,接第三方平台,支持很多种验证码识别,提供的能力可以通过HTTP接口调用。

机器学习

最靠谱的方式,但成本和要求较高,什么变量,张量,卷积层,拟合曲线没整明白,感兴趣的可以自行搜索学习,目前前端的人工智能框架有TensorFlow.js,Antd官网基于TensorFlow.js实现了一个图标识别的功能,只在3.x版本的官网上有。

接口爬虫

什么是接口爬虫

接口爬虫实际上就是通过直接调用 HTTP 接口来获取数据。相比于传统的网页爬虫,接口爬虫更依赖于接口的数据返回形式,而不需要解析整个网页的结构。接口通常以 JSON 或 XML 的格式返回数据,因此,接口爬虫可以更轻松地提取所需的数据。

爬取流程

  1. 选择目标接口:确定爬取数据的目标接口,获取接口地址和请求方式(通常是 HTTP 请求)。
  2. 构建请求:根据目标接口的要求,构建相应的请求参数,包括传递需要的请求头、请求体等信息。
  3. 发送请求:使用编程语言或工具发送 HTTP 请求到目标接口,并获取返回的数据。
  4. 解析数据:根据接口返回的数据格式(如 JSON 或 XML),解析提取出所需的数据。

试例代码

js 复制代码
const got = require('got')
const request = async () => {
    // 获取掘金推荐流列表
    const res = await got.post('https://api.juejin.cn/recommend_api/v1/article/recommend_all_feed');
    const data = res.body
    console.log(data, '解析数据')
    // ... do something
}
request()

抓包

什么是抓包

抓包是一种网络技术,用于捕获和分析数据包。通常用于故障排查、安全漏洞分析和性能优化。

如何获取目标接口的URL以及请求业务参数,以及HTTP报文信息等。网页抓包可以Chrome自带的开发者工具查看HTTP请求很方便,对于前端来说已经是家常便饭了,就不过多赘述。

whistle

官网:wproxy.org/whistle/

基于Node实现的跨平台web调试代理工具,可用于查看、修改HTTP、HTTPS、Websocket的请求、响应,也可以作为HTTP代理服务器使用。类似的工具也有平台上的FiddlerCharlesProxyman不过有平台限制,配置也较繁琐。

js 复制代码
$ npm install -g whistle // 安装
$ w2 help // 启动
获取APP接口

此步骤默认你会配置whistle的WIFI代理,没有接触过的可以点进官网去查看教程,这里确认随便找个目标APP的登陆接口。

通过whistle的面板可以直观的看到login接口以及request和response,可以看到加密后的密码和账号,以及返回的token,原则上我们可以通过复制返回的token调通之后的其他任何接口。

修改HTTP请求

whistle支持的功能很多包括修改HTTP请求参数以及响应。

perl 复制代码
https://www.baidu.com/ reqHeaders:// reqBody:// resHeaders:// resBody://
基于whistle开发PC或 H5

可以将本地启动的静态资源进行代理,请求的时候就会替换请求本地的静态资源,通过excludeFilter过滤掉指定的头信息来过滤url,比如accept=application/json或者Content-Type,一般过滤接口。

perl 复制代码
^xxxxxxxxx.xxxxx.net/*** http://127.0.0.1:3000/$1 excludeFilter://h:accept=application/json

反编译

回头看通过抓包获取请求回来的token,但是token是有时效性的,不可能每次需要token都要抓一个有时效性的token,需要做到自动获取就需要模拟请求参数重新请求,但是请求参数是加密的如何模拟?需要获取源代码查看加密方式,web的代码一般都是链接可以直接通过开发者工具查看打,但是小程序app是安装包的形式安装到手机上的,那就需要进行解包获取源代码。

app反编译

app解包相对比较简单(具体应用具体分析),通常可以通过jadx工具进行反编译。或者可以使用javadecompilers网站反编译(不过背后应该还是基于jadx)。还是上个抓包的app为例进行解包,查看源码获取登陆加密方式。

js 复制代码
brew install jadx
jadx -d you_dir apk_path
vbnet 复制代码
C02FT5T4MD6R:Documents bytedance$ jadx -d decode v1.4.9_07-11-1952.apk 
INFO  - loading ...
INFO  - processing ...
ERROR - finished with errors, count: 96   
// 完成之后会在同目录下生成decode文件                   

jadx并不能在所有情况下都能完美地反编译应用程序。可能会面临某些限制,例如无法还原所有的代码结构和逻辑,上面就有96个错误不过并不影响文件产出也不影响我们查看源代码,并且这个APP似乎使用RN写的,因为在assets文件下发现了index.android.bundle,将代码找个网站代码美化一下,因为文件代码大部分网站美化都会卡死,试了很多才找到一个toolgg.com/javascript-...

美化之后直接找入参,虽然代码是编译过的但是还是能看的懂的基本都是一些变量命名比较难读,根据encrypt字段大概率可以推出使用的JSEncrypt库。

继续顺着找就可以找到加密方式了。

小程序反编译

小程序上传前会"编译",对 JS 代码进行压缩混淆以及对 wxmlwxss 和资源文件等进行整合打包成一个 .wxapkg 文件上传给微信服务器。

获取小程序pkg文件(每个小程序都有一个pkg源文件),需要一台已经root的手机或者使用模拟器,

小程序的文件通常会在这个路径,如果有很多pkg文件无法确定自己的包就把所有文件清空重新进一下小程序就只有自己的了。

bash 复制代码
/data/data/com.tencent.mm/MicroMsg/appbrand/pkg/general

拿到apk之后使用脚本进行解包wechat-app-unpack找到nodejs版本进行git拉下来之后执行命令。

arduino 复制代码
node bin/cmd.js xxx.wxapkg
/**
  -pages/*  每个页面的wxss样式文件
  -app-config.json  页面配置的汇总(app.json+各个页面的配置文件)
  -app-service.js 源码js的汇总
  -page-frame.html wxml文件的汇总
*/

解出来的是xxx文件的汇总,并且可读性太低了,还需要使用其他脚本wxappUnpacker进行二次反编译,不过这个包的原作者已经不维护了并且直接rm掉了,但是毕竟开源过还是有很多的fork版本并且进行了些许改良。这个脚本提供了各种文件反编译的脚本也有一键解包apk(不一定适用所有小程序),所以可以忽略掉上个包的解包步骤。

node wuWxapkg.js pkg路径
node wuJs.js app-service.js

如果直接解包过程中遇到vm.js:2 SyntaxError: Unexpected end of input这个错误可以通过这个issues解决,保不齐还有其他错误需要自行解决如果实在无法解决可以结合第一步然后只反编译app-service.js,对于爬虫者来说只要js就可以了。下面是解出来之后的效果看着还是比较还原的。

JS反混淆

chatGpt,jsNice51tools,google搜索js反混淆

零编程类爬虫

反爬虫和反反爬虫

没有100%的反爬虫策略,爬虫可以无限逼近真人。

常用反爬手段

通过UA + Referer来控制访问

无论是浏览器还是爬虫程序,在向服务器发起网络请求的时候都会自己带上UA,可以建立UA白名单,符合白名单才让访问,匹配Referer是否正确。

js 复制代码
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Referer: https://ant.design/

通过IP限制来反爬虫

爬虫一般都会发送大量的请求,如果一个固定的ip在短暂的时间内,快速大量的访问一个网站,就可以被识别出来,针对IP进行封禁。

通过JS脚本来防止爬虫

爬虫终归只是一段代码,并不能像人一样去应对各种变化,如前端可以增加验证码,滑动解锁以及无限debugger禁止调试。

js 复制代码
setInterval(() => { debugger }, 1000)

Robots协议

下面会讲,不过这个协议遵守与否,都在于爬虫的编写者。

接口加密

为防止爬虫直接调用API 来抓取数据, 可以对接口返回的数据进行一个加密, 严格点还需要对接口的参数进行加密。

代码混淆压缩

将有意义的类,字段、方法名称更改为无意义的字符串,尽可能的短小,并且压缩,减小代码可读性。

反反爬虫

暂略.....

爬虫是否合法

Robots(盗亦有道)

robots协议也称爬虫协议、爬虫规则等,是指网站可建立一个robots.txt文件来告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取,而搜索引擎则通过读取robots.txt文件来识别这个页面是否允许被抓取。但是,这个robots协议不是防火墙,也没有强制执行力。

www.taobao.com/robots.txt

www.baidu.com/robots.txt

makefile 复制代码
User-agent: Baiduspider
Disallow: /
User-agent: baiduspider
Disallow: /
  1. User-agent:指定适用指令的爬虫或搜索引擎类型。
  2. Disallow:指定不允许访问的页面或目录。
  3. Allow:指定允许访问的页面或目录(一般在Disallow后面设置)。
  4. Sitemap:指定网站的XML sitemap文件位置,以便搜索引擎更好地了解网站的结构。

在互联网世界中,每天都有不计其数的爬虫在日夜不息地爬取数据,其中恶意爬虫的数量甚至高于非恶意爬虫。遵守Robots协议的爬虫才是好爬虫,但是并不是每个爬虫都会主动遵守Robots协议。

刑事法律风险

www.wangan.com/p/7fy78y928...

www.spp.gov.cn/zdgz/202111...

在使用网络爬虫技术的过程中,从技术本身的使用行为到抓取数据后的使用、传播行为,可能涉及非法侵入计算机信息系统罪、非法获取计算机信息系统数据、非法控制计算机信息系统罪、侵犯公民个人信息罪、诈骗罪、侵犯著作权罪等。

相关推荐
米奇妙妙wuu9 分钟前
react使用sse流实现chat大模型问答,补充css样式
前端·css·react.js
傻小胖14 分钟前
React 生命周期完整指南
前端·react.js
梦境之冢1 小时前
axios 常见的content-type、responseType有哪些?
前端·javascript·http
racerun1 小时前
vue VueResource & axios
前端·javascript·vue.js
m0_548514771 小时前
前端Pako.js 压缩解压库 与 Java 的 zlib 压缩与解压 的互通实现
java·前端·javascript
AndrewPerfect1 小时前
xss csrf怎么预防?
前端·xss·csrf
Calm5501 小时前
Vue3:uv-upload图片上传
前端·vue.js
浮游本尊1 小时前
Nginx配置:如何在一个域名下运行两个网站
前端·javascript
m0_748239831 小时前
前端bug调试
前端·bug
m0_748232921 小时前
[项目][boost搜索引擎#4] cpp-httplib使用 log.hpp 前端 测试及总结
前端·搜索引擎