• 用tkinter+selenium做一个CSDN热榜爬虫


    自从学会了分析热榜,就是CSDN热榜分析,每天都要爬下来分析一下热榜都在干什么。但脚本运行到底还是不方便,所以接下来就想办法将其做成一个带有界面的热榜爬虫

    UI设计

    做一个热榜爬虫的交互式界面,只需要两个按钮外加两个信息框就足够了,所以布局极其简单

    class TestSTL:
        def __init__(self):
            self.root = tk.Tk()
            self.root.geometry("600x300+200+20")
            self.root.title("CSDN分析")
            self.initVars()
            self.initWidgits()
            self.root.mainloop()
        
        def initVars(self):
            self.heatBlogs = []
            self.subHeats = {}
            self.infoCSDN = tk.StringVar()
    
        def initWidgits(self):
            frmCtrl = ttk.LabelFrame(self.root, text="CSDN工具")
            frmCtrl.pack(side=tk.TOP, fill=tk.X)
            self.setFrmCtrl(frmCtrl)
    
            frmInfo = ttk.LabelFrame(self.root, text="反馈信息")
            frmInfo.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
            ttk.Label(frmInfo, textvariable=self.infoCSDN).pack(side = tk.TOP, fill=tk.X)
            self.infoCSDN.set("无反馈")
    
            self.setFrmInfo(frmInfo)
        
        def setFrmCtrl(self, frm):
            frmPack = dict(side=tk.TOP, fill=tk.X)
            frmHeat = ttk.Frame(frm)
            frmHeat.pack(**frmPack)
            ttk.Label(frmHeat, width=10, text="热榜分析").pack(side=tk.LEFT)
            ttk.Button(frmHeat, width=10, text="爬取热榜",
                command=self.btnHeatCrawler).pack(side=tk.LEFT)
    
            ttk.Button(frmHeat, width=10, text="导出热榜",
                command=self.mbExportHeat).pack(side=tk.LEFT)
            
        def setFrmInfo(self, frm):
            scroll = ttk.Scrollbar(frm)
            scroll.pack(side=tk.RIGHT,fill=tk.Y)
            self.logTxt = tk.Text(frm)
            self.logTxt.pack(side=tk.TOP, fill=tk.BOTH, padx=5, pady=5, expand=True)
            self.logTxt.config(yscrollcommand=scroll.set)
            scroll.config(command=self.logTxt.yview)
    
        # 热榜
        def btnHeatCrawler(self):
            pass
    
        def mbExportHeat(self):
            pass
    
        def addLogs(self, text):
            self.logTxt.insert("end", f"{text}\n")
            self.logTxt.see("end")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    在setFrmCtrl中,除了热榜分期的标签外,设置了两个按钮,分别是爬取热榜和导出热榜,二者分别绑定了两个函数btnHeatCrawler和mbExportHeat,这两个函数暂时还没实现。

    在CSDN工具Frame下面,是信息界面,信息界面封装了两个组件,一个用于返回实时信息,是个Label,绑定了名为self.infoCSDN的Label;另一个是多行文本组件Text,并为其设置了addLogs函数,用于快速添加内容。布局结果如下

    在这里插入图片描述

    函数封装

    尽管此前已经多少知道如何用selenium爬取热榜了,但所有代码都是以脚本的形式写出来的,并不适合调用。所以接下来就要把这些代码函数化,首先是打开Edge的函数

    # 打开Edge
    def openEdge(url):
        op = webdriver.EdgeOptions()
        op.add_argument('--headless')
        driver = webdriver.Edge(options=op)
        driver.get(url)
        return driver
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这里用到了EdgeOptions,其参数headless表示不必打开Edge,而可以后台运行,不然Edge总会跳出来,影响其他工作。

    然后就是用于热榜爬取的主要函数,封装如下,其中callback是一个回调函数,方便将运行信息传给用户界面,后面的key,则考虑到以后会爬取各领域热榜,于是预留一个接口。

    # 输入回调函数
    def getHeatInfos(callback, key=None):
        nBlogs = 100
        driver = openEdge('https://blog.csdn.net/rank/list')
    
        titleClass = "floor-rank-item"
        ts= []
        # 获取100篇热榜博客
        while len(ts) < nBlogs:
            script = "window.scrollTo(0,document.body.scrollHeight)"
            driver.execute_script(script)
            ts = driver.find_elements(By.CLASS_NAME, titleClass)
            time.sleep(0.5)
            info = f"已读取到{len(ts)}篇热榜博客"
            if callback: callback([], info)
            
        callback([], f"已读取到所有热榜博客,开始处理")
        blogs = []
        for t in ts:
            ws = t.text.split('\n')
            blogs.append([ws[i] for i in [0, 1, 10, 2, 4, 6, 8]])
            b = blogs[-1]
            callback(blogs, f"正在处理第{b[0]}篇博客,热度{b[-1]}")
        callback(blogs, f"全部热榜博客处理完毕")
        return blogs
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    这个函数的代码几乎都来自CSDN热榜分析

    最后,是导出CSV的函数

    def saveAsCSV(path, infos, title=['id','name', 'date', '']):
        with open(path, 'w', newline='', encoding='utf8') as f:
            w = csv.writer(f)
            w.writerow(title)
            for i in infos: 
                try: w.writerow(i)
                except : continue
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    功能实现

    最后,实现窗口程序对热榜爬取函数的调用逻辑。考虑到爬虫往往是个耗时的操作,所以必须使用多线程。

    from threading import Thread
    
    • 1

    最终热榜爬取函数的内容如下,其实核心代码就只有一行,表示开启一个线程来执行getHeatInfo,并把self.backHeatCrawler作为参数输入了进去。

    backHeatCrawler是用于反馈信息的回调函数,其功能如其参数一样,共有两个,一个是保存爬取到的博客信息,二则是展示爬虫进度。

    # 热榜
    def btnHeatCrawler(self):
        Thread(target = getHeatInfos,
            args=(self.backHeatCrawler,), daemon=True).start()
    
    def backHeatCrawler(self, blogs, info):
        self.heatBlogs = blogs
        self.infoCSDN.set(info)
        if info.endswith("完毕"):
            self.addLogs(f"共读取了{len(self.heatBlogs)}热榜博客")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    最后完成数据导出工作,其主要功能就是调用saveAsCSV函数,只不过其中加入了文件名对话框等交互方式。

    def mbExportHeat(self):
        heatHead = ["序号", "标题", "作者", "浏览", "评论", "收藏", "热度"]
        self.mbExport(self.heatBlogs, heatHead, "热榜博客")
        
    def mbExport(self, lst, title, info):
        self.infoCSDN.set(f"正在保存{info}")
        path = asksaveasfilename(filetypes=[("数据文件", "csv")], 
            defaultextension='.csv')
        if path=="":
            self.infoCSDN.set(f"未保存{info}")
            return
        saveAsCSV(path, lst, title=['id'])
        self.infoCSDN.set(f"{info}保存成功")
        self.addLogs(f"{info}保存成功")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    最终演示效果如下

    在这里插入图片描述

  • 相关阅读:
    OLTP 负载性能优化实践
    2023最新SSM计算机毕业设计选题大全(附源码+LW)之java智慧社区信息化服务平台4v5hv
    android11使用google test
    教你VSCode如何快速对齐代码、格式化代码
    在PowerDesigner中应用达梦数据库
    使用go语言实现自建区块链系统的一些环境的一些总结
    【ROS2原理8】节点到参与者的重映射
    Win11蓝牙无法连接怎么办?可以试试这个方法。
    Codeforces暑期训练周报(7.21~7.27)
    qt界面之间传递数据
  • 原文地址:https://blog.csdn.net/m0_37816922/article/details/132310673