返回博客
指南
米赫内亚-奥克塔维安·马诺拉切2023年1月27日阅读时间:10分钟

如何使用 Axios 设置 HTTP 头部以规避检测

如何使用 Axios 设置 HTTP 头部以规避检测

HTTP 通信的工作原理

在深入探讨 HTTP 头部的定义之前,我认为至少对 HTTP 协议的工作原理有一个大致了解是非常重要的。相信大家已经知道,超文本传输协议(HTTP)是当今万维网的基础。从宏观层面来看,它允许信息在服务器与其客户端之间进行传输。这种信息交换的实际流程大致如下:

  • 客户端建立一个新的 TCP 连接
  • 它向服务器发送一条消息,这被称为 HTTP 请求
  • 服务器接收并解析请求
  • 然后,它会向客户端发送一条消息,这被称为 HTTP 响应
  • 客户端读取消息后,继续连接或关闭连接
HTTP 请求示意图,展示方法、URL、协议版本和请求头

今天我们将重点探讨的核心内容是消息,尤其是客户端发送的消息。为了确保服务器与客户端之间的通信高效进行,消息的格式必须符合HTTP协议的规定。就HTTP请求而言,构成消息的要素包括:

该方法描述了我们希望通过请求执行什么操作(例如接收、发送或更新信息等)。

  • 路径,即我们要访问的 URL 地址
  • 我们正在使用的 HTTP 协议版本
  • HTTP 头部用于在请求中附加额外信息和元数据
  • 如果我们使用的是向服务器发送信息的方法(例如 POST 请求),则需要包含请求主体

什么是 Axios 中的 HTTP 头部,它们是如何工作的?

简而言之,HTTP 头部是用于向消息传递附加信息和元数据的字段。 这里所说的“消息”,指的是客户端发送的请求以及服务器发送的响应。因此,服务器和客户端都可以发送和接收头部。例如,假设你想与服务器建立持久连接。默认情况下,HTTP 连接会在每次请求后关闭。要避免这种情况,你只需发送 `Keep-Alive` 头部即可。

说到 Axios 中的 HTTP 头部,其实并没有什么特别之处。正如我们之前讨论过的,Axios 是一个 HTTP 客户端,而我们已经知道,HTTP 客户端可以发送和接收头部。

为什么 Axios 是 JavaScript 的优秀 HTTP 客户端

既然我们已经大致了解了 HTTP 的工作原理,接下来就来谈谈“客户端”。JavaScript 提供了几种通过 HTTP 与服务器进行“程序化交互”的选项。其中最受欢迎的包括 `axios`、`node-fetch` 和 `got`。

在 JavaScript 社区中,对于该选用哪一款存在不同看法。当然,每个包都有其优缺点,但我自己在对这三者进行了一次简单的速度测试后,最终选择了 Axios。

以下是我用来测试速度的脚本:

// index.js

import { get_timing, array_sum } from './helpers.js'

import got from 'got'

import axios from 'axios'

const CALLS = 5

const send = async () => {

   const res = {}

  

   let start = process.hrtime()

   await got('https://httpbin.org/')

   const g = get_timing(start)

   res.got = g

   start = process.hrtime()

   await axios.get('https://httpbin.org/')

   const a = get_timing(start)

   res.axios = a

   start = process.hrtime()

   await fetch('https://httpbin.org/')

   const f = get_timing(start)

   res.fetch = f

   return res

}

let test_results = {

   got: [],

   axios: [],

   fetch: []

}

let avg = {}

console.log(`[i] Process started with ${CALLS} iterations.`)

for (let i = 0; i<=CALLS; i++) {

   let r = await send()

   Object.entries(test_results).map(([key, value]) => test_results[key].push(r[key]))

}

Object.entries(test_results).forEach(([key, value]) => {

       console.log(`\n[+] ${key}`)

       console.log(`    [i] Average: ${array_sum(value)/value.length}`)

       console.log(`    [i] Values: ${value}`)

       avg[key] = array_sum(value)/value.length

   }

)

console.log(`\n🚀🚀🚀 WINNER: ${Object.keys(avg).reduce((key, v) => avg[v] < avg[key] ? v : key)}  [${CALLS} calls sent] 🚀🚀🚀`)

以下是 `helper` 函数:

// helpers.js

export const get_timing = (start) => {

   const NS_PER_SEC = 1e9

   const NS_TO_MS = 1e6

   const diff = process.hrtime(start)

   return (diff[0] * NS_PER_SEC + diff[1]) / NS_TO_MS

}

export const array_sum = (array) => {

   return array.reduce((accumulator, value) => {

     return accumulator + value

   }, 0)

}

我分别测试了每个数据包发送 5、10、20 和 30 个请求,每次迭代 10 次,以下是结果概览:

一张柱状图,用于比较不同HTTP客户端(如fetch、Axios和Got)的平均请求时间

这里所说的“迭代”,指的是脚本的执行次数。使用以下 Bash 命令,会生成一个 .txt 文件,其中包含每次迭代的结果:

~ » for i in {1..10}

do

node got.js > "${i}.txt"

echo "${i} 完成"

done

如您查看详细结果表所见,每个批次的耗时各不相同,有时 Axios 并非最快。 不过总体而言,Axios 的平均响应时间为 387 毫秒,比其他竞争对手快了半秒。Got 和 Fetch 的响应时间非常接近,平均约为 435 毫秒。话虽如此,如果速度对您的项目至关重要,Axios 或许是您最理想的 HTTP 客户端。

如何使用 Axios 发送 HTTP 头部

我个人认为,通过实践学习几乎能立竿见影。既然我们已经掌握了发送 HTTP 头所需的知识和工具,那就开始动手做一个小项目吧。在本节中,我们将创建一个新的 Node 项目,安装 Axios,并利用它向服务器发送 HTTP 头。

设置项目

在继续操作之前,请确保您的机器已配备以下设备:

  • Node.js——您可以从这里下载
  • 一个集成开发环境(IDE)——我日常使用的 IDE 是Visual Studio Code

提示:您可以在终端中输入以下命令,检查是否已安装 Node.js:

~ » node -v  

v19.3.0

现在,让我们创建一个新文件夹,并在 IDE 中打开它。如果你使用的是类 UNIX 系统(Mac 或 Linux),可以在终端中输入以下命令,通过编程方式创建一个新目录:

~ » mkdir axios_project && cd axios_project 

~ » npm init -y

~ » npm i axios

~ » touch index.js

~ » code .

这些命令将:

  • 创建一个新目录(命名为“axios_project”),然后进入该目录
  • 在该目录内初始化一个新的 Node.js 项目
  • 在项目中安装 `axios`
  • 创建一个新的“index.js”文件
  • 在当前项目中打开您的 IDE

编程学习

实际上,使用 Axios 发送 HTTP 头部有几种方法。例如,你可以按照此处的说明使用配置对象,或者使用实例方法,这些方法会自动将你传入的配置与实例配置合并。你还可以使用 `axios.defaults.headers.common` 对象为所有 Axios 请求设置默认头部。

另外,请注意 Axios 是一个基于 Promise 的 HTTP 客户端。这意味着我们必须在异步函数中等待其完成,或者手动解析响应。

考虑到这两个方面,让我们开始实际编写代码吧。我们将要在“index.js”文件中进行操作。为了方便起见,让我们先回顾一下需要做些什么:

  • 在文件中导入 `axios`
  • 定义一个用于存储请求头的配置对象
  • 将配置传递给 `axios` 以发起请求
  • 在终端中打印响应

#1:使用 config 对象发送一个 GET 请求

import axios from "axios"

const config = {

   method: 'GET',

   url: 'https://httpbin.org/headers',

   headers: {

       'HTTP-Axios-Headers': 'This is my custom header.'

   }

}

axios(config)

   .then((response) => {

       console.log(response)

   })

   .catch((err) => {

       console.log(err)

   })

使用 Axios 发送 HTTP 头部再简单不过了。要执行此脚本,只需在终端中运行以下命令:

~ » node index.js  

{

  status: 200,

  statusText: 'OK',

  headers: ...,

  config: ...,

  request: ...,

  data: {

    headers: {

      'Accept': 'application/json, text/plain, */*',

      'Accept-Encoding': 'gzip, compress, deflate, br',

      'Host': 'httpbin.org',

      'Http-Axios-Headers': 'This is my custom header.',

      'User-Agent': 'axios/1.2.2',

      'X-Amzn-Trace-Id': 'Root=1-63b54d94-7656f02113483dfa036c476c'

    }

  }

}

