多线程批量翻译

合理爬取,不恶意扩大站点压力
本文章仅作示例,请勿用作非法用途


前言

  前几篇教程的爬取,我们一直局限于静态网站,且请求仅限于get。但在实际的开发过程中,动态内容才往往是爬取的核心。在本节内容中,我将带你一步步分析爱词霸的翻译结果获取过程,并伪装请求实现单词翻译。
  本篇内容为本人原创,转载请注明!
  请注意,本篇内容仅限于学习交流,切勿用于商业用途!
结果


分析

网络加载过程

  首先打开爱词霸,并使用 F12 打开开发者工具 【此处使用浏览器为Edge,如使用其他浏览器请参照对应教程打开此界面】 在这里插入图片描述
为了动态分析翻译过程,我们需要切换到 网络(Network) 标签页
在这里插入图片描述
输入任意单词,点击翻译按钮,你将会看到如下请求结果:
在这里插入图片描述

这就是动态翻译的请求。在此条请求上右键-复制链接地址
在这里插入图片描述
得到如下结果

1
https://ifanyi.iciba.com/index.php?c=trans&m=fy&client=6&auth_user=key_ciba&sign=0020c1fc11e96d3a

目前的链接有许多参数尚未明确,我们先留着,之后再做分析。
切换到预览标签页并展开结果,我们惊喜地发现,翻译结果就藏在对应的json中。
在这里插入图片描述
在这里插入图片描述
  目标就是模拟这个请求,那么下一步我们需要弄清楚这里面的参数究竟各代表什么。
  切换到标头页,在这里我们可以查看详细的请求头

在这里插入图片描述
  可以看到,除了明显的get请求外,该请求还携带有表单参数。分析字段不难发现,post的表单带有此次翻译的单词信息。其中,from和to分别是源语言和目标语言,q即为翻译的单词
  我们需要弄清楚其他参数是什么意思。此处采用对比的方法。
  再次翻译另一个单词,得到第二组请求地址和请求头

1
https://ifanyi.iciba.com/index.php?c=trans&m=fy&client=6&auth_user=key_ciba&sign=09467500f66fb4a7

在这里插入图片描述
  上下对比可知,两次请求的url中,仅有sign参数发生改变。可以猜测正是此参数起到加密作用。下一步我们就需要搞清楚,这个参数是怎么来的。
  我们进入重头戏:源码分析!


JS源码分析

  现在我们的目标是找sign,那么最直白的思路就很显然了:看看源代码那些地方出现了”sign”,那些就是比较可疑的地方。
  切换到源代码标签,使用快捷键Ctrl+Shift+F或按下图找到全局搜索面板
在这里插入图片描述先尝试一下直接搜索sign
在这里插入图片描述
得到了9个结果,先进入第一个排查。
使用“优质打印”格式化代码以便于后面的分析。
在这里插入图片描述

使用Ctrl+F打开页内搜索,尝试查找“sign”
但是结果很不理想,有相当大的一部分是关于assign的,不利于结果的分析。
我们大概推测,sign在代码执行过程中应该是某个对象的参数。因此改用”\.sign”重新搜索
在这里插入图片描述
逐个排查,发现这几个signature有较大概率是我们的目标。
在这里插入图片描述
是不是呢?验证一下就知道了。
在源代码左侧的行号旁单击鼠标打上断点,程序就会在运行到此处时暂停。
在这里插入图片描述
重新点击翻译按钮,得到运行结果
在这里插入图片描述
观察上图,从变量的值我们可以看到,在这行代码执行前就已经产生了sign
在这里插入图片描述
  不难看出,变量r可能就是封装好的请求内容,而e则是对应链接。
  那么我们的关注点就在变量e上,而变量e已经带有了对应的sign。于是我们从旁边的调用堆栈标签找到在断点执行位置的上一处,观察此处的代码和变量值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
变量r的值引起了我们的注意,这一串字符非常符合刚刚看到的sign的特征。究竟是不是,我们让代码恢复执行,看看结果。
在这里插入图片描述
最终的请求url中的sign参数正是此处的r
于是为r赋值的这一行代码就非常重要了,摘录如下:

