1. 程式人生 > >Python爬蟲實戰專案2 | 動態網站的抓取(爬取電影網站的資訊)

Python爬蟲實戰專案2 | 動態網站的抓取(爬取電影網站的資訊)

1.什麼是動態網站?

動態網站和靜態網站的區別在於,網頁中常常包含JS,CSS等動態效果的內容或者檔案,這些內容也是網頁的有機整體。但對於瀏覽器來說,它是如何處理這些額外的檔案的呢?首先瀏覽器先下載html檔案,然後根據需要,下載JS等額外檔案,它會自動去下載它們,如果我們要爬取這些網頁中的動態資訊,則需要我們親手去構造請求資料。

2.如何找到這些動態效果的額外檔案?

例項:

我們開啟一個電影網站:http://movie.mtime.com/103937/,然後按F12,在開發者工具中找到“網路”這一選項,我用的是FireFox,如圖:

重新整理下:

可以發現,網頁中除了html檔案,還載入了其他檔案,比如CSS,JS等。

比如網頁中的評分資訊等是在上圖我點選變藍的一行js檔案中動態載入的。那我是如何找到這個檔案的呢,很抱歉,目前只能說是經驗,因為網站中動態載入的資訊大多是在js檔案中的,所以我們可在js、xhr檔案的響應text中檢視下,是否是我們想要的資料,一般都能找到。

3.這些動態資訊檔案有什麼用,以及我們怎麼下載它們?

剛剛已經說過,這些檔案的作用就是動態地載入網頁資訊,比如上圖中的“票房:10.25億元”是我們想要爬取的資料,但在網頁中卻不存在,因此可以預見,是這些js等檔案動態載入的。

那怎麼下載它們呢?首先點選該檔案,然後再點選“訊息頭”,會看到“訊息頭”、“Cookie”、“引數”等按鈕,如圖:

然後我們可以看到這個js檔案的的請求網址,該請求網址的結構是有規律的,動態變化的只有3個部分,分別是電影的網址,時間,以及電影的編號,這三項內容顯然電影網址和電影編號可以從靜態html網頁中獲得,時間可以自己構造,然後就可以訪問該js檔案的網址獲取資料,資料是以字典的形式呈現的,內容在”響應“裡,如圖:

我們可以用json模組 來處理它,比較方便。

4.下面是該專案的結構和程式碼:

4.1.目錄結構:

4.2.程式碼模組:

1.HtmlDownloader模組中的download方法用於下載網頁資訊:

import requests
import chardet


class HtmlDownloader(object):
    def download(self, url):
        if url is None:
            return None
        user_agent = 'Mozilla/4.0 (compatible; MISE 5.5; Windows NT)'
        headers = {'User-Agent': user_agent}
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            response.encoding = 'utf-8'
            return response.text
        return None

2.HtmlParser模組 則根據首頁中的電影網址找到所有的js動態檔案,然後下載我們需要的資料。

import re
from bs4 import BeautifulSoup
import json


class HtmlParser(object):
    def parser_url(self, page_url, response):
        pattern = re.compile(r'(http://movie.mtime.com/(\d+)/)')
        urls = pattern.findall(response)
        if urls:
            # 將url進行去重
            return list(set(urls))
        else:
            return None

    def parser_json(self, page_url, response):
        '''
        解析響應
        :param page_url:
        :param response:
        :return:
        '''
        # 將“=”和“;”之間的內容提取出來
        pattern = re.compile(r'=(.*?);')
        result = pattern.findall(response)[0]
        if result:
            # json模組載入字串
            value = json.loads(result)
            try:
                isRelease = value.get('value').get('isRelease')
            except Exception as e:
                print('json異常')
                return None
            if isRelease:
                if value.get('value').get('hotValue') == None:
                    return self._parser_release(page_url, value)
                else:
                    return self._parser_no_release(page_url, value, isRelease=2)
            else:
                return self._parser_no_release(page_url, value)

    def _parser_release(self, page_url, value):
        '''
        解析已經上映的影片
        :param page_url: 電影連結
        :param value: json資料
        :return:
        '''
        try:
            isRelease = 1
            movieRating = value.get('value').get('movieRating')
            boxOffice = value.get('value').get('boxOffice')
            movieTitle = value.get('value').get('movieTitle')
            RPictureFinal = movieRating.get('RPictureFinal')
            RStoryFinal = movieRating.get('RStoryFinal')
            RDirectoryFinal = movieRating.get('RDirectoryFinal')
            ROtherFinal = movieRating.get('ROtherFinal')
            RatingFinal = movieRating.get('RatingFinal')

            MovieId = movieRating.get('MovieId')
            Usercount = movieRating.get('Usercount')
            AttitudeCount = movieRating.get('AttitudeCount')

            TotalBoxOffice = boxOffice.get('TotalBoxOffice')
            TotalBoxOfficeUnit = boxOffice.get('TotalBoxOfficeUnit')
            TodayBoxOffice = boxOffice.get('TodayBoxOffice')
            TodayBoxOfficeUnit = boxOffice.get('TodayBoxOfficeUnit')

            ShowDays = boxOffice.get('ShowDays')
            try:
                Rank = boxOffice.get('ShowDays')
            except Exception:
                Rank = 0
            # 返回所提取的內容
            return (
                MovieId, movieTitle, RatingFinal,
                ROtherFinal, RPictureFinal, RDirectoryFinal,
                RStoryFinal, Usercount, AttitudeCount,
                TotalBoxOffice+TotalBoxOfficeUnit,
                TodayBoxOffice+TodayBoxOfficeUnit,
                Rank, ShowDays, isRelease
            )
        except Exception:
            print(page_url, value)
            return None

    def _parser_no_release(self, page_url, value, isRelease=0):
        '''
        解析未上映的電影資訊
        :param page_url:
        :param value:
        :param isRelease:
        :return:
        '''
        try:
            movieRating = value.get('value').get('movieRating')
            movieTitle = value.get('value').get('movieTitle')

            RPictureFinal = movieRating.get('RPictureFinal')
            RStoryFinal = movieRating.get('RStoryFinal')
            RDirectorFinal = movieRating.get('RDirectoryFinal')
            ROtherFinal = movieRating.get('ROtherFinal')
            RatingFinal = movieRating.get('RatingFinal')

            MovieId = movieRating.get('MovieId')
            Usercount = movieRating.get('Usercount')
            AttitudeCount = movieRating.get('AttitudeCount')
            try:
                Rank = value.get('value').get('hotValue').get('Ranking')
            except Exception:
                Rank = 0
            return (MovieId, movieTitle, RatingFinal,
                    ROtherFinal, RPictureFinal, RDirectorFinal,
                    RStoryFinal, Usercount, AttitudeCount, u'無',
                    u'無', Rank, 0, isRelease)
        except Exception:
            print(page_url, value)
            return None

