基于Python和Tkinter實(shí)現(xiàn)文本文件轉(zhuǎn)換為MP3語(yǔ)音文件
一:效果展示
本項(xiàng)目是基于Python和Tkinter的圖形界面應(yīng)用程序,用于將文本文件轉(zhuǎn)換為MP3語(yǔ)音文件


二:功能描述
(1)文本轉(zhuǎn)語(yǔ)音轉(zhuǎn)換
- 使用
pyttsx3引擎將文本轉(zhuǎn)換為語(yǔ)音并保存為MP3文件 - 支持中文語(yǔ)音合成(默認(rèn)使用"
Microsoft Huihui"語(yǔ)音)
(2)文件處理
- 單文件轉(zhuǎn)換:選擇單個(gè)文本文件進(jìn)行轉(zhuǎn)換
- 批量轉(zhuǎn)換:支持同時(shí)選擇多個(gè)文本文件進(jìn)行批量轉(zhuǎn)換
- 自動(dòng)保存:在預(yù)覽窗口中修改文本內(nèi)容會(huì)自動(dòng)保存到源文件
(3)語(yǔ)音參數(shù)調(diào)節(jié)
- 語(yǔ)速調(diào)節(jié)(1-100范圍)
- 音量調(diào)節(jié)(1-100范圍)
1. 用戶界面功能
(1)主界面布局
- 標(biāo)題欄:顯示應(yīng)用名稱"文本轉(zhuǎn)語(yǔ)音轉(zhuǎn)換器"和幫助按鈕
- 左右分欄:左側(cè)為輸入和設(shè)置面板,右側(cè)為文本預(yù)覽和轉(zhuǎn)換面板
- 狀態(tài)欄:顯示當(dāng)前狀態(tài)信息和版本信息
(2)左側(cè)面板功能
- 文本文件選擇
- 通過(guò)文件對(duì)話框選擇要轉(zhuǎn)換的文本文件
- 自動(dòng)填充輸出目錄為文本文件所在目錄
- 自動(dòng)加載并顯示文本內(nèi)容到預(yù)覽區(qū)域
- 輸出設(shè)置
- 可自定義輸出目錄
- 可設(shè)置輸出文件名前綴(默認(rèn)"wen_qing_zhou")
- 語(yǔ)音設(shè)置
- 顯示當(dāng)前使用的語(yǔ)音名稱
- 語(yǔ)速調(diào)節(jié)滑塊和實(shí)時(shí)顯示
- 音量調(diào)節(jié)滑塊和實(shí)時(shí)顯示
(3)右側(cè)面板功能
- 文本預(yù)覽
- 顯示選定文本文件的內(nèi)容
- 支持直接編輯文本內(nèi)容(自動(dòng)保存到源文件)
- 帶滾動(dòng)條的可編輯文本框
- 轉(zhuǎn)換控制
- "轉(zhuǎn)換為MP3"按鈕:執(zhí)行單文件轉(zhuǎn)換
- "批量轉(zhuǎn)換"按鈕:執(zhí)行多文件批量轉(zhuǎn)換
- "打開(kāi)輸出目錄"按鈕:快速訪問(wèn)保存的MP3文件
- 進(jìn)度指示
- 轉(zhuǎn)換過(guò)程中顯示進(jìn)度條
- 狀態(tài)欄顯示轉(zhuǎn)換進(jìn)度信息
2. 技術(shù)特點(diǎn)
- 多線程處理
- 使用單獨(dú)的線程執(zhí)行轉(zhuǎn)換任務(wù),避免界面凍結(jié)
- 轉(zhuǎn)換過(guò)程中禁用按鈕防止重復(fù)操作
- 錯(cuò)誤處理
- 對(duì)文件操作、語(yǔ)音引擎配置等關(guān)鍵操作進(jìn)行異常捕獲
- 通過(guò)消息框向用戶顯示錯(cuò)誤信息
- 自適應(yīng)參數(shù)映射
- 將用戶友好的1-100范圍參數(shù)映射到語(yǔ)音引擎實(shí)際需要的參數(shù)范圍
- 語(yǔ)速映射采用非線性調(diào)整,提供更精細(xì)的慢速控制
- 跨平臺(tái)支持
- 使用pathlib處理路徑,確??缙脚_(tái)兼容性
- 根據(jù)不同操作系統(tǒng)使用適當(dāng)?shù)姆椒ù蜷_(kāi)目錄
- UI美化
- 使用ttk的clam主題
- 自定義按鈕樣式(如綠色強(qiáng)調(diào)按鈕)
- 一致的字體設(shè)置(微軟雅黑)
3. 使用流程
- 單文件轉(zhuǎn)換
- 點(diǎn)擊"選擇文件"選擇文本文件
- (可選)調(diào)整語(yǔ)音參數(shù)或修改文本內(nèi)容
- 點(diǎn)擊"轉(zhuǎn)換為MP3"開(kāi)始轉(zhuǎn)換
- 轉(zhuǎn)換完成后在輸出目錄查找MP3文件
- 批量轉(zhuǎn)換
- 點(diǎn)擊"批量轉(zhuǎn)換"選擇多個(gè)文本文件
- 設(shè)置統(tǒng)一的輸出參數(shù)
- 程序會(huì)自動(dòng)為每個(gè)文件生成帶序號(hào)的輸出文件名
- 查看結(jié)果
- 使用"打開(kāi)輸出目錄"快速訪問(wèn)輸出文件
- 狀態(tài)欄和消息框顯示轉(zhuǎn)換結(jié)果
4. 輔助功能
- 幫助文檔: 通過(guò)"幫助"按鈕查看詳細(xì)的使用說(shuō)明
- 自動(dòng)保存: 預(yù)覽區(qū)域的修改會(huì)自動(dòng)保存到源文件
- 狀態(tài)反饋: 狀態(tài)欄實(shí)時(shí)顯示當(dāng)前操作狀態(tài)
三:完整代碼
import pathlib
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog, messagebox
import pyttsx3
from PIL import Image, ImageTk
import threading
import os
import platform
import webbrowser
from datetime import datetime
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("文本轉(zhuǎn)語(yǔ)音轉(zhuǎn)換器")
self.geometry("1200x700")
self.minsize(700, 400)
self.resizable(True, True)
self.minsize(700, 400)
self.engine = pyttsx3.init()
self.file_path = tk.StringVar()
self.output_dir = tk.StringVar()
self.rate_var = tk.DoubleVar(value=50.00)
self.volume_var = tk.DoubleVar(value=50.00)
self.display_rate_var = tk.StringVar()
self.display_volume_var = tk.StringVar()
self.update_display_vars()
self.rate_var.trace_add('write', self.update_display_vars)
self.volume_var.trace_add('write', self.update_display_vars)
self.target_voice = "Microsoft Huihui"
self.voice_id = None
self.style = ttk.Style()
self.style.theme_use('clam')
self.style.configure('TButton', font=('微軟雅黑', 10), padding=5)
self.style.configure('TEntry', font=('微軟雅黑', 10))
self.style.configure('TLabel', font=('微軟雅黑', 10))
self.style.configure('TFrame', background='#f0f0f0')
self.style.configure('TLabelframe', font=('微軟雅黑', 10, 'bold'))
self.style.configure('Accent.TButton', foreground='white', background='#4CAF50', font=('微軟雅黑', 11, 'bold'))
self.style.map('Accent.TButton', background=[('active', '#45a049'), ('!active', '#4CAF50')])
self.init_ui()
self.load_voices()
def init_ui(self):
main_frame = ttk.Frame(self, padding=20)
main_frame.pack(fill=tk.BOTH, expand=True)
title_frame = tk.Frame(main_frame, bd=0, highlightthickness=0, bg=self.cget('bg'))
title_frame.pack(fill=tk.X, pady=(0, 20))
title_label = tk.Label(
title_frame,
text="文本轉(zhuǎn)語(yǔ)音轉(zhuǎn)換器",
font=('微軟雅黑', 16, 'bold'),
bg=self.cget('bg')
)
title_label.pack(side=tk.LEFT)
help_btn = ttk.Button(title_frame, text="幫助", command=self.show_help, width=8)
help_btn.pack(side=tk.RIGHT, padx=(10, 0))
pw = ttk.PanedWindow(main_frame, orient=tk.HORIZONTAL)
pw.pack(fill=tk.BOTH, expand=True)
left_panel = ttk.Labelframe(pw, text="輸入和設(shè)置", padding=15)
pw.add(left_panel, weight=2)
right_panel = ttk.Labelframe(pw, text="文本預(yù)覽和轉(zhuǎn)換", padding=15)
pw.add(right_panel, weight=3)
self.create_left_panel(left_panel)
self.create_right_panel(right_panel)
self.status_var = tk.StringVar()
self.status_var.set("準(zhǔn)備就緒")
status_frame = ttk.Frame(main_frame)
status_frame.pack(fill=tk.X, pady=(10, 0))
status_label = ttk.Label(status_frame, textvariable=self.status_var, foreground='#666')
status_label.pack(side=tk.LEFT)
version_label = ttk.Label(status_frame, text="溫輕舟", foreground='#999')
version_label.pack(side=tk.RIGHT)
def update_display_vars(self, *args):
self.display_rate_var.set(f"{self.rate_var.get():.2f}")
self.display_volume_var.set(f"{self.volume_var.get():.2f}")
def create_left_panel(self, parent):
file_frame = ttk.LabelFrame(parent, text="文本文件", padding=10)
file_frame.pack(fill=tk.X, pady=(0, 10))
entry_frame = ttk.Frame(file_frame)
entry_frame.pack(fill=tk.X, expand=True)
self.txt_path = ttk.Entry(entry_frame, textvariable=self.file_path, width=30)
self.txt_path.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
btn_sel = ttk.Button(entry_frame, text="選擇文件", command=self.select_file, width=10)
btn_sel.pack(side=tk.RIGHT)
output_frame = ttk.LabelFrame(parent, text="輸出設(shè)置", padding=10)
output_frame.pack(fill=tk.X, pady=(0, 10))
dir_frame = ttk.Frame(output_frame)
dir_frame.pack(fill=tk.X, expand=True)
self.txt_output_dir = ttk.Entry(dir_frame, textvariable=self.output_dir, width=30)
self.txt_output_dir.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
btn_dir = ttk.Button(dir_frame, text="選擇目錄", command=self.select_output_dir, width=10)
btn_dir.pack(side=tk.RIGHT)
prefix_frame = ttk.Frame(output_frame)
prefix_frame.pack(fill=tk.X, pady=(5, 0))
ttk.Label(prefix_frame, text="文件名前綴:").pack(side=tk.LEFT)
self.prefix_entry = ttk.Entry(prefix_frame, width=15)
self.prefix_entry.pack(side=tk.LEFT, padx=(5, 0))
self.prefix_entry.insert(0, "wen_qing_zhou")
voice_frame = ttk.LabelFrame(parent, text="語(yǔ)音設(shè)置", padding=10)
voice_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Label(voice_frame, text=f"當(dāng)前語(yǔ)音: {self.target_voice}").pack(side=tk.LEFT)
rate_frame = ttk.Frame(voice_frame)
rate_frame.pack(fill=tk.X, pady=(5, 0))
ttk.Label(rate_frame, text="語(yǔ)速:").pack(side=tk.LEFT)
self.rate_scale = ttk.Scale(rate_frame, from_=1, to=100, variable=self.rate_var, orient=tk.HORIZONTAL)
self.rate_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(5, 0))
self.rate_label = ttk.Label(rate_frame, textvariable=self.display_rate_var, width=6)
self.rate_label.pack(side=tk.LEFT)
volume_frame = ttk.Frame(voice_frame)
volume_frame.pack(fill=tk.X, pady=(5, 0))
ttk.Label(volume_frame, text="音量:").pack(side=tk.LEFT)
self.volume_scale = ttk.Scale(volume_frame, from_=1, to=100, variable=self.volume_var, orient=tk.HORIZONTAL)
self.volume_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(5, 0))
self.volume_label = ttk.Label(volume_frame, textvariable=self.display_volume_var, width=6)
self.volume_label.pack(side=tk.LEFT)
def create_right_panel(self, parent):
preview_frame = ttk.LabelFrame(parent, text="文本預(yù)覽", padding=10)
preview_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
self.text_preview = tk.Text(preview_frame, height=8, font=('微軟雅黑', 10), wrap=tk.WORD)
self.text_preview.pack(fill=tk.BOTH, expand=True)
self.text_preview.bind('<<Modified>>', self.on_text_modified)
scrollbar = ttk.Scrollbar(preview_frame, orient=tk.VERTICAL, command=self.text_preview.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.text_preview.configure(yscrollcommand=scrollbar.set)
convert_frame = ttk.Frame(parent)
convert_frame.pack(fill=tk.X, pady=(10, 0))
self.btn_convert = ttk.Button(
convert_frame,
text="轉(zhuǎn)換為MP3",
command=self.convert_to_mp3,
style='Accent.TButton'
)
self.btn_convert.pack(fill=tk.X, pady=(10, 0))
btn_batch = ttk.Button(
convert_frame,
text="批量轉(zhuǎn)換",
command=self.batch_convert,
style='Accent.TButton'
)
btn_batch.pack(fill=tk.X, pady=(5, 0))
btn_open_dir = ttk.Button(
convert_frame,
text="打開(kāi)輸出目錄",
command=self.open_output_dir
)
btn_open_dir.pack(fill=tk.X, pady=(5, 0))
self.progress = ttk.Progressbar(convert_frame, mode='indeterminate', length=200)
def load_voices(self):
try:
voices = self.engine.getProperty('voices')
for voice in voices:
if self.target_voice in voice.name:
self.voice_id = voice.id
break
if not self.voice_id:
if voices:
self.voice_id = voices[0].id
self.status_var.set(f"未找到 {self.target_voice} 語(yǔ)音,將使用 {voices[0].name}")
else:
raise Exception("未找到任何可用語(yǔ)音")
except Exception as e:
self.status_var.set(f"加載語(yǔ)音列表失敗: {str(e)}")
def select_file(self):
current_file = self.file_path.get()
if current_file and os.path.exists(current_file):
initial_dir = os.path.dirname(current_file)
else:
initial_dir = str(pathlib.Path.home())
txt_file = filedialog.askopenfilename(
initialdir=initial_dir,
title="選擇文本文件",
filetypes=(('文本文件', '*.txt'), ('所有文件', '*.*'))
)
if txt_file:
self.file_path.set(txt_file)
file_dir = os.path.dirname(txt_file)
self.output_dir.set(file_dir)
self.status_var.set(f"已選擇文件: {pathlib.Path(txt_file).name}")
try:
with open(txt_file, 'r', encoding='utf8') as f:
content = f.read()
self.text_preview.delete(1.0, tk.END)
self.text_preview.insert(tk.END, content)
self.text_preview.edit_modified(False)
except Exception as e:
messagebox.showwarning("警告", f"無(wú)法讀取文件內(nèi)容: {str(e)}")
def select_output_dir(self):
current_file = self.file_path.get()
if current_file and os.path.exists(current_file):
initial_dir = os.path.dirname(current_file)
elif self.output_dir.get():
initial_dir = self.output_dir.get()
else:
initial_dir = str(pathlib.Path.home())
directory = filedialog.askdirectory(
initialdir=initial_dir,
title="選擇輸出目錄"
)
if directory:
self.output_dir.set(directory)
self.status_var.set(f"輸出目錄設(shè)置為: {directory}")
def on_text_modified(self, event):
if self.text_preview.edit_modified():
current_file = self.file_path.get()
if current_file and os.path.exists(current_file):
try:
content = self.text_preview.get(1.0, tk.END)
with open(current_file, 'w', encoding='utf8') as f:
f.write(content)
self.status_var.set(f"已自動(dòng)保存修改到文件: {os.path.basename(current_file)}")
except Exception as e:
self.status_var.set(f"自動(dòng)保存失敗: {str(e)}")
self.text_preview.edit_modified(False)
def configure_engine(self):
try:
if self.voice_id:
self.engine.setProperty('voice', self.voice_id)
rate = self.rate_var.get()
volume = self.volume_var.get()
if rate <= 50:
progress = rate / 50.0
mapped_rate = 20 + (150 - 20) * (progress ** 1.5)
else:
progress = (rate - 50) / 50.0
mapped_rate = 150 + (300 - 150) * progress
mapped_volume = volume / 100.0
mapped_rate = max(20, min(300, mapped_rate))
mapped_volume = max(0.0, min(1.0, mapped_volume))
self.engine.setProperty('rate', int(mapped_rate))
self.engine.setProperty('volume', mapped_volume)
except Exception as e:
messagebox.showwarning("警告", f"配置語(yǔ)音引擎失敗: {str(e)}")
def convert_to_mp3(self):
if not self.file_path.get():
messagebox.showwarning("警告", "請(qǐng)先選擇文本文件!")
return
def convert_thread():
try:
self.btn_convert.state(['disabled'])
self.progress.pack(fill=tk.X, pady=(10, 0))
self.progress.start()
self.status_var.set("正在轉(zhuǎn)換...")
self.update()
text = self.text_preview.get(1.0, tk.END)
if not text.strip():
raise ValueError("文本內(nèi)容為空!")
self.configure_engine()
prefix = self.prefix_entry.get() or "TTS_Output"
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_name = f"{prefix}_{timestamp}.mp3"
output_dir = self.output_dir.get() or os.path.dirname(self.file_path.get()) or str(pathlib.Path.home())
output_path = pathlib.Path(output_dir) / output_name
self.engine.save_to_file(text, str(output_path))
self.engine.runAndWait()
self.progress.stop()
self.progress.pack_forget()
self.status_var.set(f"轉(zhuǎn)換成功! 文件已保存為: {output_path.name}")
messagebox.showinfo("成功", f"文件轉(zhuǎn)換成功!\n已保存為: {output_path.name}")
except Exception as e:
self.progress.stop()
self.progress.pack_forget()
self.status_var.set("轉(zhuǎn)換失敗")
messagebox.showerror("錯(cuò)誤", f"轉(zhuǎn)換過(guò)程中出錯(cuò):\n{str(e)}")
finally:
self.btn_convert.state(['!disabled'])
threading.Thread(target=convert_thread, daemon=True).start()
def batch_convert(self):
files = filedialog.askopenfilenames(
initialdir=os.path.dirname(self.file_path.get()) if self.file_path.get() else str(pathlib.Path.home()),
title="選擇多個(gè)文本文件",
filetypes=(('文本文件', '*.txt'), ('所有文件', '*.*'))
)
if not files:
return
def batch_thread():
try:
self.btn_convert.state(['disabled'])
self.progress.pack(fill=tk.X, pady=(10, 0))
self.progress.start()
self.status_var.set("正在批量轉(zhuǎn)換...")
self.update()
success_count = 0
prefix = self.prefix_entry.get() or "TTS_Output"
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
self.configure_engine()
for i, file_path in enumerate(files, 1):
if not file_path:
continue
try:
with open(file_path, 'r', encoding='utf8') as f:
text = f.read()
if not text.strip():
continue
file_prefix = f"{prefix}_batch_{timestamp}_{i}"
output_name = f"{file_prefix}.mp3"
output_dir = self.output_dir.get() or os.path.dirname(file_path) or str(pathlib.Path.home())
output_path = pathlib.Path(output_dir) / output_name
self.engine.save_to_file(text, str(output_path))
self.engine.runAndWait()
success_count += 1
self.status_var.set(f"正在批量轉(zhuǎn)換... 已完成 {i}/{len(files)}")
self.update()
except Exception as e:
self.status_var.set(f"轉(zhuǎn)換 {os.path.basename(file_path)} 失敗: {str(e)}")
continue
self.progress.stop()
self.progress.pack_forget()
self.status_var.set(f"批量轉(zhuǎn)換完成! 成功轉(zhuǎn)換 {success_count}/{len(files)} 個(gè)文件")
messagebox.showinfo("完成", f"批量轉(zhuǎn)換完成!\n成功轉(zhuǎn)換 {success_count}/{len(files)} 個(gè)文件")
except Exception as e:
self.progress.stop()
self.progress.pack_forget()
self.status_var.set("批量轉(zhuǎn)換失敗")
messagebox.showerror("錯(cuò)誤", f"批量轉(zhuǎn)換過(guò)程中出錯(cuò):\n{str(e)}")
finally:
self.btn_convert.state(['!disabled'])
def open_output_dir(self):
dir_path = self.output_dir.get()
if not dir_path and self.file_path.get():
dir_path = os.path.dirname(self.file_path.get())
if not dir_path or not os.path.isdir(dir_path):
messagebox.showwarning("警告", "無(wú)效的輸出目錄!")
return
try:
if platform.system() == "Windows":
os.startfile(dir_path)
elif platform.system() == "Darwin":
webbrowser.open(f"file://{dir_path}")
else:
webbrowser.open(dir_path)
except Exception as e:
messagebox.showerror("錯(cuò)誤", f"無(wú)法打開(kāi)目錄:\n{str(e)}")
def show_help(self):
help_text = """文本轉(zhuǎn)語(yǔ)音轉(zhuǎn)換器使用說(shuō)明
1. 基本使用:
- 點(diǎn)擊"選擇文件"按鈕選擇要轉(zhuǎn)換的文本文件
- 在右側(cè)預(yù)覽區(qū)域可以查看和編輯文件內(nèi)容(修改后會(huì)自動(dòng)保存)
- 點(diǎn)擊"轉(zhuǎn)換為MP3"進(jìn)行轉(zhuǎn)換(使用編輯后的內(nèi)容)
2. 高級(jí)設(shè)置:
- 輸出目錄: 默認(rèn)使用當(dāng)前文件所在目錄,可手動(dòng)修改
- 文件名前綴: 設(shè)置輸出文件的前綴名稱
- 語(yǔ)音設(shè)置: 固定使用Microsoft Huihui語(yǔ)音,可調(diào)整語(yǔ)速和音量
3. 批量轉(zhuǎn)換:
- 點(diǎn)擊"批量轉(zhuǎn)換"按鈕可以選擇多個(gè)文本文件進(jìn)行批量轉(zhuǎn)換
- 轉(zhuǎn)換后的文件會(huì)添加序號(hào)以避免重名
4. 其他功能:
- 打開(kāi)輸出目錄: 快速訪問(wèn)保存的MP3文件
"""
messagebox.showinfo("幫助", help_text)
if __name__ == "__main__":
app = Application()
app.mainloop()
以上就是基于Python和Tkinter實(shí)現(xiàn)文本文件轉(zhuǎn)換為MP3語(yǔ)音文件的詳細(xì)內(nèi)容,更多關(guān)于Python文本文件轉(zhuǎn)MP3語(yǔ)音文件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python接口測(cè)試結(jié)果集實(shí)現(xiàn)封裝比較
這篇文章主要介紹了Python接口測(cè)試結(jié)果集比較封裝,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05
Java實(shí)現(xiàn)的執(zhí)行python腳本工具類示例【使用jython.jar】
這篇文章主要介紹了Java實(shí)現(xiàn)的執(zhí)行python腳本工具類,結(jié)合實(shí)例形式分析了java使用jython.jar執(zhí)行Python腳本的具體操作技巧,需要的朋友可以參考下2018-03-03
Qt通過(guò)QGraphicsview實(shí)現(xiàn)簡(jiǎn)單縮放及還原效果
本文主要介紹通過(guò)QGraphicsview實(shí)現(xiàn)簡(jiǎn)單的縮放以及縮放后還原原始大小,通過(guò)scale可以對(duì)view進(jìn)行放大或縮小,具體內(nèi)容詳情跟隨小編一起看看吧2021-09-09
python如何實(shí)現(xiàn)word批量轉(zhuǎn)HTML
這篇文章主要介紹了python如何實(shí)現(xiàn)word批量轉(zhuǎn)HTML,幫助大家更好的理解和學(xué)習(xí)python,感興趣的朋友可以了解下2020-09-09
python庫(kù)lxml在linux和WIN系統(tǒng)下的安裝
這篇內(nèi)容我們給大家分享了lxml在WIN和LINUX系統(tǒng)下的簡(jiǎn)單快速安裝過(guò)程,有興趣的朋友參考學(xué)習(xí)下。2018-06-06

