Python3爬取CSDN個人部落格相關資料--新增GUI圖形化介面

遲到_啦發表於2020-12-11

一、效果演示

二、涉及模組

  • python3.7
  • wxPython
  • re
  • pymysql
  • BeautifulSoup
  • time
  • urllib
  • webbrowser

三、功能說明

  • 此次新增GUI介面查詢,是在上篇文章的基礎上,將控制檯輸入引數調整為圖形化介面輸入引數,並新增了 [測試連線] 功能
  • 上篇文章傳送門 Python3爬取CSDN個人部落格相關資料
  • 此次圖形化介面採用的是wxPython模組

四、程式碼實現

1、入口/啟動程式碼

import wx
from MyMenu import MyMenu

class MyApp(wx.App):
    def OnInit(self):
        frame = MyMenu(None, -1, '我的CSDN部落格資料查詢工具')
        frame.Show(True)
        return True


app = MyApp(0)
app.MainLoop()

2、GUI介面開發程式碼

import pymysql
import wx
import webbrowser as brower

from Crawl import Crawl


class MyMenu(wx.Frame):
    def __init__(self, parent, id, title):
        super(MyMenu, self).__init__(parent, id, title)
        # 設定視窗寬高
        self.SetSize(wx.Size(600, 400))
        # 設定介面的圖示
        self.SetIcon(wx.Icon('ico/crawl.ico', wx.BITMAP_TYPE_ICO))
        # 預設啟動後視窗位置為螢幕居中
        self.Centre()
        self.qt = None
        self.link = None

        # 繪製皮膚
        panel = wx.Panel(self, -1)
        # 向panel中新增圖片
        # image = wx.Image("bg.jpg", wx.BITMAP_TYPE_JPEG).ConvertToBitmap()
        # ima
        # ge2 = wx.Bitmap('bg.jpg', wx.BITMAP_TYPE_ANY)
        # wx.StaticBitmap(panel, -1, bitmap=image2, pos=(0, 0), size=(300, 400))

        w = self.GetSize().width
        h = self.GetSize().height

        # 向皮膚新增靜態標籤
        label_url = wx.StaticText(panel, -1, "[我的部落格]地址:", pos=((w - 300)/2, (h - 300)/2))
        label_dbname = wx.StaticText(panel, -1, "本地資料庫名:", pos=((w - 300)/2, (h - 300)/2 + 50))
        label_user = wx.StaticText(panel, -1, "資料庫使用者:", pos=((w - 300)/2, (h - 300)/2 + 100))
        label_pwd = wx.StaticText(panel, -1, "資料庫密碼:", pos=((w - 300)/2, (h - 300)/2 + 150))
        # 向皮膚新增輸入框
        # label_url文字輸入框
        self.entry_url = wx.TextCtrl(panel, -1, size=(200, 30), pos=((w - 300)/2 + 100, (h - 300)/2))
        # label_dbname文字輸入框
        self.entry_dbname = wx.TextCtrl(panel, -1, size=(200, 30), pos=((w - 300)/2 + 100, (h - 300)/2 + 50))
        # label_user文字輸入框
        self.entry_user = wx.TextCtrl(panel, -1, size=(200, 30), pos=((w - 300) / 2 + 100, (h - 300) / 2 + 100))
        # label_pwd文字輸入框
        self.entry_pwd = wx.TextCtrl(panel, -1, size=(200, 30), pos=((w - 300) / 2 + 100, (h - 300) / 2 + 150))

        # 皮膚中新增按鈕
        left, top = self.entry_pwd.Position
        self.but_test_connect = wx.Button(panel, -1, "測試連線", size=(100, 30), pos=(left, top + 50))
        self.but_search = wx.Button(panel, -1, "查詢", size=(80, 30), pos=(left + 120, top + 50))
        # 設定按鈕的顏色
        self.but_test_connect.SetBackgroundColour("#009ad6")
        self.but_search.SetBackgroundColour("#1d953f")

        # 給按鈕繫結事件
        self.Bind(wx.EVT_BUTTON, self.on_test_connect, self.but_test_connect)
        self.Bind(wx.EVT_BUTTON, self.on_search, self.but_search)

        # 設定全域性變數
        self.url = ''
        self.db_name = ''
        self.db_user = ''
        self.user_pwd = ''

        self.init_ui()

    """
        建立選單欄
    """
    def init_ui(self):
        menu = wx.MenuBar()
        '''設定選單'''
        # file選單
        file = wx.Menu()
        # file.Append(101, '&Open', 'Open a new document')  # file選單-選項1
        # file.AppendSeparator()  # 分割線
        # file.Append(102, '&Save', 'Save the document')  # file選單-選項2
        # file.AppendSeparator()  # 分割線
        self.qt = wx.MenuItem(file, 103, '&Quit\tCtrl+Q', 'Quit the Application')  # file選單-選項3
        self.qt.SetBitmap(wx.Bitmap('ico/quit.ico', wx.BITMAP_TYPE_ICO))  # 設定退出的icon
        file.Append(self.qt)

        # help選單
        help = wx.Menu()
        self.link = wx.MenuItem(help, 201, '&About', 'About Introduction')  # file選單-選項3
        help.Append(self.link)

        # 選單加到選單欄
        menu.Append(file, '&File')
        menu.Append(help, '&Help')

        # 程式中建立選單欄
        self.SetMenuBar(menu)
        # self.Centre()

        # 給退出按鈕繫結事件
        self.Bind(wx.EVT_MENU, self.on_quit, self.qt)
        # 給幫助按鈕繫結事件
        self.Bind(wx.EVT_MENU, self.on_link, self.link)

        # 顯示圖形化介面
        self.Show()

    '''
        定義一個訊息彈出框的函式
    '''
    def alert_error(self, word=""):
        dlg = wx.MessageDialog(None, word, u"警告", wx.YES_NO)
        if dlg.ShowModal() == wx.ID_YES:
            # self.Close(True)
            pass
        dlg.Destroy()

    '''
        定義一個訊息彈出框的函式
    '''
    def alert_success(self, word=""):
        dlg = wx.MessageDialog(None, word, u"提示", wx.YES_NO)
        if dlg.ShowModal() == wx.ID_YES:
            pass
        dlg.Destroy()

    '''
    測試連線方法
    '''
    def on_test_connect(self, event, is_alert=True):
        # 連線到本地資料庫
        self.db_name = self.entry_dbname.GetValue()
        self.db_user = self.entry_user.GetValue()
        self.user_pwd = self.entry_pwd.GetValue()

        # 判斷,檢視使用者名稱和密碼名是否為空 ,當密碼或使用者名稱為空時會出現會導致出錯
        if self.db_name and self.db_user and self.user_pwd:
            try:
                conn = pymysql.connect(host="127.0.0.1"
                                       , user=self.db_user
                                       , password=self.user_pwd
                                       , db=self.db_name
                                       , port=3306)
                if is_alert:  # 只有測試連線的時候才會彈出成功的提示
                    self.alert_success('連線成功!')
                return True
            except Exception as e:
                print(e)
                self.alert_error('連線失敗!')
                return False
            finally:
                conn.close()  # 關閉連線
        else:
            self.alert_error(word='存在未填寫資訊!')
            return False

    '''
    查詢方法
    '''
    def on_search(self, event):
        self.url = self.entry_url.GetValue()
        if self.url == '':
            self.alert_error('URL不能為空!')
            return False
        flag = self.on_test_connect(event, is_alert=False)
        if flag:  # 成功連線,則執行查詢操作
            is_success = Crawl(url=self.url
                               , db_name=self.db_name
                               , user=self.db_user
                               , pwd=self.user_pwd)
            if is_success:
                self.alert_success('查詢並儲存本地資料庫成功,可前往檢視呦')
            else:
                self.alert_error('查詢出錯了!')

    '''
        退出視窗
    '''
    def on_quit(self, event):
        self.Close()

    '''
        幫助方法
    '''
    def on_link(self, event):
         brower.open('https://blog.csdn.net/qq_19314763/article/details/110951204')

