How to format text being called from BS4 onto Tkinter?

Question:

This is for a personal D&D project of mine.
I’m trying to get text to come out nicely in my last frame using BS4 and Tkinter.
The goal is that when I click on a new spell in my listbox, the appropriate information will display neatly below what I clicked.

Here is my code:

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()

This is the closest I’ve gotten to what I’m trying to get.
I’ve tried without a list, with my_var.set() in the for loop, but that only gets me the last line of all of the text I am trying to grab.
Not sure what else to try, I’ve searched everything I could think of online to help.

I’m trying to get the text in the previous code to come out like how this prints out:

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()
Asked By: iamTay

||

Answers:

The text can be formatted more nicely using

my_var.set("n".join(spell_info_list))

This will join the items with a newline. To stop the text overflowing, add the wraplength attribute to the Label

selected_spell_info = Label(frame_three, textvariable=my_var, wraplength = 500, justify = "left")

I’ve also added justify = "left" for left text alignment.

You may also want to consider using a Text widget instead as these are better suited for multiline text and also allow for more complex formatting.

Answered By: Henry