You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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组件的局限性:

  1. StringVar仅支持字符串类型赋值,直接传入列表会被转为带括号、引号的字符串格式,需将列表内容用换行符拼接成完整文本。
  2. Label组件不适合多行文本的排版展示,改用Text组件不仅支持自然换行,还能配合滚动条处理长文本。

修改后的核心代码部分

  1. 替换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()
  1. 调整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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 20:37:03