3、爬取部落格資訊程式碼

import urllib.request as request
import urllib.error as error
import time
from bs4 import BeautifulSoup
import pymysql
import re


class Crawl:
    def __init__(self, url, db_name, user, pwd):
        self.url = url
        self.db_name = db_name
        self.db_user = user
        self.user_pwd = pwd
        self.main()
    """
    爬取整個HTML頁面
    """
    def download(self, crawl_url, num_retries=3):
        # 設定使用者代理
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                          'Chrome/72.0.3626.121 Safari/537.36',
        }
        # 設定請求的URL
        req = request.Request(crawl_url, headers=headers)
        try:
            # 開始爬取整個HTML程式碼
            crawl_html = request.urlopen(req).read().decode("utf-8")

        # 此處異常用於處理,csdn伺服器異常後導致爬取失敗
        except error.URLError as e:
            print("download error:", e.reason)
            crawl_html = None
            if num_retries > 0:
                if hasattr(e, "code") and 500 <= e.code <= 600:
                    time.sleep(5000)  # 主執行緒睡眠5s後再重新訪問,最多訪問3次,也就是如果程式開始執行10s後,csdn伺服器始終未正常啟動,則此次爬取失敗
                    return self.download(crawl_url, num_retries-1)
        return crawl_html

    """
    解析HTML獲取頁面上的10組資料
    """
    def parse_page(self, page_html):
        # 宣告一個包含10組key的字典data_dict
        data_dict = {'積分': '', '粉絲': '', '獲贊': '', '評論': '', '收藏': ''
                     , '原創': '', '周排名': '', '總排名': '', '訪問': '', '等級': ''
                     , '賬號': '', '暱稱': ''}

        # 開始解析html
        soup = BeautifulSoup(page_html, "html.parser")

        # 解析class='text-center'的所有<dl>標籤列表
        # 方式一
        # dl_list = soup.find_all(name='div', attrs={'class': re.compile('text-center')})
        # 方式二
        dl_list = soup.find_all('dl', class_='text-center')

        # 遍歷dl標籤列表,獲取到每一個dl標籤,將目標數值存入字典data_dict
        for dl in dl_list:
            # print(dl)
            dd_name = dl.select('dd')[0].text  # 讀取該dl標籤的子標籤dd的文字內容
            dd_title = dl.get('title')  # 讀取該dl標籤的title屬性值
            for k in data_dict.keys():  # 遍歷data_dict字典,將匹配的數值存入字典中
                if dd_name == k:
                    data_dict[k] = dd_title

        # 獲取賬號和暱稱資訊
        self_info_list = soup.find_all('a', id='uid')  # 根據id獲取a標籤元素
        alias = self_info_list[0].get('title')
        account = self_info_list[0].select('span')[0].get('username')
        data_dict.update({"賬號": account, "暱稱": alias})

        # data_dict新增爬取時間的資訊
        data_dict.update({"爬取時間": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())})

        return data_dict

    """
    判斷表是否存在
    """
    def table_exists(con, table_name):  # 這個函式用來判斷表是否存在
        sql = "show tables;"
        con.execute(sql)
        tables = [con.fetchall()]
        table_list = re.findall('(\'.*?\')',str(tables))
        table_list = [re.sub("'", '', each) for each in table_list]
        if table_name in table_list:
            return 1        # 存在返回1
        else:
            return 0        # 不存在返回0

    '''
    將字典資料存入資料庫
    '''
    def save_to_mysql(self, data_obj):
        # db = input('請輸入本地資料庫名:')
        # username = input('請輸入資料庫使用者名稱:')
        # password = input('請輸入資料庫密碼:')

        # 連線資料庫
        conn = pymysql.connect(
            host="127.0.0.1",
            port=3306,  # 埠號
            user=self.db_user,  # 資料庫使用者
            password=self.user_pwd,  # 資料庫密碼
            database=self.db_name  # 要連線的資料庫名稱
        )
        # 建立遊標,用於資料庫插入
        cursor = conn.cursor()

        # # 校驗資料庫是否存在部落格資料記錄表
        # table_count = table_exists(conn, 'CSDN_SELF_BLOG_DATA')
        # # 如果資料庫不存在該表,則建立
        # if table_count == 0:
        sql_create = """CREATE TABLE IF NOT EXISTS `csdn_self_blog_data` (
                                      `id` bigint NOT NULL AUTO_INCREMENT,
                                      `account` varchar(100) DEFAULT NULL,
                                      `alias` varchar(100) DEFAULT NULL,
                                      `grade` int DEFAULT NULL,
                                      `count_fan` int DEFAULT NULL,
                                      `count_thumb` bigint DEFAULT NULL,
                                      `count_comment` bigint DEFAULT NULL,
                                      `count_star` int DEFAULT NULL,
                                      `count_original` int DEFAULT NULL,
                                      `rank_week` bigint DEFAULT NULL,
                                      `rank_all` bigint DEFAULT NULL,
                                      `count_scan` bigint DEFAULT NULL,
                                      `blog_level` varchar(100) DEFAULT NULL,
                                      `crawl_time` datetime DEFAULT NULL,
                                      `start_hour` int DEFAULT NULL,
                                      `end_hour` int DEFAULT NULL,
                                      `crawl_date` date DEFAULT NULL,
                                      PRIMARY KEY (`id`)
                                    ) ENGINE=InnoDB DEFAULT CHARSET=utf8
                                    """
        cursor.execute(sql_create)
        # print('==========表建立完成=========')

        # 建立SQL語句並往資料庫插入資料
        curr_date = time.strftime("%Y-%m-%d", time.localtime())
        curr_time = data_obj['爬取時間']
        sql_insert = """insert into csdn_self_blog_data(
                                     account, alias, grade, count_fan, count_thumb
                                    ,count_comment, count_star, count_original, rank_week, rank_all
                                    ,count_scan, blog_level, crawl_time, start_hour, end_hour
                                    ,crawl_date)
                        values( %s, %s, %s, %s, %s
                               ,%s, %s, %s, %s, %s
                               ,%s, %s, %s, %s, %s
                               ,%s)"""
        values_list = [data_obj['賬號'], data_obj['暱稱'], data_obj['積分'], data_obj['粉絲'], data_obj['獲贊']
                       , data_obj['評論'], data_obj['收藏'], data_obj['原創'], data_obj['周排名'], data_obj['總排名']
                       , data_obj['訪問'], data_obj['等級'], curr_time, curr_time[11:13], int(curr_time[11:13]) + 1
                       , curr_date]
        cursor.execute(sql_insert, tuple(values_list))
        conn.commit()  # 提交請求,不然不會插入資料
        conn.close()
        print("======================儲存資料庫成功!=======================")
        return True

    """
    主程式
    """
    def main(self):
        # url = input('請輸入您的CSDN網站[我的部落格]URL地址:')

        # url = "https://blog.csdn.net/qq_19314763?spm=1010.2135.3001.5113"  # 使用自己的CSDN[我的部落格]的URL地址
        # 1、獲取整個HTML
        html = self.download(self.url)
        # 2、解析HTML獲取目標資料,並儲存到字典中
        dict_obj = self.parse_page(html)

        # 列印字典資訊,檢視效果使用
        # for key, value in dict_obj.items():
        #     print(key + ' = ' + value)

        # 3、將字典資料存入MySQL資料庫
        self.save_to_mysql(dict_obj)

4、完整程式碼

碼雲Gitee地址

五、參考連結

相關文章