3.DataOutput模組則用於將資料存入資料庫表中。

import sqlite3

class DataOutput(object):
    def __init__(self):
        self.cx = sqlite3.connect('MTime.db')
        self.create_table('MTime')
        self.datas=[]

    def create_table(self, table_name):
        '''
        建立資料表
        :param table_name:
        :return:
        '''
        values = '''
        id integer primary key,
        MovieId integer,
        MovieTitle varchar(40) NULL,
        RatingFinal REAL NULL DEFAULT 0.0,
        ROtherFinal REAL NULL DEFAULT 0.0,
        RPictureFinal REAL NULL DEFAULT 0.0,
        RDirectoryFinal REAL NULL DEFAULT 0.0,
        RStoryFinal REAL NULL DEFAULT 0.0,
        Usercount integer NULL DEFAULT 0,
        AttitudeCount integer NULL DEFAULT 0,
        TotalBoxOffice varchar(20) NULL,
        TodayBoxOffice varchar(20) NULL,
        Rank integer NULL DEFAULT 0,
        ShowDays integer NULL DEFAULT 0,
        isRelease integer NULL
        '''
        self.cx.execute("DROP TABLE IF EXISTS %s" % table_name)
        self.cx.execute("CREATE TABLE %s( %s );" % (table_name, values))

    def store_data(self, data):
        '''
        資料儲存
        :param data:
        :return:
        '''
        if data is None:
            return
        self.datas.append(data)
        print('passby')
        if len(self.datas) > 10:
            self.output_db('MTime')
            print('Output successfully!')

    def output_db(self, table_name):
        '''
        將資料儲存到sqlite
        :param table_name:
        :return:
        '''
        for data in self.datas:
            self.cx.execute("INSERT INTO %s (MovieId, MovieTitle,"
                            "RatingFinal, ROtherFinal, RPictureFinal,"
                            "RDirectoryFinal, RStoryFinal, Usercount,"
                            "AttitudeCount, TotalBoxOffice, TodayBoxOffice,"
                            "Rank, ShowDays, isRelease) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
                            "" % table_name, data)
            self.datas.remove(data)
        self.cx.commit()

    def output_end(self):
        '''
        關閉資料庫
        :return:
        '''
        if len(self.datas) > 0:
            self.output_db('MTime')
        self.cx.close()

4.SpiderMan模組則用於呼叫各個模組,實現功能的統一。

import time
from the_python_spider_for_dynamic_websites.HtmlDownloader import HtmlDownloader
from the_python_spider_for_dynamic_websites.HtmlParser import HtmlParser
from the_python_spider_for_dynamic_websites.DataOutput import DataOutput


class SpiderMan(object):
    def __int__(self):
        pass

    def crawl(self, root_url):
        downloader = HtmlDownloader()
        parser = HtmlParser()
        output = DataOutput()
        content = downloader.download(root_url)
        urls = parser.parser_url(root_url, content)

        # 構造一個獲取評分和票房連結
        for url in urls:
            try:
                print(url[0], url[1])
                t = time.strftime("%Y%m%d%H%M%S3282", time.localtime())
                # print('t:', t)
                rank_url = 'http://service.library.mtime.com/Movie.api' \
                            '?Ajax_CallBack=true'\
                            '&Ajax_CallBackType=MTime.Library.Services'\
                            '&Ajax_CallBackMethod=GetMovieOverviewRating'\
                            '&Ajax_CrossDomain=1'\
                            '&Ajax_RequestUrl=%s'\
                            '&t=%s'\
                            '&Ajax_CallBackArgument0=%s' % (url[0], t, url[1])
                rank_content = downloader.download(rank_url)
                print("rank_content:", rank_content)
                data = parser.parser_json(rank_url, rank_content)
                print("data:", data)
                output.store_data(data)
            except Exception as e:
                print("Crawl failed:", e)
        output.output_end()
        print("Crawl finish")


if __name__ == '__main__':
    spider = SpiderMan()
    spider.crawl('http://theater.mtime.com/China_Beijing/')

最後,放一張爬取資料的圖片: