嘘~ 正在从服务器偷取页面 . . .

ja3-tls指纹


JA3

  • 最近在爬虫练习的时候遇到一个网站,特别神奇
  • 可以识别是否开启了 FD 抓包工具,以及 Python 发出的请求。
  • 一开始看了一头雾水,猜测是跟 FD 证书有关系。
  • 可以通过以下这个网站进行查看 ja3
  • 不管你的代理,请求头怎么换,他网页返回的ja3_hash都是不变的。

原理

  • 简单来说就是通过:分析 SSL/TLS 客户端请求的报头。
  • TLS 是 SSL 的标准版本,名称为:传输层安全协议,如果没有这个协议,相当于在互联网裸奔。
  • 因为 Python 的库的握手是有指纹的(包括 scrapy,aiohttp,httpx),而当请求被 FD 拦截转发之后,指纹就变为了 FD 的,所以通过这个算法,就可以识别正常的浏览器请求,和爬虫请求,以及抓包工具等中间人请求,
  • 上有政策下有对策,经过不懈的 sousuo,还是找到解决方法。
  • 但不建议所有网站都启用,因为会特别慢,每个请求都会重新生成一个新的指纹。
    • 当然你可以全局只生成一次指纹,每次启动爬虫才生成一次。

创建独一无二的指纹

  • 注意 这些只修改了 Python 表层 如果后端检测的比较深层次 还是可以检测到的
    • 如果希望完全过掉 可能需要修改源码 或者改用 GO 语言
  • 想要创建自己独一无二的指纹,只需要修改ORIGIN_CIPHERS 内部加密的顺序
  • 但代码内其实都有随机了,无关紧要。

request

import random

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

ORIGIN_CIPHERS = ('DH+3DES:RSA+3DES:ECDH+AES256:DH+AESGCM:DH+AES256:DH+AES:ECDH+AES128:'
                  'DH+HIGH:RSA+AESGCM:ECDH+3DES:RSA+AES:RSA+HIGH:ECDH+AESGCM:ECDH+HIGH')


class DESAdapter(HTTPAdapter):
    def __init__(self, *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)


headers = {'User-Agent': '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'}
session = requests.Session()
session.headers.update(headers)

ssl = DESAdapter()

for _ in range(5):
    # 设置只绑定在 https://ja3er.com 这个网站
    # s.mount('https://ja3er.com', adapter=DESAdapter())
    # 设置绑定在任何 https 的请求上
    session.mount('https://', adapter=ssl)

    result = session.get('https://ja3er.com/json').json()
    print(result)

aiohttp

  • 这是我 copy 来的,但我的电脑没有办法执行,会报错证书错误
  • 但把他的 url 换成别的就可以,不知道什么原因。
  • 结果不懈的调试,终于解决报错问题。。。
import asyncio
import random
import ssl

import aiohttp

ORIGIN_CIPHERS = ('RSA+3DES:RSA+AES:RSA+AESGCM:ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:ECDH+HIGH:'
                  'DH+HIGH:DH+3DES:RSA+HIGH:DH+AES:ECDH+3DES')


class SSLFactory:
    def __init__(self):
        self.ciphers = ORIGIN_CIPHERS.split(":")

    def __call__(self) -> ssl.SSLContext:
        random.shuffle(self.ciphers)
        ciphers = ":".join(self.ciphers)
        ciphers = ciphers + ":!aNULL:!eNULL:!MD5"

        context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        context.set_ciphers(ciphers)
        return context


sslgen = SSLFactory()
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'}


async def main():
    async with aiohttp.ClientSession() as session:
        for _ in range(5):
            async with session.get("https://ja3er.com/json", headers=headers, ssl=sslgen()) as resp:
                data = await resp.text()
                print(data)


asyncio.get_event_loop().run_until_complete(main())

scrapy

  • 目前 scrapy 还不知道怎么修改指纹
  • 不过已经在 scrapy 项目留言。
  • 目前解决办法就是写个中间件,用 aiohttp 的。
  • 在 github 多次翻阅之后,发现只要修改一个配置参数就可以修改指纹

1.配置文件中直接修改

# 最末尾需要确定是  :!aNULL:!eNULL:!MD5    结尾
DOWNLOADER_CLIENT_TLS_CIPHERS = 'RSA+AES:RSA+3DES:RSA+AESGCM:ECDH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:ECDH+HIGH:DH+HIGH:DH+3DES:RSA+HIGH:DH+AES:ECDH+3DES:!aNULL:!eNULL:!MD5'

2.启动自动随机修改

# 在scrapy中定义一个函数
def ssl():
    ciphers = 'RSA+3DES:RSA+AES:RSA+AESGCM:ECDH+AESGCM:DH+AESGCM:ECDH+AES256' \
              ':DH+AES256:ECDH+AES128:ECDH+HIGH:DH+HIGH:DH+3DES:RSA+HIGH:DH+AES:ECDH+3DES'.split(":")
    random.shuffle(ciphers)
    ciphers = ":".join(ciphers)
    ciphers = ciphers + ":!aNULL:!eNULL:!MD5"
    return ciphers

# 在内部进行配置参数的修改
class PaSpider(scrapy.Spider):
    name = 'pa'

    start_urls = ['https://ja3er.com/json']

    custom_settings = {
        'DOWNLOADER_CLIENT_TLS_CIPHERS': ssl(),
    }

    def start_requests(self):
		***

文章作者: 林木木
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 林木木 !
评论
  目录