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

如何在同继承自ttk.Frame的Tkinter类之间传递爬取数据以填充Treeview

我完全理解你想保持两个Frame类独立、避免继承耦合的需求,其实不需要让它们互相继承,通过主App类作为中间协调者来传递组件引用,就能完美实现“搜索完成后更新Treeview”的功能,同时保持代码结构清晰。

下面是具体的修改方案和完整代码:

核心修改思路

  1. 让主App类管理两个Frame实例:在App中创建ResultFrame和SearchFrame的实例,并把ResultFrame的引用传递给SearchFrame,这样SearchFrame就知道要把数据传给哪个展示组件。
  2. 修正爬取逻辑的bug:你当前的代码会把所有书的作者/朗读者都设为第一本书的内容,需要改为从当前遍历的product_item中提取信息。
  3. 添加数据清空逻辑:每次搜索前清空旧的搜索结果和Treeview内容,避免结果叠加。

修改后的完整代码

import tkinter as tk
from tkinter import *
from tkinter import ttk
import requests
from bs4 import BeautifulSoup

class ResultFrame(ttk.Frame):
    def __init__(self, container):
        super().__init__(container)
        # field options
        options = {'padx': 10, 'pady': 10}
        # result label frame
        self.result_label = ttk.LabelFrame(self, text='Results')
        self.result_label.grid(row=1, column=0, sticky=tk.W, **options)
        # result tree
        self.tree = ttk.Treeview(self.result_label)
        self.tree['columns'] = ("Title", "Author", "Narrator", "Published")
        self.tree.column("#0", width=0, stretch=NO) # hide the first autogenerated column
        self.tree.column("Title", anchor=W)
        self.tree.column("Author", anchor=W)
        self.tree.column("Narrator", anchor=W)
        self.tree.column("Published", anchor=W)
        # result tree headings
        self.tree.heading('#0', text='') # hide the first autogenerated column
        self.tree.heading('Title', text='Title', anchor=W)
        self.tree.heading('Author', text='Author', anchor=W)
        self.tree.heading('Narrator', text='Narrator', anchor=W)
        self.tree.heading('Published', text='Published', anchor=W)
        self.tree.pack()
        self.grid(padx=10, pady=10, sticky=tk.NSEW)

    def populate_tree(self, data):
        count = 0
        for book in data.items():
            self.tree.insert(
                parent='', index='end', iid=count, text='',
                values=(
                    book[0], book[1]['authors'], book[1]['narrators'], 'not implemented'
                )
            )
            count += 1

class SearchFrame(ttk.Frame):
    def __init__(self, container, result_frame):
        super().__init__(container)
        self.result_frame = result_frame  # 保存ResultFrame实例引用
        self.audiobooks = {}
        # field options
        options = {'padx': 5, 'pady': 5}
        # search label
        self.search_label = ttk.Label(self, text='Search')
        self.search_label.grid(row=0, column=0, sticky=tk.W, **options)
        # search entry
        self.search = tk.StringVar()
        self.search_entry = ttk.Entry(self, textvariable=self.search, width=50)
        self.search_entry.grid(row=0, column=1, **options)
        self.search_entry.focus()
        self.search_button = ttk.Button(self, text="search")
        self.search_button['command'] = self.search_audible
        self.search_button.grid(row=0, column=2, sticky=tk.W, **options)
        # add padding to the frame and show it
        self.grid(padx=10, pady=10, sticky=tk.NSEW)

    def search_audible(self):
        """
        Search audible for the book
        :param self:
        :return:
        """
        # 清空旧数据
        self.audiobooks.clear()
        for item in self.result_frame.tree.get_children():
            self.result_frame.tree.delete(item)
            
        # build the url
        base_url = "https://www.audible.co.jp/search?ipRedirectOverride=true&overrideBaseCountry=true&keywords="
        query = self.search_entry.get()
        # request the page
        page = requests.get(base_url + query)
        soup = BeautifulSoup(page.content, "html.parser")
        # grab only the content that we need
        search_results = soup.find(id="center-3")
        if not search_results:
            return
        # scrape information
        for product_item in search_results.find_all('li', class_="bc-list-item productListItem"):
            title = product_item['aria-label']
            temp_authors = []
            temp_narrators = []
            # 从当前product_item提取作者
            author_item = product_item.find("li", class_="bc-list-item authorLabel")
            if author_item:
                for link in author_item.find_all("a"):
                    if link.text not in temp_authors:
                        temp_authors.append(link.text)
            authors = ", ".join(temp_authors)
            # 从当前product_item提取朗读者
            narrator_item = product_item.find("li", class_="bc-list-item narratorLabel")
            if narrator_item:
                for link in narrator_item.find_all("a"):
                    if link.text not in temp_narrators:
                        temp_narrators.append(link.text)
            narrators = ", ".join(temp_narrators)
            self.audiobooks[title] = {
                "authors": authors,
                "narrators": narrators
            }
        # 调用ResultFrame的populate_tree方法更新界面
        self.result_frame.populate_tree(self.audiobooks)

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title('Audiobook Tagger')
        # 创建两个Frame实例并建立关联
        self.result_frame = ResultFrame(self)
        self.search_frame = SearchFrame(self, self.result_frame)

if __name__ == '__main__':
    app = App()
    app.mainloop()

关键修改点说明

  • 组件引用传递:在App的初始化方法中,先创建ResultFrame,再把它作为参数传给SearchFrame,这样SearchFrame就能直接调用ResultFrame的实例方法。
  • 爬取逻辑修复:将作者和朗读者的查找范围从soup改为当前的product_item,确保每本书的信息对应正确。
  • 清空旧数据:每次搜索前清空audiobooks字典和Treeview的旧条目,避免新结果和旧结果混在一起。

这种方式既保持了两个Frame类的职责分离(一个负责搜索,一个负责展示),又实现了它们之间的交互,代码结构更清晰,也更容易后续扩展功能。

内容的提问来源于stack exchange,提问作者user12758446

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 17:57:48