Python小爬蟲——抓取豆瓣電影Top250數據


寫LeetCode太累了,偶爾練習一下Python,寫個小爬蟲玩一玩~比較簡單,抓取豆瓣電影Top250數據,並保存到txt、上傳到數據庫中。

確定URL格式

先找到豆瓣電影TOP250任意一頁URL地址的格式,如第一頁為:https://movie.douban.com/top250?start=0&filter=,分析該地址:

  • https:// 代表資源傳輸協議使用https協議;
  • movie.douban.com/top250 是豆瓣的二級域名,指向豆瓣服務器;
  • /top250 是服務器的某個資源;
  • start=0&filter= 是該URL的兩個參數,分別代表開始位置和過濾條件。

通過分析可以發現,不同頁面之間是有start的值在變化,其他為固定部分。

獲取頁面數據

以面向對象的編碼方式編寫這個程序,養成好的編碼習慣。

基本信息在 \(__init__\) 函數中初始化。注意到其中有一個 \(headers\),這是用來做什么的呢?有一些網站有一點點反爬蟲機制,對於一般的爬蟲請求拒絕返回實際數據。一般來說,基本的反爬蟲是通過判斷發送請求中是否有瀏覽器的基本信息,因此,我們可以偽裝成瀏覽器發送這些爬蟲請求,通過修改http包中的heafer實現。

問題又來了,這個User-Agent從哪來呢?你如果急着用,復制我的就可以用!如果想知道自己電腦瀏覽器的基本信息,可以搜索下載小程序“Fiddler”,打開程序后,打開一個網頁,小程序就會獲取發送的請求,其中就包含你要的東西。

class MovieTop(object):
    def __init__(self):
        self.start = 0
        self.param = '&filter'
        self.headers = {"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; WOW64) "
                                       "AppleWebKit/537.36 (KHTML, like Gecko) "
                                       "Chrome/65.0.3325.146 Safari/537.36"}
        self.movieList = []
        self.filePath = './DoubanTop250.txt'

    def get_page(self):
        try:
            url = 'https://movie.douban.com/top250?start=' + str(self.start) + '&filter='
            myRequest = request.Request(url, headers=self.headers)
            response = request.urlopen(myRequest)
            page = response.read().decode('utf-8')
            print('正在獲取第' + str((self.start+25)//25) + '頁數據...')
            self.start += 25
            return page
        except request.URLError as e:
            if hasattr(e, 'reason'):
                print('獲取失敗,失敗原因:', e.reason)

提取頁面信息

在上面的代碼中,可以得到頁面的代碼,是HRML格式的文本,我們需要從中提取出有用的信息。在Chrome瀏覽器中,右鍵查看頁面源代碼可以看到稍微有格式的HTML文本,這和我們獲取的page內容是相同的。找到其中的關鍵數據,如下:

從中可以看到一條記錄的頁面代碼結構,如何從中提取想要的信息呢?正則表達式匹配!還記得 \(re模塊\) 中的 \(compile\)嗎?

這是比較麻煩的一件事,暫時先這樣寫,懂這行的都知道有一個東西叫做 \(beautiful soup\),它可以簡潔易懂地提取頁面信息,鑒於練習一下正則表達式,而且也比較簡單,直接寫出整個匹配式。下面是參考代碼:

還有一個小問題,本來想提取每條記錄的全部信息,后來發現有的電影沒有“別名”,還有的電影沒有“主演”,不得不忽略這兩條信息,最后每條記錄只提取了10個信息。

    def get_page_info(self):
        patern = re.compile(u'<div.*?class="item">.*?'
                            + u'<div.*?class="pic">.*?'
                            + u'<em.*?class="">(.*?)</em>.*?'
                            + u'<div.*?class="info">.*?'
                            + u'<span.*?class="title">(.*?)</span>.*?'
                            + u'<span.*?class="other">(.*?)</span>.*?'
                            + u'<div.*?class="bd">.*?'
                            + u'<p.*?class="">.*?'
                            + u'導演:\s(.*?)\s.*?<br>'
                            + u'(.*?)&nbsp;/&nbsp;'
                            + u'(.*?)&nbsp;/&nbsp;(.*?)</p>.*?'
                            + u'<div.*?class="star">.*?'
                            + u'<span.*?class="rating_num".*?property="v:average">'
                            + u'(.*?)</span>.*?'
                            + u'<span>(.*?)人評價</span>.*?'
                            + u'<span.*?class="inq">(.*?)</span>'
                            , re.S)

        while self.start <= 225:
            page = self.get_page()
            movies = re.findall(patern, page)
            for movie in movies:
                self.movieList.append([movie[0],
                                       movie[1],
                                       movie[2].lstrip('&nbsp;/&nbsp;'),
                                       movie[3],
                                       movie[4].lstrip(),
                                       movie[5],
                                       movie[6].rstrip(),
                                       movie[7],
                                       movie[8],
                                       movie[9]])

寫入TXT文件

這個就比較簡單了,直接進行文件操作,將其寫入txt即可。

    def write_page(self):
        print('開始寫入文件...')
        file = open(self.filePath, 'w', encoding='utf-8')
        try:
            for movie in self.movieList:
                file.write('電影排名:' + movie[0] + '\n')
                file.write('電影名稱:' + movie[1] + '\n')
                file.write('電影別名:' + movie[2] + '\n')
                file.write('導演:' + movie[3] + '\n')
                file.write('上映年份:' + movie[4] + '\n')
                file.write('制作國家/地區:' + movie[5] + '\n')
                file.write('電影類別:' + movie[6] + '\n')
                file.write('評分:' + movie[7] + '\n')
                file.write('參評人數:' + movie[8] + '\n')
                file.write('簡短影評:' + movie[9] + '\n')
                file.write('\n')
            print('成功寫入文件...')
        except Exception as e:
            print(e)
        finally:
            file.close()

上傳至數據庫

作為實驗,先在本地mysql數據庫中建立PythonTest數據庫,創建doubanTop250表,就不在python代碼中創建了。注意字符集設為 \(utf8\),不然可能出現1366錯誤。

CREATE DATABASE PythonTest;

CREATE TABLE doubanTop250(
    ID int PRIMARY KEY AUTO_INCREMENT,
    rank int,
    name varchar(50),
    alias varchar(100),
    director varchar(50),
    showYear varchar(50),
    makeCountry varchar(50),
    movieType varchar(50),
    movieScore float,
    scoreNum int,
    shortFilm varchar(255)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

接下來在Python代碼中添加一個函數,將數據導入數據庫。

    def upload(self):
        db = pymysql.connect("localhost", "root", "love1125", "PythonTest", charset='utf8')
        cursor = db.cursor()

        insertStr = "INSERT INTO doubanTop250(rank, name, alias, director," \
                    "showYear, makeCountry, movieType, movieScore, scoreNum, shortFilm)" \
                    "VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', %f, %d, '%s')"

        try:
            for movie in self.movieList:
                insertSQL = insertStr % (int(movie[0]), str(movie[1]), str(movie[2]), str(movie[3]),
                                         str(movie[4]), str(movie[5]), str(movie[6]), float(movie[7]),
                                         int(movie[8]), str(movie[9]))
                cursor.execute(insertSQL)
            db.commit()
            print('成功上傳至數據庫...')
        except Exception as e:
            print(e)
            db.rollback()
        finally:
            db.close()

完整代碼:https://github.com/Pacsiy/learnPY

本文版權歸作者AlvinZH和博客園所有,歡迎轉載和商用,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM