Skip to content

指纹检测

更新: 2025/2/24 字数: 0 字 时长: 0 分钟

写爬虫的时候,觉得只要自己每次请求都使用不同的代理IP,每次请求的Headers都写得跟浏览器的一模一样,就不会被网站发现,但实际上还有一个东西是不变的,叫做“指纹”。我们可以将指纹理解为一串带有特征字符的字符串,其实就是一组哈希值。

两种API内容

现在的指纹都是安全厂商/逆向用户意淫出来的名词,在之前我们都称之为 WebAPI,主要是以下内容:

全局相关:window,document
环境相关:navigator(包括经纬度在内都在这个接口里),screen,history
请求相关:XMLHttpRequest  fetch worker
dom相关:canvas,所有对dom节点操作,包括jquery等三方库以及自设导入接口
数据库相关:Storage  IndexedDB  cookie
其他:caches WebGL AudioContext  WebRTC

与WebAPI相对应的就是NodeApi,主要是以下内容:

全局变量 global
导包引擎  require 【这一条很危险】
可被重写的全局
绝大多数的webAPI
全部的dom节点

具体参看下面两个网站:

http://nodejs.cn/api/path.html                    nodejsAPI
https://developer.mozilla.org/zh-CN/docs/Web/API  webAPI

为了后面好讲解,我们统一都称为指纹。

JA3指纹

现在市面上所提到的指纹,基本都是指一种叫JA3指纹

JA3算法

**JA3指纹是通过一种叫做JA3算法生成的,该算法收集了 SSL 请求里面的信息,包括但不限于 SSL/TLS 版本,Cipher Suites数量,浏览器扩展列表,elliptic curves等等,通过这一系列参数综合起来生成一个指纹字符串。**也许你跟一些人的 Cipher Suites 数量相同,你跟另外一些人的浏览器扩展数相同,你又跟另外一些人的 TLS 版本号相同……但是所有这些参数全部相同的人,就非常少了,而在这非常少的人里面,这些人还同时访问同一个网站的可能性就更小了。**所以,网站用 JA3算法,可以近似认为,在一段时间内,指纹字符串相同的连续请求,有极大概率是来自同一个人。**使用Wireshark展示的Client Hello数据包示例:

640

这些字段的顺序如下所示:

Version  TLS版本
Ciphers  TLS可选择的加密套件
Extensions  浏览器扩展
EllipticCurves  椭圆曲线算法
EllipticCurvePointFormats  椭圆曲线标准

根据这些字段的顺序和内容,生成对应的JA3字符串,以及对应32位字符的MD5哈希值,它就是客户端TLS的JA3指纹:

6402

可以看到,JA3字符串以 , 分割,第一个数 771 其对应的就是上面第一个字段内容 Version: TLS 1.2 (0x0303) 最后的十六进制数值,后面就依次类推了。还有要注意的就是TLS版本,有的版本第四个字段 EllipticCurves 值改成了 supported_groups 值:

6403

浏览器指纹

除了通过Wireshark抓包查看ja3指纹,还可以通过网址 https://ja3er.com/json 查看ja3指纹,该网址会返回3个字段分别为:

ja3_hash    ja3指纹哈希
ja3         ja3指纹
User-Agent  用户代理头

相同浏览器不同版本:可以看到同样都是Chrome浏览器,一个92版本,一个96版本,但是他们访问,返回的指纹和哈希都是一样。

20211210171139

不同浏览器:可以看不同浏览器的指纹和哈希都是不一样的。

20211210172014

最后,我们可以得出一个总结,每种浏览器都有自己的指纹,也就意味着相同的浏览器的指纹是一样的。

爬虫指纹

和浏览器类似,爬虫也有指纹,爬虫的指纹也不是唯一的。

在不改变请求头的情况下访问该站点:可以看到当只在一台机器上时,爬虫指纹是不会发生变化的;当换了一台机器后;是会发生变化的。

python
import requests

res = requests.get('https://ja3er.com/json')
print((res.json()).get("ja3_hash"))

res = requests.get('https://ja3er.com/json')
print((res.json()).get("ja3_hash"))

'''
第一台电脑输出:
3e185b41c1418c77e11390421fca3512
3e185b41c1418c77e11390421fca3512
第二胎电脑输出:
ed6dfd54b01ebe31b7a65b88abfa7297
ed6dfd54b01ebe31b7a65b88abfa7297
'''

在改变请求头的情况下访问该站点:可以看到爬虫指纹没有发生变化。

python
import requests

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36"}
res = requests.get('https://ja3er.com/json', headers=headers)
print((res.json()).get("ja3_hash"))

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36 Edg/96.0.1054.43"}
res = requests.get('https://ja3er.com/json', headers=headers)
print((res.json()).get("ja3_hash"))

'''
输出:
3e185b41c1418c77e11390421fca3512
3e185b41c1418c77e11390421fca3512
'''

在使用代理的情况下访问该站点:可以看到爬虫指纹没有发生变化。

python
import requests

proxies = {
    "http": "http://598020642:[email protected]:15977",
    "https": "https://598020642:[email protected]:15977"
}
res = requests.get('https://ja3er.com/json', proxies=proxies)
print((res.json()).get("ja3_hash"))

proxies = {
    "http": "http://598020642:[email protected]:20456",
    "https": "https://598020642:[email protected]:20456"
}
res = requests.get('https://ja3er.com/json', proxies=proxies)
print((res.json()).get("ja3_hash"))

'''
输出:
3e185b41c1418c77e11390421fca3512
3e185b41c1418c77e11390421fca3512
'''

在使用不同库的情况下访问该站点:可以看到爬虫指纹发生变化。

python
import httpx
import requests

res = requests.get('https://ja3er.com/json')
print((res.json()).get("ja3_hash"))

res = httpx.get('https://ja3er.com/json')
print((res.json()).get("ja3_hash"))

'''
输出:
3e185b41c1418c77e11390421fca3512
f320b573a00b0a6ab6f3005486004da9
'''

这里可以得出一个结论就是:爬虫的指纹是不会随着你访问次数、更换 IP 或者 User-Agent 而改变的,而是和使用的第三方库内部算法以及不同机器的客户端有关,但他们的指纹每次请求也是固定的。

改变指纹

**如果上面的地址是一个加了指纹检测机制的网站,只要网站发现某个拥有特定指纹的客户端持续高频率请求网站,那么它轻松就能把我给屏蔽了,所以使用 requests 请求网站的时候,需要修改 JA3指纹。通过上面的认识可知道 JA3 指纹里面,很大的一块就是 Cipher Suits,也就是加密算法。**而 requests 里面默认的加密算法在 /python安装路径/site-packages/urllib3/util/ssl_.py 路径,如下:

64018

通过冒号分割了不同的加密算法,每一种顺序其实就对应了一个 JA3 指纹字符串,只要我们修改这个顺序,就能得到不同的JA3字符串。

ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:!eNULL:!MD5

DH改变指纹

回顾上一节《通信协议》中最后一个反DH检测爬虫就是修改了 requests 里面默认的加密算法,借用这个例子,我们来访问地址:可以看到,爬虫指纹果然发生了变化,同时也说明了 加密算法和指纹的密切关系。

python
import requests

res = requests.get('https://ja3er.com/json')
print((res.json()).get("ja3_hash"))

# 反DH检测爬虫
requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += 'HIGH:!DH:!aNULL'
res = requests.get('https://ja3er.com/json')
print((res.json()).get("ja3_hash"))

'''
输出:
3e185b41c1418c77e11390421fca3512
a8fb57edced8c0a55d0268f819eed019
'''

生成指纹

前面讲到,指纹和加密算法息息相关,因此想要生成不同的指纹爬虫,就需要修改第三库的默认加密算法。**由于 requests 是基于 urllib3 实现的,要修改 Cipher Suits 中的加密算法,需要修改 urllib3 里面的 ssl 上下文,可以看看上面例子中调用的requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS 属性 。**在这里我们还要实现一个新的 HTTP 适配器 (HTTPAdapter),在每次请求的时候,随机更换加密算法,**但需要注意的是 !aNULL:!eNULL:!MD5 就不用修改了,让他们保持在最后。**涉及到的代码如下:

python
import random
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.ssl_ import create_urllib3_context

# requests默认的加密算法顺序
ORIGIN_CIPHERS = ('ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES')

class DESAdapter(HTTPAdapter):
    # 初始化,默认加密算法转为列表后打乱顺序重新组合
    def __init__(self, *args, **kwargs):
        """
        在一般情况下,当我们实现一个子类的时候,__init__的第一行应该是super().__init__(*args, **kwargs),但是由于init_poolmanager和proxy_manager_for是复写了父类的两个方法,这两个方法是在执行super().__init__(*args, **kwargs)的时候就执行的。所以,我们随机设置 Cipher Suits 的时候,需要放在super().__init__(*args, **kwargs)的前面。
        """
        CIPHERS = ORIGIN_CIPHERS.split(':')
        random.shuffle(CIPHERS)
        CIPHERS = ':'.join(CIPHERS)
        self.CIPHERS = CIPHERS + ':!aNULL:!eNULL:!MD5'
        super().__init__(*args, **kwargs)
    
    def init_poolmanager(self, *args, **kwargs):
        context = create_urllib3_context(ciphers=self.CIPHERS)
        kwargs['ssl_context'] = context
        return super(DESAdapter, self).init_poolmanager(*args, **kwargs)

    def proxy_manager_for(self, *args, **kwargs):
        context = create_urllib3_context(ciphers=self.CIPHERS)
        kwargs['ssl_context'] = context
        return super(DESAdapter, self).proxy_manager_for(*args, **kwargs)
    
s = requests.Session()

for _ in range(5):
    # 适配器绑定到https://ja3er.com网址,表示适配器只在特定开头的网址中生效
    s.mount('https://ja3er.com', DESAdapter())
    res = s.get('https://ja3er.com/json')
    print((res.json()).get("ja3_hash"))

'''
输出:
30004cb53e14ceb7e9484fdabc63db86
f5b3199fb96caf32b1933874a3cd4c33
c3865e92e0e5c3b1c26aa0bb0da77493
ae56cffb397ef79fd9fb614c1be193e7
5ad9f93ea7cda1eb8f5a4429ac3f7277
解释:可以看到请求时JA3指纹在不断变化。
'''

反指纹检测

下面的这道题名称为:指纹检测(提示本题会进行TLS检测,会屏蔽掉常用爬虫库且使用默认加密算法的爬虫),题目地址:https://match.yuanrenxue.com/match/,废话不多说,直接开干:

640 (1)

请求分析

看了下,没有加密参数:

640 (2)

然后拿着接口直接请求,没问题,直接返回结果:

640 (3)

用requests上,果然有猫腻:

640 (4)

那就只有抓包看看了,当我打开fiddler抓包时,再次运行程序,数据居然出来了:

640 (5)

但当我把fiddler一关,再次请求,又不行了:

640 (6)

那么思考下,开了fiddler跟没开fidder有什么区别?**最容易想到的代理证书的区别,fiddler伪造https证书,难道是ssl证书问题?我中途也试过请求时直接提交一个证书,也不行的。**还是先上个wireshark抓包吧:

?> 提示:需要用最新版的wireshark才能看到ja3指纹,因为ja3指纹是基于tls1.3的,旧版的wireshark只能看到tls1.2及以下的。

这是浏览器请求一个接口的数据:

640 (7)

这是爬虫程序请求一次接口的数据:

640 (8)

一眼就能看出来区别,所以接下来就是分析区别了。先看浏览器的,选中那个【client Hello】包,展开最后发现了ja3指纹算法

640 (9)

640 (10)

640 (11)

640 (12)

现在我们可以得出结论了:**也就是说,网站使用了ja3指纹检测,识别到了你用的python的请求库去请求,所以直接给你返回【page not found】。**现在再看下python脚本的指纹,找到【client Hello】包,记录下指纹:

640 (13)

640 (14)

总结了下不同机制出现的ja3指纹:从这个就可以很明显的发现不同了,凡是指纹很长的都是不通过的,为什么那么长,说明底层的加密算法用的不一样,这就是原因了。

640 (15)

方法选择

规避检测大概有四种方法可以尝试:

  1. 访问ip指定host绕过waf:通过套了阿里云waf的服务器cname解析域名,这种情况可以直接ping域名获取真实ip,然后请求地址设置为真实ip在 HTTP Header的Host字段中指定域名即可绕过waf的防护,当然这种方式如果目标服务器开启了强制域名访问会失效。经过测试发现不行,直接把域名替换成了host,仍然如此,说明不是cdn式的防护。

  2. 代理中转请求:在本地启动代理服务器,如Burp Suite,发起http请求时指定代理服务器为burp的地址,让burp来进行TLS握手,算是一种曲线救国的方法。这个在windows+fiddler可以,mac下不行,这里也就解释上面为啥开fiddler可行。

  3. 更换request工具库:requests其实是对urllib3的一个封装,那python有没有不用urllib的http request库呢?翻了翻aiohttp的源码发现貌似并没有用urllib3,抓包发现tls指纹和requests也有着明显的差异,但还是识别到并被拦截了下来。

  4. 魔改requests:从根本上解决问题,debug跟踪到了几处可能可以修改TLS握手特征的代码:/python安装路径/site-packages/urllib3/util/ssl_.py

那就只剩第四个方法了,我们借用上面的生成指纹的方法看看:

640 (16)

看ja3指纹的对比就知道,差太多了:

640 (17)

其原因就是:用了random.shuffle来乱序算法,虽然每次出来都不是同一个ja3指纹,但指纹的长度并没有发生改变,而这里我们要欺骗服务器以为用的浏览器访问,而不是爬虫程序访问。

指纹长度

既然长度不一样,那我删点默认的算法试试呢?以前搞过的那些js加密过后很长的加密字段,都是用了很多次加密算法得算法得出来的,所以我大胆猜测,还是加密算法用的太多了,我直接删好几个加密算法看看,我直接把下面选中都删了:

640 (20)

一执行,发现,可行了:

640 (19)

爬虫代码

爬虫代码如下:

python
import re
import random
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.ssl_ import create_urllib3_context

headers = {
    'Host': 'match.yuanrenxue.com',
    'user-agent': 'yuanrenxue.project',
    'accept-encoding': 'gzip, deflate, br',
    'accept': 'application/json, text/javascript, */*; q=0.01',
    'Connection': 'keep-alive',
    'accept-language': 'zh-CN,zh;q=0.9',
    'cookie': '自己的cookie',
    'referer': 'https://match.yuanrenxue.com/match/19',
    'sec-ch-ua': '"Google Chrome";v="94", " Not;A Brand";v="99", "Chromium";v="99"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
    'sec-fetch-dest': 'empty',
    'sec-fetch-mode': 'cors',
    'sec-fetch-site': 'same-origin',
    'Sec-Fetch-Site': 'none',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-User': '?1',
    'Sec-Fetch-Dest': 'document',
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'x-requested-with': 'XMLHttpRequest'
}

# 魔改requests加密算法
ORIGIN_CIPHERS = ( 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:RSA+3DES:!aNULL:!eNULL:!MD5'
)

