【Python网络爬虫】2023年新版有道翻译爬虫破解

一.项目背景

在去年下半年,网页版有道翻译上新,使得原来的爬虫接口失效,目前最新的接口用的是https://dict.youdao.com/webtranslate ,接口情况如下图所示:

接口的请求看似简单,但里面加密请求和返回值解密却非常复杂,本文将会介绍该新版有道翻译的爬虫破解过程。


二.请求参数部分

打开F12开发者工具,查看请求的参数:

通过多次不同请求,我们可以发现,这些参数中需要进行特定修改的有以下几个:

  • i: 这个参数是需要进行翻译的文字
  • from: 从什么语言翻译进行翻译,'auto'表示系统自动识别
  • to: 表示翻译到什么语言,为空则为自动识别
  • sign: 加密签名,具体下面介绍
  • mysticTime: 时间戳,应该是请求时的时间戳

其他的参数都比较容易处理,我们关键留意一下sign这个参数,显然,这个sign的参数是用来进行校验的,这个请求是一个带校验参数的请求,通过检验sign的值与请求中的其他参数的值是否对应来进行请求参数的校验,我们来看一下这个参数是怎么生成的。

通过逆向追踪,可以看到这个这个sign的参数是从app.68a22c0c.js里面的第2000多行的这个k函数里面生成的,k函数中的sign调用了上面的w函数,w函数又调用了再上面的A函数,然后可以看到,这里的A函数里面是对参数e进行了md5的哈希,并转化为十六进制,而这个e参数的生成,其实是在w函数中进行多参数字符组合而来的,这里的多参数包含了d、e、u、t,通过打断点的方式,可以跟踪到这四个参数分别其实就是对应一些固定的header参数,唯一有变化的参数正是对应上面的mysticTime时间戳。

核心代码:

r = 'fanyideskweb'
i = 'webfanyi'
e = 'fsdsogkndfokasodnaso'
t = int(round(time.time() * 1000))

p = f"client={r}&mysticTime={t}&product={i}&key={e}"
sign = hashlib.md5(p.encode('utf8')).hexdigest()

三.返回值解密部分

针对post请求返回数据进行解密,通过进行逆向解密,可以看到app.68a22c0c.js这个文件中有相应的解码过程

可以看到,这里用到的是AES加密算法中的CBC模型进行解密,点进去createDecipheriv函数:

其中,CBC解密的key为e参数,iv为n参数,然后我们回到前面的传参部分:

通过打断点进行分析,可以看到,参数a是一个16长度的列表,c参数也是一样,都由一个alloc函数进行构建,alloc函数的第二个参数为一个j函数,j函数中接受一个参数传入

o参数为

"ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl"

n参数为

"ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4"

另一方面,查看o参数和n参数的定义,这两个参数其实是固定的:

在知道了j函数参数的内容之后,再点进去j函数的定义:

和请求头的参数构建一样,这里也是将传入的参数进行md5的转换,不过没有转为16进制

就这样一步一步地进行挖掘,返回值部分的核心代码如下:

def translate(text, key, iv):
    iv = hashlib.md5(iv).digest()
    key = hashlib.md5(key).digest()

    # CBC模式解密
    AES_decrypt = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
    # 返回数据转为byte
    t = base64.b64decode(text, b'-_')
    # 解码
    decrypted_data = AES_decrypt.decrypt(t)
    # 解码数据拆分
    unpadded_message = unpad(decrypted_data, AES.block_size).decode()
    # 解码数据转为json数据结构
    json_data = json.loads(unpadded_message)
    res = json_data['dictResult']['ce']['word']['trs'][0]['#text']

    return res

decodekey = b'ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl'
decodeIv  = b'ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4'
text = make_request('你好', to='')
translate(text, decodekey, decodeIv)

四.完整代码

完整代码如下:

import hashlib
import base64
import time
import json
import js2py
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad, pad

headers = {
    'Accept': 'application/json, text/plain, */*'
    ,'Accept-Encoding': 'gzip, deflate, br'
    ,'Accept-Language': 'zh-CN,zh;q=0.9'
    ,'Connection': 'keep-alive'
    ,'Content-Length': '252'
    ,'Content-Type': 'application/x-www-form-urlencoded'
    ,'Cookie': 'OUTFOX_SEARCH_USER_ID=-128580344@10.169.0.83; OUTFOX_SEARCH_USER_ID_NCOO=1344989105.8974342; _ntes_nnid=9b83b473d066e2e23751e2d758891587,1641266653081; search-popup-show=-1; __yadk_uid=Uby0XrclZI18dTgSjb55uEGqfHqTXOfo'
    ,'Host': 'dict.youdao.com'
    ,'Origin': 'https://fanyi.youdao.com'
    ,'Referer': 'https://fanyi.youdao.com/index.html'
    ,'Sec-Fetch-Dest': 'empty'
    ,'Sec-Fetch-Mode': 'cors'
    ,'Sec-Fetch-Site': 'same-site'
    ,'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
}

def make_request(word, to = ''):
    form = {
        'from': 'auto'
        ,'to': to
        ,'dictResult': 'true'
        ,'keyid': 'webfanyi'
        ,'client': 'fanyideskweb'
        ,'product': 'webfanyi'
        ,'appVersion': '1.0.0'
        ,'vendor': 'web'
        ,'pointParam': 'client,mysticTime,product'
        ,'keyfrom': 'fanyi.web'
    }

    r = 'fanyideskweb'
    i = 'webfanyi'
    e = 'fsdsogkndfokasodnaso'
    t = int(round(time.time() * 1000))

    p = f"client={r}&mysticTime={t}&product={i}&key={e}"
    sign = hashlib.md5(p.encode('utf8')).hexdigest()

    form['i'] = word
    form['sign'] = sign
    form['mysticTime'] = t

    url = 'https://dict.youdao.com/webtranslate'

    res = requests.post(url=url, headers=headers, data=form)

    return res.text

def translate(text, key, iv):
    iv = hashlib.md5(iv).digest()
    key = hashlib.md5(key).digest()

    # CBC模式解密
    AES_decrypt = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
    # 返回数据转为byte
    t = base64.b64decode(text, b'-_')
    # 解码
    decrypted_data = AES_decrypt.decrypt(t)
    # 解码数据拆分
    unpadded_message = unpad(decrypted_data, AES.block_size).decode()
    # 解码数据转为json数据结构
    json_data = json.loads(unpadded_message)
    res = json_data['dictResult']['ce']['word']['trs'][0]['#text']

    return res

if __name__ == '__main__':
    decodekey = b'ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl'
    decodeIv  = b'ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4'
    text = make_request('你好', to='')
    translate(text, decodekey, decodeIv)

  • 7
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

moyuweiqing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值