如何将BS4获取的文本在Tkinter中格式化显示?
D&D法术查询工具:Tkinter文本显示优化问题
我开发了一个个人D&D项目,通过BS4爬取法术信息,用Tkinter构建界面。核心需求是:点击列表框中的法术名称后,对应的法术信息能整齐排版显示在下方的Frame中。
目前我的代码已经接近预期,但存在显示问题:如果直接在循环中调用my_var.set(),只能获取并显示抓取文本的最后一行;现在改用列表存储所有段落再赋值给StringVar,显示的却是列表结构(带方括号和引号),无法达到像单独打印代码那样的自然换行排版效果。
原代码
from tkinter import * from bs4 import BeautifulSoup import requests book = Tk() book.title('Pick a spell') book.geometry("900x900") html_text = requests.get('http://dnd5e.wikidot.com/spell:haste').text soup = BeautifulSoup(html_text, 'html.parser') spell_levels = {"Cantrip": "0", "First Level": "1", "Second Level": "2", "Third Level": "3", "Fourth Level": "4", "Fifth Level": "5", "Sixth Level": "6", "Seventh Level": "7","Eighth Level": "8", "Ninth Level": "9"} spells_by_class = ["All Spells", "Artificer", "Bard", "Cleric", "Druid", "Eldritch Knight", "Paladin", "Ranger", "Arcane Trickster", "Sorcerer", "Warlock", "Wizard"] def spell_text(event): spell_name = soup.find('div', class_ = 'page-title page-header').text spell_info = soup.find_all('p') print(spell_name) for spell in spell_info: set(spell.text) def popup(): response = messagebox.showerror("Error", "There are no spells available for this selection.") def take_spell_box(): my_listbox.delete(0, END) url = 'http://dnd5e.wikidot.com/spells' number = spell_level_clicked.get() number = spell_levels[number] if spell_class_clicked.get() == "All Spells": html_text = requests.get(url).text soup = BeautifulSoup(html_text, 'html.parser') elif spell_class_clicked.get() != "All Spells": dnd_class = spell_class_clicked.get() html_text = requests.get(url + ":" + dnd_class.replace(" ", "-").lower()) soup = BeautifulSoup(html_text.text, 'html.parser') list_of_spells = soup.find('div', id = f'wiki-tab-0-{number}') try: spells = list_of_spells.find_all("a") for spell in spells: my_listbox.insert(END, spell.text) except AttributeError: popup() def define_spell(event): url = 'http://dnd5e.wikidot.com/spell' #selected_spell_info.delete(ANCHOR) selected_spell = my_listbox.get(ANCHOR) or listbox_to_bd.get(ANCHOR) html_text = requests.get(url + ":" + selected_spell.replace(" (HB)", "").replace(" (UA)", "").replace(" ", "-").lower()) soup = BeautifulSoup(html_text.text, 'html.parser') def spell_text(): spell_info_list = [] spell_name = soup.find('div', class_ = 'page-title page-header').text spell_info = soup.find_all('p') for spell in spell_info: spell_info_list.append(spell.text) my_var.set(spell_info_list[:]) spell_text() #Create frame frame_one = Frame(book, width=300, height=200) frame_one.columnconfigure(1, weight=1) #Classes drop down menu and information spell_class_clicked = StringVar(frame_one) spell_class_clicked.set(spells_by_class[0]) spell_class_drop = OptionMenu(frame_one, spell_class_clicked, *spells_by_class) spell_class_drop.config(width=13) spell_class_drop.grid(row=1, column=0, columnspan=2, padx=(10,0), sticky=W+E) #Spell Levels drop down menu and information spell_level_clicked = StringVar(frame_one) spell_level_clicked.set("Cantrip") spell_level_drop = OptionMenu(frame_one, spell_level_clicked, *spell_levels) spell_level_drop.config(width=13) spell_level_drop.grid(row=1, column=2, sticky=W+E) #Submit button to grab the information from the drop down menu submit_button = Button(frame_one, text="Show Selection", command=take_spell_box) submit_button.grid(row=2, column=1, columnspan=2, sticky=N) frame_one.pack() #Create frame and scrollbar frame_two = Frame(book, width=300, height=200, pady=10) listbox_scrollbar = Scrollbar(frame_two, orient=VERTICAL) #List Box: SINGLE, BROWSE, MULTIPLE, EXTENDED my_listbox = Listbox(frame_two, width=30, yscrollcommand=listbox_scrollbar.set, selectmode=SINGLE) #Configure scrollbar listbox_scrollbar.config(command=my_listbox.yview) listbox_scrollbar.grid(row=1, column=3, ipady=56) my_listbox.grid(row=1, column=1, columnspan=2) frame_two.pack() frame_three = Frame(book) my_listbox.bind("<<ListboxSelect>>", define_spell) my_var = StringVar() my_var.set("") selected_spell_info = Label(frame_three, textvariable=my_var) #selected_spell_info.config(text="") selected_spell_info.grid(row=0, column=0, rowspan=5) frame_three.pack() book.mainloop()
预期效果参考
我希望文本显示效果和以下代码打印的结果一致:
from bs4 import BeautifulSoup import requests html_text = requests.get('http://dnd5e.wikidot.com/spell:haste').text soup = BeautifulSoup(html_text, 'html.parser') def spell_text(): spell_name = soup.find('div', class_ = 'page-title page-header').text spell_info = soup.find_all('p') print(spell_name) for spell in spell_info: print(spell.text) spell_text()
解决方案
问题出在StringVar的赋值逻辑和Label组件的局限性:
StringVar仅支持字符串类型赋值,直接传入列表会被转为带括号、引号的字符串格式,需将列表内容用换行符拼接成完整文本。Label组件不适合多行文本的排版展示,改用Text组件不仅支持自然换行,还能配合滚动条处理长文本。
修改后的核心代码部分
- 替换
frame_three中的显示组件:
frame_three = Frame(book) # 替换Label为Text组件,添加垂直滚动条 info_scrollbar = Scrollbar(frame_three, orient=VERTICAL) selected_spell_info = Text(frame_three, width=80, height=20, yscrollcommand=info_scrollbar.set) info_scrollbar.config(command=selected_spell_info.yview) info_scrollbar.grid(row=0, column=1, sticky=N+S) selected_spell_info.grid(row=0, column=0) frame_three.pack()
- 调整
define_spell中的文本插入逻辑:
def define_spell(event): url = 'http://dnd5e.wikidot.com/spell' selected_spell = my_listbox.get(ANCHOR) html_text = requests.get(url + ":" + selected_spell.replace(" (HB)", "").replace(" (UA)", "").replace(" ", "-").lower()) soup = BeautifulSoup(html_text.text, 'html.parser') def spell_text(): # 清空之前的文本内容 selected_spell_info.delete(1.0, END) # 插入法术名称并换行 spell_name = soup.find('div', class_='page-title page-header').text selected_spell_info.insert(END, spell_name + '\n\n') # 依次插入所有段落内容,段落间留空行 spell_info = soup.find_all('p') for spell in spell_info: selected_spell_info.insert(END, spell.text + '\n\n') spell_text()
完整修改后的代码
from tkinter import * from tkinter import messagebox from bs4 import BeautifulSoup import requests book = Tk() book.title('Pick a spell') book.geometry("900x900") spell_levels = {"Cantrip": "0", "First Level": "1", "Second Level": "2", "Third Level": "3", "Fourth Level": "4", "Fifth Level": "5", "Sixth Level": "6", "Seventh Level": "7","Eighth Level": "8", "Ninth Level": "9"} spells_by_class = ["All Spells", "Artificer", "Bard", "Cleric", "Druid", "Eldritch Knight", "Paladin", "Ranger", "Arcane Trickster", "Sorcerer", "Warlock", "Wizard"] def popup(): response = messagebox.showerror("Error", "There are no spells available for this selection.") def take_spell_box(): my_listbox.delete(0, END) url = 'http://dnd5e.wikidot.com/spells' number = spell_level_clicked.get() number = spell_levels[number] if spell_class_clicked.get() == "All Spells": html_text = requests.get(url).text soup = BeautifulSoup(html_text, 'html.parser') elif spell_class_clicked.get() != "All Spells": dnd_class = spell_class_clicked.get() html_text = requests.get(url + ":" + dnd_class.replace(" ", "-").lower()) soup = BeautifulSoup(html_text.text, 'html.parser') list_of_spells = soup.find('div', id = f'wiki-tab-0-{number}') try: spells = list_of_spells.find_all("a") for spell in spells: my_listbox.insert(END, spell.text) except AttributeError: popup() def define_spell(event): url = 'http://dnd5e.wikidot.com/spell' selected_spell = my_listbox.get(ANCHOR) html_text = requests.get(url + ":" + selected_spell.replace(" (HB)", "").replace(" (UA)", "").replace(" ", "-").lower()) soup = BeautifulSoup(html_text.text, 'html.parser') def spell_text(): selected_spell_info.delete(1.0, END) spell_name = soup.find('div', class_='page-title page-header').text selected_spell_info.insert(END, spell_name + '\n\n') spell_info = soup.find_all('p') for spell in spell_info: selected_spell_info.insert(END, spell.text + '\n\n') spell_text() #Create frame frame_one = Frame(book, width=300, height=200) frame_one.columnconfigure(1, weight=1) #Classes drop down menu and information spell_class_clicked = StringVar(frame_one) spell_class_clicked.set(spells_by_class[0]) spell_class_drop = OptionMenu(frame_one, spell_class_clicked, *spells_by_class) spell_class_drop.config(width=13) spell_class_drop.grid(row=1, column=0, columnspan=2, padx=(10,0), sticky=W+E) #Spell Levels drop down menu and information spell_level_clicked = StringVar(frame_one) spell_level_clicked.set("Cantrip") spell_level_drop = OptionMenu(frame_one, spell_level_clicked, *spell_levels) spell_level_drop.config(width=13) spell_level_drop.grid(row=1, column=2, sticky=W+E) #Submit button to grab the information from the drop down menu submit_button = Button(frame_one, text="Show Selection", command=take_spell_box) submit_button.grid(row=2, column=1, columnspan=2, sticky=N) frame_one.pack() #Create frame and scrollbar frame_two = Frame(book, width=300, height=200, pady=10) listbox_scrollbar = Scrollbar(frame_two, orient=VERTICAL) #List Box: SINGLE, BROWSE, MULTIPLE, EXTENDED my_listbox = Listbox(frame_two, width=30, yscrollcommand=listbox_scrollbar.set, selectmode=SINGLE) #Configure scrollbar listbox_scrollbar.config(command=my_listbox.yview) listbox_scrollbar.grid(row=1, column=3, ipady=56) my_listbox.grid(row=1, column=1, columnspan=2) frame_two.pack() frame_three = Frame(book) info_scrollbar = Scrollbar(frame_three, orient=VERTICAL) selected_spell_info = Text(frame_three, width=80, height=20, yscrollcommand=info_scrollbar.set) info_scrollbar.config(command=selected_spell_info.yview) info_scrollbar.grid(row=0, column=1, sticky=N+S) selected_spell_info.grid(row=0, column=0) my_listbox.bind("<<ListboxSelect>>", define_spell) frame_three.pack() book.mainloop()
内容的提问来源于stack exchange,提问作者iamTay
相关产品推荐
相关产品推荐