1
2
3
4
5
6
7
8
9
10
11
12
takeResult: function(e) {
var t = p.a.parse(e)
, r = c()("6key_cibaifanyicjbysdlove1".concat(t.q.replace(/(^\s*)|(\s*$)/g, ""))).toString().substring(0, 16);
return g("/index.php?c=trans&m=fy&client=6&auth_user=key_ciba&sign=".concat(r), {
baseURL: "//ifanyi.iciba.com",
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
data: e
})
},

  从这里的js代码可得知,r是调用c()函数得到的结果,传入的参数是一个固定字符串 “6key_cibaifanyicjbysdlove1” 再拼接上翻译的单词,之后调用 replace(/(^\s*)|(\s*$)/g, "")) 的结果。下一步就是看看,这个c()究竟是个什么东西?
  切换标签为控制台页,输入c()
在这里插入图片描述
发现它返回的是一个函数,点击上面的输出结果,可以跳转到对应的源代码位置
在这里插入图片描述
  这是一个陌生的函数。进行到这一步,让我们回顾一下,我们输入的参数是明文,出来的却是英文字符组成的字符串。我们可以猜测,此处的函数作用就是对参数进行某种加密或哈希算法。
  翻阅这个js文件,可以发现大量的位运算操作,这正是加密中常用的操作。因此我们可以就此猜测,这个文件就是一个库文件,功能就是实现常见的字符串加密算法。
在这里插入图片描述

到这里思路换了个方向。我们不妨暂时跳出分析代码的过程,看看JS中有没有这样的库存在。
在这里插入图片描述
搜索的结果几乎全部指向了CryptoJS这个第三方库,那我们去Github看看它的源代码
在这里插入图片描述
尝试搜索刚刚代码中出现的 _createHelper 字段。
在这里插入图片描述
core.js 下,我们发现了这一段:
在这里插入图片描述
与刚才的
在这里插入图片描述
一模一样!!!

寻找具体算法

简单搜索得知,CryptoJS提供了种类丰富的加密算法。要找到此处用的是哪一种就有很多办法。这里我们选择:黑箱理论

黑箱理论,是指对特定的系统开展研究时,人们把系统作为一个看不透的黑色箱子,研究中不涉及系统内部的结构和相互关系,仅从其输入输出的特点了解该系统规律,用黑箱方法得到的对一个系统规律的认识。

寻找一个特定的输入:
在这里插入图片描述
在调试模式下选中生成参数的部分代码,就可以看到此次运行的结果
在这里插入图片描述
上面这一次的运行结果如下:
在这里插入图片描述

随便打开一个用于加密的网站,经过不断尝试,在尝试到MD5的时候,得到了一模一样的结果:
在这里插入图片描述

1
18dc7242de5f73cae3b299a6c8eba326

如此就确定了加密方式:MD5!
一切就绪,下一步自然就是编写代码啦!开干!

代码编写

上面分析得很到位了,每一个参数都给了详尽的解释,此处就不再赘述。参见代码和注释即可。

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
"""
爱词霸 单词翻译。仅用于学习与交流
@copyright : FunnySaltyFish
@date : 2021/04/17 20:38:45
"""
import requests
from hashlib import md5
import json

def get_result(word,source="zh",to="en"):
# 伪装请求头
# 不知道为什么要有这一行的可以看 https://blog.csdn.net/qq_43596067/article/details/105889267
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.57"
}
# 计算sign参数
sign = get_signature(word)[0:16]
# 组装url
url = f"https://ifanyi.iciba.com/index.php?c=trans&m=fy&client=6&auth_user=key_ciba&sign={sign}"
# post表单 数据
data = {
"from":source,
"to":to,
"q":word
}
# 获得的json文本
result = requests.post(url=url,headers=headers,data=data)
return result.text

def get_signature(word:str):
# 按js代码写出来的拼接字符串
raw = "6key_cibaifanyicjbysdlove1"+word.replace(r"(^\s*)|(\s*$)","")
return md5(raw.encode("utf-8")).hexdigest()

def parse_json(text):
# 简单解析获得翻译结果
# 针对其他一些翻译结果 , 您可以自行修改
result = json.loads(text)
return result["content"]["out"]

if __name__ == "__main__":
word = "你好"
raw = get_result(word)
translation = parse_json(raw)
print(f"【{word}】的翻译结果是【{translation}】")
✿✿ヽ(°▽°)ノ✿ 完结撒花 ✿✿ヽ(°▽°)ノ✿
回到目录