Python利用爬虫技术实现一个简洁单词翻译程序

0.写在开头

申明/叠甲


该程序只用于个人学习,个人不会也请他人不要用于非法牟利。

需求


  • 学习需要经常翻译某些单词。(整段话在网页翻译并不觉得麻烦,就不考虑这个功能)
  • 翻译界面应置顶、不过多遮挡其他应用的界面。

分析


  • 一开始想下载所有中英互译的单词用数据库(我也不确定?)实现,但太麻烦而且也不知道能不能成功,毕竟我只是想快速实现一个能让我方便一点的功能。
  • 后来学习/参照/抄别人的爬虫程序,外加边学Python边设计的一点点UI实现了功能。

最终实现


图片加载失败



1.爬虫学习

  • 抄归抄,还是得学习一下别人是怎么实现的。
  • 相关的爬虫教程有很多,这里仅记录相关的知识点。

1.1.Ajax

  • Ajax的全称是Asynchronous JavaScript+XML,即异步的JavaScript和XML。
  • Ajax是与服务器交换数据的技术,它在不重载整个页面的情况下,实现对部分网页的更新。

  • 抓取网页在这里,我们要先判断该网页是不是使用Ajax请求
  • 打开网页后右键-检查-Network
  • 在网页输入文字进行翻译,选择Fetch/XHR,抓取异步加载的数据包。
  • 在Headers中的Request Headers发现这行代码(如下),说明该网页是使用Ajax请求的。
X-Requested-With:XMLHttpRequest
  • 也就是我们知道了网页是怎么加载数据的,方便后续我们对数据的查找。



1.2.POST请求

  • 还是在Headers,可以看到Request Method:POST,说明网页是通过POST来提交翻译请求。
  • python使用post请求的代码为——response=requests.post(url,data,headers)
  • 只要知道url、data、headers这些信息,我们就能向服务器发送post请求获取翻译内容。

1.2.1.url

  • 这里的url是指发往服务器接口的地址,并不是一开始我们打开网页的url
  • 还是Headers,在General里找到Request URL,这就是发往服务器接口的地址。
Request URL:https://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule

1.2.2.headers

  • 这里的headers很明显就是对应网页的Headers,但不是所有信息都是必需的,我们记下不可缺少的几项即可。
Cookie: OUTFOX_SEARCH_USER_ID=-1091853714@10.169.0.83; OUTFOX_SEARCH_USER_ID_NCOO=1713181479.4418712; JSESSIONID=aaa_M7byIa4ulgC56O39x; SESSION_FROM_COOKIE=unknown; DICT_UGC=be3af0da19b5c5e6aa4e17bd8d90b28a|; JSESSIONID=abcs0WCkW9GQz9NWlBvmy; ___rl__test__cookies=1662626354497

Referer: https://fanyi.youdao.com/

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.27

1.2.3.data

  • data记录着各种数据,包括我们需要翻译的文本
  • 点击Payload,From Data下所有数据都是需要填入的。
  • 但是,并没有那么容易,有些数据是动态变化的,用来反爬,我们再输入内容进行翻译,比较出哪些是动态的。
#静态数据
from: AUTO
to: AUTO
smartresult: dict
client: fanyideskweb
bv: 01e27702dbb21a6d2b97645ec075ab88
doctype: json
version: 2.1
keyfrom: fanyi.web
action: FY_BY_REALTlME

#动态变化的数据
i: 你好(需要翻译的内容)
salt: 16626444326669
sign: 1775ab088613c2b68870b4fdb49baff3
lts: 1662644432666


1.3.JS文件

  • 前面说到网页是使用Ajax请求加载数据的,所以到js文件查看动态数据是如何生成的。
  • 点击Sources,Ctrl+Shift+f全局搜索salt(或sign/lts)
    PS:快捷键没反应的可能是已经有搜索框,找找Search
  • 可以看见在fanyi.min.js的文件里出现过,打开文件,点击{}展开,Ctrl+f更精准的搜索salt
  • 一番判断后,可以确定动态变化的数据是靠以下代码生成的(在8千多行)
var r = function(e) {
        var t = n.md5(navigator.appVersion)
        r = "" + (new Date).getTime()
        i = r + parseInt(10 * Math.random(), 10);
        return {
        	//推导得ts="" + (new Date).getTime()
            ts: r, 
            bv: t,
            //推导得salt="" + (new Date).getTime()+parseInt(10 * Math.random(),10)
            salt: i, 
            //sign=md5加密后的("fanyideskweb" + e + salt + "Ygy_4c=r#e#4EX^NUGUc5")
            //e通过在该处设置断点,网页输入文字进行翻译获得,e="输入内容",PS:断点用的有点奇奇怪怪的
            sign: n.md5("fanyideskweb" + e + i + "Ygy_4c=r#e#4EX^NUGUc5")
        }
    };
    ……
	var t=e.i,i=r(t);
	……
	//lts=ts
	lts:i.ts, 
  • 这里稍微总结下——
//时间戳,从一个公认时间到现在的毫秒数
lts="" + (new Date).getTime()
//时间戳加一位随机数[0,10)
salt="" + (new Date).getTime()+parseInt(10 * Math.random(),10)
//e是需要翻译的文本,这些字符串合并后通过md5加密
sign=n.md5("fanyideskweb" + e + salt + "Ygy_4c=r#e#4EX^NUGUc5")


1.4.Python编写程序

  • 所有数据已经准备好,用python代替js编写发送请求即可
  • 代码来源于这里,在此基础上,进行了改动方便解释重要功能。
import requests
import time
import random
import hashlib
import re
from tkinter import END, Entry, Tk

class Youdao:
	def __init__(self):
		self.__i=''
		self.__from='AUTO'
		self.__to='AUTO'
		self.__web_url='https://fanyi.youdao.com/'
		self.__requests_url='https://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
		#标头
		self.__headers={
			"User-Agent":'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.27',
			"Cookie":'OUTFOX_SEARCH_USER_ID=-1091853714@10.169.0.83; OUTFOX_SEARCH_USER_ID_NCOO=1713181479.4418712; JSESSIONID=aaa_M7byIa4ulgC56O39x; SESSION_FROM_COOKIE=unknown; DICT_UGC=be3af0da19b5c5e6aa4e17bd8d90b28a|; JSESSIONID=abcs0WCkW9GQz9NWlBvmy; ___rl__test__cookies=1662626354497',
			"Referer":'https://fanyi.youdao.com/'
		}
		#请求出错等待时间
		self.__sleep_second=3
		
	def __get_sign_lastField(self):
		'''获取生成sign所用的加密字符,如上述的"Ygy_4c=r#e#4EX^NUGUc5"'''
	
		#向服务器发送请求,返回一个包含所有服务器资源的Response对象
		web_url_response=requests.get(url=self.__web_url,headers=self.__headers)
	
		'''
		我们需要得到fanyi.min.js的链接,它在网页源代码中的代码为
		<script type="text/javascript" src="https://shared.ydstatic.com/fanyi/newweb/v1.1.10/scripts/newweb/fanyi.min.js"></script>
		采用正则表达式匹配
		.表示匹配除换行以外的任何单字符,*表示零次或多次,后面搭配有?表示匹配尽可能少的字符。如a.*?b表示a开头遇到第一个b即停止。
		()表示若匹配则将括号内的文本内容记录。
		[]表示括号中一组字符有1个匹配即可,后面跟?表示匹配0个或1个,后面跟*表示匹配零个或多个字符。
		a-zA-Z0-9表示a到z、A到Z、0到9的字符。
		'''
		fanyi_min_js=re.findall('<script.*?src="(http[s]?://[a-zA-Z0-9./]*fanyi.min.js)"></script>',web_url_response.text)
		try:
			#获得fanyi.min.js链接返回的网页代码
			fanyi_min_js_response=requests.get(url=fanyi_min_js[0],headers=self.__headers)
		except IndexError as ie:
			print("IndexError:",ie)
			#没有匹配到链接,等待时间结束重新运行函数
			time.sleep(self.__sleep_second)
			self.__get_sign_lastField()
		#正则表达式搜索该段代码——sign: n.md5("fanyideskweb" + e + i + "Ygy_4c=r#e#4EX^NUGUc5"),记录最后一项字符串
		#\将下一个字符记为一个特殊字符或原义字符,\s表示空格
		res=re.search('"fanyideskweb"\s?\+\s?[a-zA-Z]\s?\+\s?[a-zA-Z]\s?\+\s?"(.*?)"',fanyi_min_js_response.text,re.S)
		try:
			#group(1)表示第一个括号(这里只有一个括号)匹配记录的内容
			return res.group(1)
		except AttributeError as ae:
			print("AttributeError:",ae)
			time.sleep(self.__sleep_second)
			self.__get_sign_lastField()
	
	def __translate(self):
		'''翻译实例'''
		#时间戳
		timestamp=time.time()
		#js使用的时间戳单位是毫秒,python的是秒,需换算。
		lts=str(int(timestamp*1000))
		salt=lts+str(random.randint(0,10))
		sign='fanyideskweb'+self.__i+salt+self.__get_sign_lastField()
		sign_md5=hashlib.md5(sign.encode(encoding='UTF-8')).hexdigest()
		data={
			'i':self.__i,
			'from':self.__from,
			'to':self.__to,
			'smartresult':'dict',
			'client':'fanyideskweb',
			'salt':salt,
			'sign':sign_md5,
			'lts':lts,
			'bv':'01e27702dbb21a6d2b97645ec075ab88',
			'doctype': 'json',
			'version': '2.1',
			'keyfrom': 'fanyi.web',
			'action': 'FY_BY_REALTlME'
		}
		response=requests.post(url=self.__requests_url,data=data,headers=self.__headers)
		response.encoding='utf-8'
		#反序列化成python的字典结构
		result=response.json()
		'''
		result内容参照XHR包的Response,如下
		{"errorCode":0,"translateResult":[[{"tgt":"hello","src":"你好"}]],"type":"zh-CHS2en"}
		这是一个字典,字典后面加一个[]表示取相应的值,而下面的[0]表示取列表[]内的第一个元素
		'''
		self.__result=result['translateResult'][0][0]['tgt'] if result['errorCode']==0 else '翻译出错'

	def translate(self,string:str) -> str:
		'''翻译功能'''
		self.__i=string
		self.__translate()
		return self.__result


2.简单UI设计

  • 边学边写了一个简单的UI界面,搭配上述代码使用。

2.1.组件构建

def __init__(self) :
            self.yd=Youdao()
            #主窗口
            self.window=Tk()
            #窗口置顶
            self.window.wm_attributes('-topmost',1)
            #窗口大小
            self.window.geometry('297x30')
            #单行文本框Entry,self.window是父控件,font字体,bg背景颜色,fg字体颜色,exportselection在文本框选中将复制到粘贴板,这里=0关闭了功能,width宽度(单位是0的长度)
            self.input_entry=Entry(self.window,font=('微软雅黑',14),bg='white',fg='black',exportselection=0,width=12)
            #pack管理组件的布局,side="left"指定组件靠左放置,另外还提供expand参数可以将父组件的额外空间也填满
            self.input_entry.pack(side="left")
            self.output_entry=Entry(self.window,font=('微软雅黑',14),bg='white',fg='black',exportselection=0,width=14)
            self.output_entry.pack(side="left")

2.2.绑定事件

  • 在某些部件上将函数绑定(bind)到某些事件上,当触发事件将执行相应的函数。
  • 这里给输入框分别进行了两个绑定,一个是回车事件,一个是获得焦点事件。
def __init__(self):
……
	self.input_entry.bind("<Return>",self.commit)
	self.input_entry.bind("<FocusIn>",self.Focus)
……

#输入框触发回车事件执行commit
def commit(self,event=None):
	#输出框显示翻译内容
	self.output_entry.insert(END,self.yd.translate(self.input_entry.get().replace('\n',' ')))
	#输出框获得焦点,让输入框失去焦点
	self.output_entry.focus_set()

#输入框获得焦点执行Focus
def Focus(self,event=None):
	#清空输入框和输出框的内容
	self.output_entry.delete(0,END)
	self.output_entry.delete(0,END)


3.完整代码

  • 由于编写问题,直接复制会出现tap和空格复用的问题(但我好像没用过空格,可能是格式不兼容?)
  • 完整代码可查看这里,translate.py(推荐运行,无修改),translate_withAnnotation.py(带有注释,即上述代码,仅作理解用)



4.打包生成exe程序

  • 在cmd窗口输入pip install pyinstaller安装pyinstaller
  • 打包要在程序当前路径进行。打开带有程序的界面,点击上方路径空白处输入cmd。
  • 输入pyinstaller -F -w 打包文件.py
  • 生成的exe程序和系统自动生成文件即在当前路径。



5.最后

  • 流程到这里就结束了,但关于Requests、Response、json、编码解码还感觉到有点迷糊,肝完软工作业再继续总结。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值