python3爬虫之有道翻译(下)


  上一篇我们讲到分析有道翻译接口请求,并利用python3构造参数爬取接口。在本篇中,我将会针对有道翻译接口进行图形化界面的设计。

一、器欲尽其能,必先得其法。

像java中的AWTSwing一样,在python3中也自带了GUI工具包,利用其中的Tkinter组件,我们可以方便快捷的设计我们的图形界面。

PS:需要注意的是,在python2和python3中其导入方式存在差异。

# Python2.x:
from Tkinter import *
# Python3.x:
from tkinter import *

二、操千曲而后晓声,观千剑而后识器。

那么该如何使用该模块呢,通过下面一个Hello World程序我们可以对tkinter有一个简单的认识。

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.pack()
        self.create_widgets()

    def create_widgets(self):
        self.hi_there = tk.Button(self)
        self.hi_there["text"] = "你好\n(点击我)"
        self.hi_there["command"] = self.say_hi
        self.hi_there.pack(side="top")

        self.quit = tk.Button(self, text="退出", fg="red",
                              command=self.master.destroy)
        self.quit.pack(side="bottom")

    def say_hi(self):
        print("大家好!")

root = tk.Tk()
app = Application(master=root)
app.mainloop()

三、只要功夫深,铁杵磨成针

tkinter有了简单的了解后,首先我们在面板上添加文本区组件和按钮组件,将主要功能展示出来

其次,我们针对不同的语言增加翻译选项


重新调整组件和布局


这样,一个简单的GUI界面就实现了,好像有点丑,一股浓浓的windows经典风格。。。

四、不畏浮云遮望眼,只缘身在最高层。

我们前面都是直接使用tkinter模块下的 GUI 组件,这些组件看上去特别复(chou)古(lou),仿佛将我门带回了20年前。
  为了更好的视觉效果,Tkinter后来引入了一个ttk组件作为补充(主要就是简单包装、美化一下),并使用功能更强大的Combobox取代了原来的Listbox,且新增了 LabeledScale(带标签的 Scale)、Notebook(多文档窗口)、Progressbar(进度条)、Treeview(树)等组件。
ttk作为一个模块被放在tkinter包下,使用ttk组件与使用普通的Tkinter组件并没有多大的区别,只要导入ttk模块即可。
  其实,tkinter中还存在一个进阶组件ttk,tk带有17个小部件,其中11个已经存在于tkinter中:Button,Checkbutton,Entry,Frame,Label,LabelFrame,Menubutton,PanedWindow,Radiobutton,Scale和Scrollbar。 6个新的窗口小部件类是:Combobox,Notebook,Progressbar,Separator,Sizegrip和Treeview。所有这些类都是Widget的子类。
导入方式如下:

# Python2.x:
from Tkinter import *
# 导入ttk
from ttk import *

# Python3.x:
from tkinter import *
# 导入ttk
from tkinter import ttk

然后我们利用ttk模块,对组件样式进行优化,并且利用pyperclip模块对剪贴板进行相应的读写操作。

还可利用Progressbar进度条组件,增加进度条显示

以及菜单、多标签和弹出窗体的实现

我们还可利用pyinstaller将程序打包成exe在windows平台上进行方便的使用,这样我们利用python的GUI就完成了有道词典的图形界面制作。

最后,附上完整代码:

# -*- coding:utf-8 -*-
import time
import threading
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as mBox
import hashlib
import json
import random
import pyperclip
import webbrowser
import base64
import os
from urllib import parse
from urllib import request

"""
类说明:有道词典翻译的类
"""
class YOUDAO:
    
    def _quit(self):
        self.exitFlag = False
        mBox.showinfo(title='小y温馨提示', message='感谢您的使用~')
        self.root.quit()
        self.root.destroy()
