python3爬蟲之有道翻譯(下)


  上一篇我們講到分析有道翻譯接口請求,并利用python3構(gòu)造參數(shù)爬取接口。在本篇中,我將會(huì)針對(duì)有道翻譯接口進(jìn)行圖形化界面的設(shè)計(jì)。

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

像java中的AWTSwing一樣,在python3中也自帶了GUI工具包,利用其中的Tkinter組件,我們可以方便快捷的設(shè)計(jì)我們的圖形界面。

PS:需要注意的是,在python2和python3中其導(dǎo)入方式存在差異。

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

二、操千曲而后曉聲,觀千劍而后識(shí)器。

那么該如何使用該模塊呢,通過下面一個(gè)Hello World程序我們可以對(duì)tkinter有一個(gè)簡單的認(rèn)識(shí)。

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(點(diǎ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()

三、只要功夫深,鐵杵磨成針

對(duì)tkinter有了簡單的了解后,首先我們在面板上添加文本區(qū)組件和按鈕組件,將主要功能展示出來

其次,我們針對(duì)不同的語言增加翻譯選項(xiàng)


重新調(diào)整組件和布局


這樣,一個(gè)簡單的GUI界面就實(shí)現(xiàn)了,好像有點(diǎn)丑,一股濃濃的windows經(jīng)典風(fēng)格。。。

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

我們前面都是直接使用tkinter模塊下的 GUI 組件,這些組件看上去特別復(fù)(chou)古(lou),仿佛將我門帶回了20年前。
  為了更好的視覺效果,Tkinter后來引入了一個(gè)ttk組件作為補(bǔ)充(主要就是簡單包裝、美化一下),并使用功能更強(qiáng)大的Combobox取代了原來的Listbox,且新增了 LabeledScale(帶標(biāo)簽的 Scale)、Notebook(多文檔窗口)、Progressbar(進(jìn)度條)、Treeview(樹)等組件。
ttk作為一個(gè)模塊被放在tkinter包下,使用ttk組件與使用普通的Tkinter組件并沒有多大的區(qū)別,只要導(dǎo)入ttk模塊即可。
  其實(shí),tkinter中還存在一個(gè)進(jìn)階組件ttk,tk帶有17個(gè)小部件,其中11個(gè)已經(jīng)存在于tkinter中:Button,Checkbutton,Entry,F(xiàn)rame,Label,LabelFrame,Menubutton,PanedWindow,Radiobutton,Scale和Scrollbar。 6個(gè)新的窗口小部件類是:Combobox,Notebook,Progressbar,Separator,Sizegrip和Treeview。所有這些類都是Widget的子類。
導(dǎo)入方式如下:

# Python2.x:
from Tkinter import *
# 導(dǎo)入ttk
from ttk import *

# Python3.x:
from tkinter import *
# 導(dǎo)入ttk
from tkinter import ttk

然后我們利用ttk模塊,對(duì)組件樣式進(jìn)行優(yōu)化,并且利用pyperclip模塊對(duì)剪貼板進(jìn)行相應(yīng)的讀寫操作。

還可利用Progressbar進(jìn)度條組件,增加進(jìn)度條顯示

以及菜單、多標(biāo)簽和彈出窗體的實(shí)現(xiàn)

我們還可利用pyinstaller將程序打包成exe在windows平臺(tái)上進(jìn)行方便的使用,這樣我們利用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)
        # 綁定關(guān)閉窗口為自定義函數(shù)
        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

        # 創(chuàng)建tabControl
        tabControl = ttk.Notebook(self.root)
        # 創(chuàng)建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='點(diǎn)我點(diǎn)我')
        tabControl.pack(expand=1, fill="both")
        ttk.Label(tab2, text="作者長得帥").pack(pady=100)
        
        # 創(chuàng)建Frame空間
        # pack控件布局
        frame1 = ttk.Frame(tab1)
        frame2 = ttk.LabelFrame(tab1, text='翻譯區(qū)')
        frame3 = ttk.Frame(tab1)
        
        # 創(chuàng)建MenuBar
        menuBar = tk.Menu(self.root)
        self.root.config(menu=menuBar)
        # 創(chuàng)建menu
        menu1 = tk.Menu(menuBar, tearoff=0)
        menuBar.add_cascade(label='菜單', menu=menu1)
        # 添加菜單項(xiàng)
        menu1.add_command(label='關(guān)于作者', command=lambda:webbrowser.open('http://inplus.top'))
        menu1.add_separator()
        menu1.add_command(label='退出', command=self._quit)
        
        toLangDictList = [{'自動(dòng)':'AUTO'}, {'漢->日':'ja'}, {'漢->韓':'ko'}, {'漢->法':'fr'}, {'漢->俄':'ru'}, {'漢->西班牙':'es'}, {'漢->葡萄牙':'pt'}, {'漢->越':'vi'}]

        # 控件內(nèi)容設(shè)置
        # 使用grid布局需要填寫行列
        # 使用pack的side布局方式可以依次排開
        # ipadx控制左右內(nèi)邊距,ipady控制上下內(nèi)邊距
        # padx控制左右外邊距,padx控制上下外邊距
        # row控制行數(shù),column控制列數(shù)
        # rowspan控制占據(jù)行數(shù),columnspan控制占據(jù)列數(shù)
        # sticky控制位置,W、N、S、E、W+N等等八個(gè)方位
        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默認(rèn)為1,當(dāng)設(shè)置成0時(shí),則其外觀是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="翻譯結(jié)果:")
        self.text2 = tk.Text(frame2, height=6, width=35)
        translateButton2 = ttk.Button(frame2, text="自動(dòng)▲", command=lambda:self.text_translateAnsy(2))
        translateButton4 = ttk.Button(frame2, text="復(fù)制結(jié)果到剪貼板", 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='作者:風(fēng)澈', 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)

    """
    創(chuàng)建線程,異步調(diào)用
    """
    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()

    """
    函數(shù)說明:執(zhí)行翻譯
    """
    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的值,并剔除最后一個(gè)字符,即text自帶的一個(gè)換行符
        contentWord = textGet.get(1.0, tk.END)[:-1]
        # 因?yàn)橛械谰W(wǎng)頁是textarea,識(shí)別\r\n但不識(shí)別\n,在此做替換 
        contentWord.replace('\n', '\r\n')
        if not contentWord:
            self.nextFlag = 0
            self.proBar.grid_forget()
            return
        translateResults = self.conn(urlMain, contentWord, 'AUTO' , toLang)
        # 找到翻譯結(jié)果
        try:
            if translateResults["errorCode"] == 0:
                newResult = ''
                for translateResult in translateResults["translateResult"]:
                    newResult += translateResult[0]['tgt'] + '\n'

            elif translateResults["errorCode"] == 40:
                newResult = '輸入個(gè)漢語唄~~'
            else:
                mBox.showerror(title='小y溫馨提示', message='網(wǎng)絡(luò)連接異常')
                self.nextFlag = 0
                self.proBar.grid_forget()
                return
        except:
            # 異常時(shí)啟用備用地址翻譯
            if self.errorFlag == 0:
                self.errorFlag = 1
                return self.text_translate(mode)
            mBox.showerror(title='小y溫馨提示', message='網(wǎng)絡(luò)連接異常')
            self.nextFlag = 0
            self.proBar.grid_forget()
            return
        # 結(jié)束前設(shè)置進(jìn)度條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()
 
    """
    函數(shù)說明:連接服務(wù)器執(zhí)行翻譯
    """
    def conn(self, url, contentWord, fromLang, toLang):
        # 構(gòu)造有道的加密參數(shù)
        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'
        # 創(chuàng)建Form_Data字典,存儲(chǔ)請求體
        Form_Data = {}
        # 需要翻譯的文字
        Form_Data['i'] = contentWord
        # 下面這些都先按照我們之前抓包獲取到的數(shù)據(jù)
        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'
        # 對(duì)數(shù)據(jù)進(jìn)行字節(jié)流編碼處理
        data = parse.urlencode(Form_Data).encode('utf-8')
        # 創(chuàng)建Request對(duì)象
        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/')
        # 傳入創(chuàng)建好的Request對(duì)象
        try:
            # 超時(shí)時(shí)間設(shè)置3秒
            response = request.urlopen(req, timeout=3)
        except:
            return
        # 讀取信息并解碼
        html = response.read().decode('utf-8')
        # 使用JSON
        return json.loads(html)

    '''
從剪貼板粘貼并執(zhí)行翻譯
    '''
    def pasteAndExecute(self):
        self.text1.delete(1.0, tk.END)
        self.text1.insert(tk.INSERT, pyperclip.paste())
        self.text_translateAnsy(1)
        
    """
    函數(shù)說明: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))

    """
    函數(shù)說明:loop等待用戶事件
    """
    def loop(self):
        # 禁止修改窗口大小
        self.root.resizable(False, False)
        # 窗口居中
        self.center()
        # 設(shè)置圖標(biāo)
        self.root.iconbitmap(r'resource\youdao.ico')
        # 光標(biāo)焦點(diǎn)
        self.text1.focus()
        self.root.mainloop()

if __name__ == '__main__':
    app = YOUDAO()  # 實(shí)例化APP對(duì)象
    app.loop()  # loop等待用戶事件

通過此篇文章,你學(xué)會(huì)了嗎?


參考資料:
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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容