整个响应数据量较大,且遵循以下结构。不过,我们最关注的是 `data` 字段,它包含了从服务器接收到的实际响应。现在请查看上方的响应。请记住,我们向服务器发送了一个自定义标头 `Http-Axios-Headers`,如您所见,服务器确实接收到了该标头。

#2:使用方法别名发送 POST 请求

import axios from "axios"

const data = {

   'foo':'bar'

}

const config = {

   headers: {

       'HTTP-Axios-Headers': 'This is my custom header.'

   }

}

axios.post('http://httpbin.org/post', data, config)

   .then(response => console.log(response.data))

   .catch(err => console.log(err))

请注意,为了发送 POST 请求,我在脚本中添加了一个新的 `data` 对象,并且修改了 URL。现在,如果你运行该脚本,你会发现从服务器接收到的数据如下:

{

  args: {},

  data: '{"foo":"bar"}',

  files: {},

  form: {},

  headers: {

    Accept: 'application/json, text/plain, */*',

    'Accept-Encoding': 'gzip, compress, deflate, br',

    'Content-Length': '13',

    'Content-Type': 'application/json',

    Host: 'httpbin.org',

    'Http-Axios-Headers': 'This is my custom header.',

    'User-Agent': 'axios/1.2.2',

    'X-Amzn-Trace-Id': 'Root=1-63b5508a-3a86493f087662d3169e80ee'

  },

  json: { foo: 'bar' },

  origin: '49.12.221.20',

  url: 'http://httpbin.org/post'

}

如何在 Axios 中使用 HTTP 头部进行网页抓取

如果您计划使用 Axios 进行网页抓取,请注意,大多数网站都设有防护规则,会拦截来自自动化软件(包括网页抓取工具)的请求。

在进行网页抓取时,利用 HTTP 头部(尤其是 `User-Agent` 头部)是一种有效的规避检测的方法。User-Agent 头部向服务器标识客户端的浏览器和操作系统,而 Web 服务器可能会根据这些信息提供不同的内容或阻止请求。通过设置 User-Agent 头部,您可以模拟常见的 Web 浏览器,从而增加绕过某些检测机制的可能性。

以下是一个示例,展示了如何在进行网页抓取时,利用 Axios 设置 User-Agent 头部来避免被检测:

import axios from "axios"

axios.defaults.headers.common['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'

axios({

   method: 'GET',

   url: 'https://httpbin.org/get',

}).then(response => {

   console.log(response.data)

});

请注意,这次我使用了 Axios 的config defaults选项,该选项会将该头部信息应用于所有后续请求。在此示例中,User-Agent 头部被设置为模拟运行于 Windows 10 操作系统上的 Chrome 浏览器。您可以尝试使用不同的 User-Agent 值或多种不同的头部信息,以找出最适合您具体使用场景的方案。

值得注意的是,虽然修改 User-Agent 头部在某些情况下可能有助于避免被检测,但这并非万无一失的方法,Web 服务器仍可能识别出您正在使用网络爬虫。因此,建议结合多种技术手段来避免被检测,并遵守网站的服务条款。

结论

在 Axios(或其他任何 HTTP 客户端)中使用 HTTP 头部,可以提高服务器与客户端之间的通信效率。此外,在构建网页爬虫时,这甚至有助于避免被检测到。事实上,在 WebScrapingAPI,我们正将使用多种用户代理作为基本规避技术之一。

当然,检测并不局限于用户代理,但这确实是一个不错的切入点。这里有一篇关于如何在进行网页抓取时避免IP地址被封禁的实用教程,它将帮助你更好地理解规避机制的运作原理。

顺便问一下,你知道 Web Scraping API 允许你在请求中设置自定义头部吗?如果不知道,可以在此了解更多相关信息。

关于作者
Mihnea-Octavian Manolache,全栈开发工程师 @ WebScrapingAPI
米赫内亚-奥克塔维安-马诺拉什全栈开发工程师

Mihnea-Octavian Manolache 是 WebScrapingAPI 的全栈及 DevOps 工程师,负责开发产品功能并维护确保平台平稳运行的基础设施。

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网页数据。