#         exit()
        
    def _progressGo(self):
        self.proBar.grid(row=0, column=0)
        for i in range(100):
            if not self.exitFlag:
                return
            self.proBar["value"] = i + 1
            self.root.update()
            time.sleep(0.06)
        self.proBar.grid_forget()

    def __init__(self, width=500, height=300):
        self.w = width
        self.h = height
        self.title = '有道词典(特别开心版)'
        self.root = tk.Tk(className=self.title)
        # 绑定关闭窗口为自定义函数
        self.root.protocol("WM_DELETE_WINDOW", self._quit)
        
        self.radio = tk.StringVar()
        self.radio.set('AUTO')
        self.exitFlag = True
        self.errorFlag = 0
        self.nextFlag = 0

        # 创建tabControl
        tabControl = ttk.Notebook(self.root)
        # 创建tab
        tab1 = ttk.Frame(tabControl)
        tabControl.add(tab1, text='O(∩_∩)O~')
        tabControl.pack(expand=1, fill="both")
        
        tab2 = ttk.Frame(tabControl)
        tabControl.add(tab2, text='点我点我')
        tabControl.pack(expand=1, fill="both")
        ttk.Label(tab2, text="作者长得帅").pack(pady=100)
        
        # 创建Frame空间
        # pack控件布局
        frame1 = ttk.Frame(tab1)
        frame2 = ttk.LabelFrame(tab1, text='翻译区')
        frame3 = ttk.Frame(tab1)
        
        # 创建MenuBar
        menuBar = tk.Menu(self.root)
        self.root.config(menu=menuBar)
        # 创建menu
        menu1 = tk.Menu(menuBar, tearoff=0)
        menuBar.add_cascade(label='菜单', menu=menu1)
        # 添加菜单项
        menu1.add_command(label='关于作者', command=lambda:webbrowser.open('http://inplus.top'))
        menu1.add_separator()
        menu1.add_command(label='退出', command=self._quit)
        
        toLangDictList = [{'自动':'AUTO'}, {'汉->日':'ja'}, {'汉->韩':'ko'}, {'汉->法':'fr'}, {'汉->俄':'ru'}, {'汉->西班牙':'es'}, {'汉->葡萄牙':'pt'}, {'汉->越':'vi'}]

        # 控件内容设置
        # 使用grid布局需要填写行列
        # 使用pack的side布局方式可以依次排开
        # ipadx控制左右内边距,ipady控制上下内边距
        # padx控制左右外边距,padx控制上下外边距
        # row控制行数,column控制列数
        # rowspan控制占据行数,columnspan控制占据列数
        # sticky控制位置,W、N、S、E、W+N等等八个方位
        ttk.Label(frame1, text="模式:").pack(side=tk.LEFT)
        for i in range(len(toLangDictList)):
            textKey = list(toLangDictList[i].keys())[0]
            textValue = list(toLangDictList[i].values())[0]
            # Radiobutton的indicatoron默认为1,当设置成0时,则其外观是Sunken
            tk.Radiobutton(frame1, text=textKey, variable=self.radio, value=textValue, command=lambda:self.text_translateAnsy(1), indicatoron=0).pack(side=tk.LEFT)
        label1 = ttk.Label(frame2, text="请输入要翻译的文本:")
        self.text1 = tk.Text(frame2, height=6, width=35)
        translateButton1 = ttk.Button(frame2, text="翻译▼", command=lambda:self.text_translateAnsy(1))
        translateButton3 = ttk.Button(frame2, text="从剪贴板翻译", command=self.pasteAndExecute)
        label2 = ttk.Label(frame2, text="翻译结果:")
        self.text2 = tk.Text(frame2, height=6, width=35)
        translateButton2 = ttk.Button(frame2, text="自动▲", command=lambda:self.text_translateAnsy(2))
        translateButton4 = ttk.Button(frame2, text="复制结果到剪贴板", command=lambda:pyperclip.copy(self.text2.get(1.0, tk.END)[:-1]))
        
        self.proBar = ttk.Progressbar(frame3, length=200, mode="determinate", orient=tk.HORIZONTAL)
        self.proBar["maximum"] = 100
        self.proBar["value"] = 0
        # ttk需要使用style
        style = ttk.Style()
        style.configure("BW.TLabel", foreground='red', font=('楷体', 12, 'bold'))
        label_tip = ttk.Label(frame3, text='作者:风澈', style="BW.TLabel")

        frame1.pack(pady=10)
        frame2.pack()
        frame3.pack()
        
        label1.grid(row=2, column=0)
        self.text1.grid(row=2, column=1, rowspan=2)
        translateButton1.grid(row=3, column=2)
        translateButton3.grid(row=3, column=0, padx=5)
        label2.grid(row=4, column=0)
        self.text2.grid(row=4, column=1, rowspan=2)
        translateButton2.grid(row=4, column=2)
        translateButton4.grid(row=5, column=0, padx=5)
        
        label_tip.grid(row=1, column=0)

    """
    创建线程,异步调用
    """
    def text_translateAnsy(self, mode):
        if self.nextFlag == 1:
            return
        translateT = threading.Thread(target=self.text_translate, args=(mode,))
        translateT.setDaemon(True)
        translateT.start()
        self.progressT = threading.Thread(target=self._progressGo(), args=(mode,))
        self.progressT.start()

    """
    函数说明:执行翻译
    """
    def text_translate(self, mode):
        self.nextFlag = 1
        # 主通道
        urlMain = 'http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
        # 备用通道
        urlBak = 'http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule'
        # text1翻译至text2
        if mode == 1:
            textGet = self.text1
            textSet = self.text2
            toLang = self.radio.get()
        # text2翻译至text1
        if mode == 2:
            textGet = self.text2
            textSet = self.text1
            toLang = 'AUTO'
        if self.errorFlag == 1:
            urlMain = urlBak
        # 获取将要源text的值,并剔除最后一个字符,即text自带的一个换行符
        contentWord = textGet.get(1.0, tk.END)[:-1]
        # 因为有道网页是textarea,识别\r\n但不识别\n,在此做替换 
        contentWord.replace('\n', '\r\n')
        if not contentWord:
            self.nextFlag = 0
            self.proBar.grid_forget()
            return
        translateResults = self.conn(urlMain, contentWord, 'AUTO' , toLang)
        # 找到翻译结果
        try:
            if translateResults["errorCode"] == 0:
                newResult = ''
                for translateResult in translateResults["translateResult"]:
                    newResult += translateResult[0]['tgt'] + '\n'

            elif translateResults["errorCode"] == 40:
                newResult = '输入个汉语呗~~'
            else:
                mBox.showerror(title='小y温馨提示', message='网络连接异常')
                self.nextFlag = 0
                self.proBar.grid_forget()
                return
        except:
            # 异常时启用备用地址翻译
            if self.errorFlag == 0:
                self.errorFlag = 1
                return self.text_translate(mode)
            mBox.showerror(title='小y温馨提示', message='网络连接异常')
            self.nextFlag = 0
            self.proBar.grid_forget()
            return
        # 结束前设置进度条100,显得更符合逻辑
        self.proBar["value"] = 100
        self.root.update()
        textSet.delete(1.0, tk.END)
        textSet.insert(tk.INSERT, newResult[:-1])
        self.nextFlag = 0
        self.proBar.grid_forget()
 
    """
    函数说明:连接服务器执行翻译
    """
    def conn(self, url, contentWord, fromLang, toLang):
        # 构造有道的加密参数
        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 + contentWord + salt + flowerStr).encode('utf-8')).hexdigest()
        bv = '9deb57d53879cce82ff92bccf83a3e4c'
        # 创建Form_Data字典,存储请求体
        Form_Data = {}
        # 需要翻译的文字
        Form_Data['i'] = contentWord
        # 下面这些都先按照我们之前抓包获取到的数据
        Form_Data['from'] = fromLang
        Form_Data['to'] = toLang
        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对象
        try:
            # 超时时间设置3秒
            response = request.urlopen(req, timeout=3)
        except:
            return
        # 读取信息并解码
        html = response.read().decode('utf-8')
        # 使用JSON
        return json.loads(html)

    '''
从剪贴板粘贴并执行翻译
    '''
    def pasteAndExecute(self):
        self.text1.delete(1.0, tk.END)
        self.text1.insert(tk.INSERT, pyperclip.paste())
        self.text_translateAnsy(1)
        
    """
    函数说明:tkinter窗口居中
    """
    def center(self):
        ws = self.root.winfo_screenwidth()
        hs = self.root.winfo_screenheight()
        x = int((ws / 2) - (self.w / 2))
        y = int((hs / 2) - (self.h / 2))
        self.root.geometry('{}x{}+{}+{}'.format(self.w, self.h, x, y))

    """
    函数说明:loop等待用户事件
    """
    def loop(self):
        # 禁止修改窗口大小
        self.root.resizable(False, False)
        # 窗口居中
        self.center()
        # 设置图标
        self.root.iconbitmap(r'resource\youdao.ico')
        # 光标焦点
        self.text1.focus()
        self.root.mainloop()

if __name__ == '__main__':
    app = YOUDAO()  # 实例化APP对象
    app.loop()  # loop等待用户事件

通过此篇文章,你学会了吗?


参考资料:
https://docs.python.org/3/library/tkinter.html
https://wiki.python.org/moin/TkInter
https://docs.python.org/3/library/tkinter.ttk.html
https://cloud.tencent.com/developer/section/1372347
https://cloud.tencent.com/developer/section/1372352
https://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html

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