python3爬虫之有道翻译(上)


  平时偶尔会用到翻译工具,其中最常用的就是有道翻译了,web端的有道翻译,在早期是直接可以爬到接口来使用的,但自从有道翻译推出他的API服务的时候,就对这个接口做了反爬虫的机制,从而来推广他的付费接口服务。这个反爬虫机制在爬虫领域算是一个非常经典的技术手段,今天我们就来对它一探究竟吧。

一、莫听穿林打叶声,何妨吟啸且徐行。

首先我们使用chrome浏览器打开有道翻译的链接:http://fanyi.youdao.com
然后使用F12ctrl+shift+i唤起“开发者工具”,鼠标右键点击检查一样,也就是审查元素,选择Network网络监听窗口,如下图所示,之后页面中所有的请求都会在这里显示出来:

接着我们在左侧翻译的窗口输入我们需要翻译的文字,比如输入“你好”,然后浏览器就会向有道发起异步ajax请求,在下面就可以看到所有的请求:


我们点开第一条请求,在Headers可以看到,在进行翻译的时候,发送的请求就是图中Request URL后的URL:

接着我们滚动到最下面,可以看到有一个Form Data的地方,这是请求时所携带的参数,这些数据就是在翻译的时候浏览器给服务器发送的数据:


Tip:在我们请求多次后,发现每次翻译时有些参数固定不变,而有些参数会动态变化,这就是我们破解有道反爬虫机制的关键点,后面会讲到。

然后再点击Response,这里就是接口返回的结果:

最后我们选择Application应用窗口,点击左侧Storage-Cookies,选择有道翻译的域名,这里存储的是cookie信息,cookie校验也是反爬虫的常见手段,这也是我们后面需要注意的:

二、工欲善其事,必先利其器。

运行平台:Windows
Python版本:Python3.x
IDE:Eclipse + PyDev插件

到现在为止,我们得到了url和入参,我们就可以简单写一个爬虫,去调用有道翻译的接口了,我们选用python3来进行爬虫编写。
这里使用的是Python3自带的网络请求库urllib,也伪造一下包括cookie在内的请求头,相关代码如下:

import json
from urllib import parse
from urllib import request
    
# 等待用户输入需要翻译的单词
i = input('请输入需要翻译的句子:')
# 有道翻译的url链接
url = 'http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
# 创建Form_Data字典,存储请求体
Form_Data = {}
# 需要翻译的文字
Form_Data['i'] = i
# 下面这些都先按照我们之前抓包获取到的数据
Form_Data['from'] = 'AUTO'
Form_Data['to'] = 'AUTO'
Form_Data['smartresult'] = 'dict'
Form_Data['client'] = 'fanyideskweb'
Form_Data['salt'] = '15326858088180'
Form_Data['sign'] = '4805445cac590750301ad08319a79675'
Form_Data['ts'] = '1532685808818'
Form_Data['bv'] = '9deb57d53879cce82ff92bccf83a3e4c'
Form_Data['doctype'] = 'json'
Form_Data['version'] = '2.1'
Form_Data['keyfrom'] = 'fanyi.web'
Form_Data['action'] = 'FY_BY_REALTIME'
Form_Data['typoResult'] = 'false'
# 对数据进行字节流编码处理
data = parse.urlencode(Form_Data).encode('utf-8')
# 创建Request对象
req = request.Request(url=url, data=data, method='POST')
# 写入header信息
req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36')
req.add_header('cookie', 'OUTFOX_SEARCH_USER_ID=-1626129620@10.168.8.63')
req.add_header('Referer', 'http://fanyi.youdao.com/')
# 传入创建好的Request对象
response = request.urlopen(req, timeout=5)
# 读取信息并进行字节流解码
html = response.read().decode('utf-8')
# 把返回来的json字符串解析成字典
translate_results = json.loads(html)
# 打印返回信息
print("返回的结果是:%s" % translate_results)

我们运行这个文件后,当我们输入的是“你好”的时候,我们可以得到“hello”的这个正确的翻译结果。而当我们输入其他需要翻译的字符串的时候,比如输入“我爱你”,那么就会得到一个错误代码{'errorCode': 50},这就是有道词典的反爬虫机制,接下来我们就来一步一步破解它。

三、横看成岭侧成峰,远近高低各不同。

我们刚才有讲到,在有道翻译web页面尝试进行了多次翻译,并且每次翻译后都去查看相应的网络请求入参,比较每次请求的Form Data的值,我们注意到,除i代表翻译的源字符串以外,saltsign以及ts这三个参数是动态变化的,其他的参数都是固定值。
这里我分别用“great”和“date”两个单词翻译时候Form Data的数据进行比较:

经过多次变换语种等尝试,观察键值的变化,我们可以对请求携带的参数有如下猜测:

  • i:需要进行翻译的字符串
  • from:源语言的语种
  • to:翻译后的语种
  • smartresult:智能结果,固定值
  • client:客户端,固定值
  • salt:加密用到的盐,待定
  • sign:签名字符串,待定
  • ts:毫秒时间戳
  • bv:未知的md5值,固定值
  • doctype:文档类型,固定值
  • version:版本,固定值
  • keyfrom:键来源,固定值
  • action:操作动作,固定值
  • typoResult:是否打印错误,固定值

那么这三个动态参数的值是怎么产生的呢?这里我们可以分析一下,这三个值在每次请求的时候都不一样,只有两种情况:
第一是每次翻译的时候,浏览器会从有道服务器获取这三个值。这样可以达到每次翻译的时候值不同的需求。
第二是在本地,用js代码按照一定的规则生成的。
那么我们首先来看第一个情况,我们可以看到在每次发送翻译请求的时候,并没有一个请求是专门用来获取这几个值的:


所以就可以排除了第一种情况。就只剩下一种可能,那就是在本地自己生成的,如果是在本地自己生成的,那么规则是什么呢?

四、欲穷千里目,更上一层楼。

这里我们点击Elements审查元素,查看网页源代码,查找所有的js文件,找到一个fanyi.min.js

同样,我们在Sources-Page中也可以看到该js文件:

打开该js文件,可以看到该js是被压缩过的,我们复制出来使用在线格式化工具对齐进行格式化,然后把格式化后的代码,复制下来,用编辑器打开,然后搜索salt,可以找到相关的代码片段:

这里我们就可以发现动态值的生成原理了:

  • i:需要进行翻译的字符串的前5000字
  • salt:当前毫秒时间戳与10以内随机数字字符串的拼接
  • sign:"fanyideskweb"+i+salt+"p09@Bn{h02_BIEe]$P^nG"的md5值
  • ts:当前毫秒时间戳

在得到saltsign以及ts的生成原理后,我们就可以开始进一步改写Python代码,来对接有道的接口了:

import json
import random
import time
import hashlib
from urllib import parse
from urllib import request
    
# 等待用户输入需要翻译的单词
i = input('请输入需要翻译的句子:')
# 有道翻译的url链接
url = 'http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
# 构造有道的加密参数
client = "fanyideskweb"
ts = int(time.time() * 1000)
salt = str(ts + random.randint(1, 10))
flowerStr = "p09@Bn{h02_BIEe]$P^nG"
sign = hashlib.md5((client + i + salt + flowerStr).encode('utf-8')).hexdigest()
bv = '9deb57d53879cce82ff92bccf83a3e4c'
# 创建Form_Data字典,存储请求体
Form_Data = {}
# 需要翻译的文字
Form_Data['i'] = i
# 下面这些都先按照我们之前抓包获取到的数据
Form_Data['from'] = 'AUTO'
Form_Data['to'] = 'AUTO'
Form_Data['smartresult'] = 'dict'
Form_Data['client'] = client
Form_Data['salt'] = salt
Form_Data['sign'] = sign
Form_Data['ts'] = ts
Form_Data['bv'] = bv
Form_Data['doctype'] = 'json'
Form_Data['version'] = '2.1'
Form_Data['keyfrom'] = 'fanyi.web'
Form_Data['action'] = 'FY_BY_REALTIME'
Form_Data['typoResult'] = 'false'
# 对数据进行字节流编码处理
data = parse.urlencode(Form_Data).encode('utf-8')
# 创建Request对象
req = request.Request(url=url, data=data, method='POST')
# 写入header信息
req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36')
req.add_header('cookie', 'OUTFOX_SEARCH_USER_ID=-1626129620@10.168.8.63')
req.add_header('Referer', 'http://fanyi.youdao.com/')
# 传入创建好的Request对象
response = request.urlopen(req, timeout=5)
# 读取信息并进行字节流解码
html = response.read().decode('utf-8')
# 把返回来的json字符串解析成字典
translate_results = json.loads(html)
# 打印翻译结果
translate_result = translate_results["translateResult"][0][0]['tgt']
print("翻译的结果是:%s" % translate_result)

运行,输入内容,翻译结果,一气呵成,大功告成。


五、纸上得来终觉浅,绝知此事要躬行。

像有道翻译这样,通过用js在本地生成随机字符串的反爬虫机制,是爬虫经常会遇到的一个问题。授人以鱼不如授人以渔,还是但愿大家能多多实践与练习,在编写爬虫的过程中也能学到一些反爬虫的知识为我所用。希望通过以上的讲解,能为大家提供一种思路,以后再碰到这种问题的时候知道该如何解决,这样本篇文章的目的也就达到了。

写到这里的时候突然又有了新的想法,那就将此篇作为上篇,期待下一篇吧~


特别感谢@Jack-Cui @南窗客斯黄

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343