class DESAdapter(HTTPAdapter):
    # 初始化,默认加密算法转为列表后打乱顺序重新组合
    def __init__(self, *args, **kwargs):
        """
        A TransportAdapter that re-enables 3DES support in Requests.
        """
        CIPHERS = ORIGIN_CIPHERS.split(':')
        random.shuffle(CIPHERS)
        CIPHERS = ':'.join(CIPHERS)
        self.CIPHERS = CIPHERS + ':!aNULL:!eNULL:!MD5'
        super().__init__(*args, **kwargs)

    def init_poolmanager(self, *args, **kwargs):
        context = create_urllib3_context(ciphers=self.CIPHERS)
        kwargs['ssl_context'] = context
        return super(DESAdapter, self).init_poolmanager(*args, **kwargs)

    def proxy_manager_for(self, *args, **kwargs):
        context = create_urllib3_context(ciphers=self.CIPHERS)
        kwargs['ssl_context'] = context
        return super(DESAdapter, self).proxy_manager_for(*args, **kwargs)

# 数值
values = 0
session = requests.Session()
# 共5页数据
for page in range(1, 6):
    # 接口地址
    url = f'https://match.yuanrenxue.com/api/match/19?page={page}'
    print(url)
    # 忽略掉警告
    requests.packages.urllib3.disable_warnings()
    session.mount(f'https://match.yuanrenxue.com/api/match/19?page={page}', DESAdapter())
    response = session.get(url, headers=headers, verify=False)
    print(f'第{page}页:{response.text}')
    for v in re.findall(r'{"value": (-?\d+)}', response.text):
        values += int(v)

# 总值
print(values)

?> 提示:这里多说一下,用前面的DH改变指纹的方法也是可以过该题目的。

!> 注意:上面的JA3字符串还有个很明显的特征,就是浏览器的最后一个数值是【0】,而这里是【0-1-2】,假如服务器验证最后一个数字是【0】呢,然而目前还没找到能直接为【0】的方法。

曲线救国

深入源码

在上面的解决方案里,通过修改JA3加密算法里面的【Ciphers】来生成了不同的JA3指纹,但调试requests请求时,在 http/client.py 文件库里看到,刚开始建立链接时http版本直接写死成了1.0,还有这个建立连接的 tunnel_headers 直接给了个空值:

64022

64023

如果你再配上fiddler或者charles抓包看的时候,明显能看到,在python发送实际请求前的CONNECT请求如下:

64042

而浏览器的这个CONNECT请求是http1.1,且headers是有值的,如下:

64052

遗憾的是,python目前只能改Ciphers里面的算法套件,来生成非默认的ja3指纹,可以骗过检测不是太高的反爬机制。有人可能会问,为啥上面的题可以过?那是因为题目检测的不严,只要ja3指纹长度小于等于浏览器的指纹长度都可以过,但其实还有很多特征的可以检测到的。

详情分析

**python第三方库的https请求是借助openssl库也就是的 ssl_.py 里的 create_urllib3_context 方法发起的,由于openssl库对外提供的方法或者接口是没办法这么高度自定义的,因此Extensions,EllipticCurves,EllipticCurvePointFormats都是没法改的,Ciphers部分最多只能改改算法,所以我之前测试时不管用requests,httpx,还是aiohttp都不行,因为这三个库底层都借助了openssl库发请求。而Chrome有自己的ssl,因此Chrome肯定是不会被禁止的。**所以目前python针对tls指纹的有两个缺陷:

  1. 发起CONNECT请求时http1.0被写死,headers为空(当然这个可以改源码临时解决);
  2. 第二个指纹没法完全自定义,有很多特征被识别。

上面的例子说明,这是Python第三方库的问题,既然Python现有的第三方库不行,那么还有一个方案,用golang突破。

golang突破

我们在golang爬虫代码里加了第三方库 ja3transport,它可以直接伪造ja3,具体使用如下:

go
package main

import (
 "fmt"
 "github.com/CUCyber/ja3transport"
 tls "github.com/refraction-networking/utls"
 "io/ioutil"
 "net/http"
)


type Browser struct {
 Ja3 string
 UserAgent string
}


func req(browser Browser) {
 config := tls.Config{
  InsecureSkipVerify: true,

 }
 tr, _ := ja3transport.NewTransportWithConfig(browser.Ja3, &config)

 client := &http.Client{
  Transport: tr,
 }
 req, _ := http.NewRequest("GET", "https://ja3er.com/json", nil)
 req.Header.Set("User-Agent", browser.UserAgent)
 resp, err := client.Do(req)
 if err != nil {
  fmt.Println(err)
 }

 defer resp.Body.Close()
 content, err := ioutil.ReadAll(resp.Body)
 if err != nil {
  fmt.Println(err)
 }
 fmt.Println(string(content))
}

func main() {
 ja3List := []Browser{
  {Ja3: "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53-10,0-23-65281-10-11-35-16-5-13-18-51-45-43-27,29-23-24,0",UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15"},
  {Ja3: "771,4865-4866-4867-49196-49195-52393-49200-49199-52392-49188-49187-49162-49161-49192-49191-49172-49171-157-156-61-60-53-47-49160-49170-10,0-23-65281-10-11-16-5-13-18-51-45-43-21,29-23-24-25,0", UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"},
  {Ja3: "771,4865-4866-4867-49196-49195-49188-49187-49162-49161-52393-49200-49199-49192-49191-49172-49171-52392-157-156-61-60-53-47-49160-49170-10,65281-0-23-13-5-18-16-11-51-45-43-10-21,29-23-24-25,0", UserAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.1 Mobile/15E148 Safari/604.1"},
 }
 for _, ja3 := range ja3List{
  req(ja3)
 }
}

**这段代码的关键是:tr, _ := ja3transport.NewTransportWithConfig(browser.Ja3, &config),这一行代码,把我们自定义的 JA3指纹字符串传入进去。**这个库在发起请求进行如下操作:

  1. 三次握手之后,到实际要发起client hello包之前,ja3transport把数据包拦截了,即hello包hook的方法。
  2. 然后把原来的ja3指纹修改成了自传递的ja3字段发出client hello,服务端就认了,然后就通过了。

**简单讲,它不是直接修改的tls里的那五个数组套件,而是在5个JA3参数创建好之后进行拦截,用我们自定义的指纹字符串替换,从而达到修改请求的client hello数据包中JA3指纹目的。**运行效果如下图所示:

64021

**最后说明一下,JA3有自己的生成规则,有一些地方可以改,但也不能随便乱改,如果你随意地伪造JA3,假如服务端通过一些方式得知你客户端访问进程跟实际的JA3不匹配,那也可以屏蔽你。**大家可以用测试网站进行测试,看改了以后能不能正确返回不同的 ja3_hash。如果能返回,说明改对了。如果报错了,说明改错了。当然最简单的方法是,收集尽可能多的不同类别不同型号的浏览器指纹和对应的 User-Agent,然后随机选择一对,轮换使用,这样就可以有效规避网站对 JA3字符串频率的检查。

测试脚本

网络请求如果是TLS1.3协议的,requests会被检测,可以使用这个代码测试

import tls_client

session = tls_client.Session(client_identifier='chrome_101', random_tls_extension_order=True)

headers = {
  'authority': 'holmes.taobao.com',
  'accept': 'application/json, text/plain',
  'accept-language': 'zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6',
  'cache-control': 'no-cache',
  'content-type': 'application/json',
  'origin': 'https://www.dingtalk.com',
  'pragma': 'no-cache',
  'referer': 'https://www.dingtalk.com/',
  'sec-ch-ua': '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"',
  'sec-ch-ua-mobile': '?0',
  'sec-ch-ua-platform': '"Windows"',
  'sec-fetch-dest': 'empty',
  'sec-fetch-mode': 'cors',
  'sec-fetch-site': 'cross-site',
  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'
}
url = 'https://holmes.taobao.com/web/corp/customer/searchWithSummary'
data = {
  "pageNo": 2,
  "pageSize": 10,
  "keyword": "成都",
  "orderByType": 5
}
response = session.post(url=url, headers=headers, json=data)

print(response.text)

企业微信截图_16916350086500

企业微信截图_16908595991089

随机指纹

参考文章:https://mp.weixin.qq.com/s/cuFimSLIiNqrLr-qkwn2IQ