python爬取貓眼正在熱映電影

靜然顧遺塵發表於2019-03-04

python寫爬蟲爬取需要的資料比較容易,以Python簡潔的語法和一大波成熟的庫,寫起來相當的快

python的版本以及使用的庫

  • Python 3.6.4
  • requests
  • lxml

這次主要是爬取貓眼的熱映電影,網址為http://maoyan.com/films

新建個專案

我用 類(Class) 的方式建立,並初始化需要使用的引數

class MaoYanHot:

    def __init__(self):
        self.session = requests.session()  # requests庫的session物件能夠幫我們跨請求保持某些引數,也會在同一個session例項發出的所有請求之間保持cookies
        self.baseUrl = 'http://maoyan.com/films'
        # 設定requests請求的headers引數來模擬瀏覽器訪問
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
            'Accept': 'text/html;q=0.9,*/*;q=0.8',
            'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
            'Accept-Encoding': 'gzip',
            'Connection': 'close',
            'Referer': None  # 注意如果依然不能抓取的話,這裡可以設定抓取網站的host
        }

    # 開始訪問
    def run(self, pages='?offset=0'):
        url = self.baseUrl + pages
        print('獲取url:' + url)


MaoYanHot = MaoYanHot()
MaoYanHot.run()
複製程式碼

獲取正在熱映電影首頁資料

寫個def來獲取網頁內容,並在run裡面呼叫

# 獲取網頁內容
def getHtmlContent(self, url):
    try:
        respone = self.session.get(url, headers=self.headers)
        if respone.status_code == 200:
            # 這裡的編碼取決於網頁編碼
            # 在decode的時候指定錯誤處理方式,有strict, ignore和replace三種處理方式,strict是預設的也就是丟擲異常,ignore是忽略非法字元
            return respone.content.decode('utf-8', 'ignore')  
        return False
    except Exception as e:
        print('err1:' + str(e))
        return False
複製程式碼

run裡面呼叫contents = self.getHtmlContent(url)

如果順利的話就會得到HTML內容,通過HTML發現dd標籤並沒有閉合需要處理下

python爬取貓眼正在熱映電影
通過etree.HTML我發現並不能正確閉合dd標籤,造成層層巢狀,寫個例子測試下

from lxml import etree

aaa = '''
<dl>
    <dd>1111
    <dd>1111
    <dd>1111
    <dd>1111
</dl>
'''
bbb = etree.HTML(aaa)
print(etree.tostring(bbb))
複製程式碼

列印的結果是b'<html><body><dl>\n <dd>1111\n <dd>1111\n <dd>1111\n <dd>1111\n</dd></dd></dd></dd></dl>\n</body></html>'

把HTML轉為XML

手動閉合dd後通過etree.HTML把HTML轉為XML,利用xpath語法可以快速匹配我們需要的節點。

# 把html轉為xml
def htmlToXml(self, contents):
    # 手動閉合dd標籤
    contents = contents.replace('<dd', '</dd><dd')
    return etree.HTML(contents)
複製程式碼

run裡面呼叫xmls = self.htmlToXml(contents)
再來看看電影結構組成,如下圖

python爬取貓眼正在熱映電影
我們可以看到電影資料都是在dd裡面,評分分為 暫無評分具體評分 2種,父元素是dl,而它的class[movie-list]在頁面只有一個。

獲取電影的名稱和評分

接下來寫個def分析需要的電影資料,如電影的名稱和評分

# 利用xpath解析html內容
    def parsingHtml(self, html):
        try:
            # 選擇文件中的節點,而不考慮它們的位置。選取屬性class="movie-list"的 dl 節點,這裡返回的是dd列表集合
            movieList = html.xpath('//dl[@class="movie-list"]/dd') 

            if len(movieList):
                for item in movieList:
                    # 從當前的 dd 節點選取屬性data-act="movies-click"的 a 的節點的文字內容,這裡匹配的是電影名稱
                    movieName = item.xpath('.//a[@data-act="movies-click"]/text()')[0]
                    # 從當前的 dd 節點選取屬性class="channel-detail channel-detail-orange的 div 的節點的文字內容,這裡匹配的是暫無評分
                    moviePa = item.xpath('.//div[@class="channel-detail channel-detail-orange"]/text()')
                    # moviePa沒有值說明是有具體評分
                    if not moviePa:
                        # 匹配評分的整數
                        integer = item.xpath('.//div[@class="channel-detail channel-detail-orange"]/i[@class="integer"]/text()')
                        # 匹配評分的小數
                        fraction = item.xpath('./div[@class="channel-detail channel-detail-orange"]/i[@class="fraction"]/text()')
                        moviePa = integer + fraction
                    yield({
                        'movieName': movieName,
                        'moviePa': ''.join(moviePa)
                    })
            return
        except Exception as e:
            print('err2:' + str(e))
            return
複製程式碼

run裡面呼叫movieList = self.parsingHtml(xmls),通過for迴圈列印電影資料

for item in movieList:
    strs = '電影:%s -- 評分: %s' % (item['movieName'], item['moviePa'])
    print(strs)
複製程式碼

輸出如下:

獲取url:http://maoyan.com/films?offset=0
電影:復仇者聯盟3:無限戰爭 -- 評分: 8.6
電影:我是你媽 -- 評分: 8.2
電影:後來的我們 -- 評分: 8.1
電影:超時空同居 -- 評分: 8.8
電影:寂靜之地 -- 評分: 暫無評分
電影:幕後玩家 -- 評分: 8.1
電影:狂暴巨獸 -- 評分: 9.0
電影:戰犬瑞克斯 -- 評分: 8.2
電影:巴霍巴利王2:終結 -- 評分: 8.7
電影:親愛的,我要和別人結婚了 -- 評分: 8.2
電影:小公主艾薇拉與神祕王國 -- 評分: 5.7
電影:晝顏 -- 評分: 暫無評分
電影:頭號玩家 -- 評分: 9.1
電影:七號公館 -- 評分: 暫無評分
電影:冰雪女王3:火與冰 -- 評分: 8.2
電影:侏羅紀世界2 -- 評分: 暫無評分
電影:瑪麗與魔女之花 -- 評分: 8.0
電影:復仇者聯盟4 -- 評分: 暫無評分
電影:青年馬克思 -- 評分: 8.4
電影:狄仁傑之四大天王 -- 評分: 暫無評分
電影:愛情公寓 -- 評分: 暫無評分
電影:路過未來 -- 評分: 暫無評分
電影:21克拉 -- 評分: 8.2
電影:遊俠索羅:星球大戰外傳 -- 評分: 暫無評分
電影:午夜十二點 -- 評分: 3.5
電影:龍在哪裡? -- 評分: 8.6
電影:火魔高跟鞋 -- 評分: 暫無評分
電影:動物世界 -- 評分: 暫無評分
電影:犬之島 -- 評分: 7.9
電影:厲害了,我的國 -- 評分: 9.6
複製程式碼

自動獲取下一頁的資料

自此已經可以請求網頁並且獲取需要的資料,接下來自動請求下一頁。通過分析發現URl都是http://maoyan.com/films拼接?offset=x這個引數,x是當前頁數的起始數,第一頁x為0,第二頁x為30,以此類推。剛好下一頁的href就是這個引數。

寫個def獲取下一個的引數

# 匹配下一頁
def getNextPage(self, html):
    try:
        # 獲取a元素的文字值為下一頁的href屬性
        result = html.xpath('//a[text()="下一頁"]/@href')
        return result[0] if len(result) else False
    except Exception as e:
        print('err3:' + str(e))
        return False
複製程式碼

run裡面通過遞迴的方式實現自動獲取下一頁的資料,並隨機休眠5-20秒以預防被貓眼拒絕訪問

NextPage = self.getNextPage(xmls)
    if NextPage:
        sleepTime = random.randint(5, 20)
        print('休眠時間:%s' % (sleepTime))
        # time.sleep(sleepTime)
        sarr = list(range(sleepTime))
        sarr.sort(reverse=True)
        for iss in sarr:
            time.sleep(1)
            print('倒數計時:' + str(iss))
        self.run(NextPage)
複製程式碼

到這裡已經基本完成了一個爬蟲

設定cookie來獲取需要登陸的網站的資料

有些網站內容必須需要登陸了才能訪問,手動設定cookie的方式應該是比較省力的,通過瀏覽器登陸網頁後,通過F12 - Network - Headers - Request Headers 找到 Cookie 欄位裡面包含了cookie資訊,寫個def把字串轉為字典

def dictToCookie(self):
    cookie_str = 'uuid=1A6E888B4A4B29B16FBA1299108DBE9C716675A309C3BAA9D55E347DEB6E83EC; _lxsdk_cuid=16367a2e6a9c8-0d58eea69a4d89-3c3c5905-1fa400-16367a2e6a9c8; _lxsdk=1A6E888B4A4B29B16FBA1299108DBE9C716675A309C3BAA9D55E347DEB6E83EC; _csrf=89ce8482c620a863330c9ffcaf2e20579a55a76cbd8637ac62a2b9f72c172a7b; _lx_utm=utm_source%3DBaidu%26utm_medium%3Dorganic; lt=DiIgzaThdLC5XZdcq4joMkZRpD8AAAAA3QUAAIpNTDuzpXNwUmtKXTXO6i9RayLLEM7KoYPwtHI1-tbRoMyue5EEa87kRKXrkA9SxQ; lt.sig=HiWqeKJoko8oWV-YaVVbIHt4iXQ; __mta=46043675.1526452119497.1526534209978.1526537087635.59; _lxsdk_s=1636cb36a83-aff-08c-b8a%7C%7C2'
    cookie_dict = dict()
    for item in cookie_str.split(';'):
        key, value = item.split('=', 1)  # 1代表只分一次,得到兩個資料
        cookie_dict[key] = value
    return cookie_dict
複製程式碼

然後在class__init__裡面把字典轉為cookiejar並設定cookie

cookie_dict = self.dictToCookie()
self.session.cookies = requests.utils.cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True)
複製程式碼

完整程式碼

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2018-05-16 15:14:06
# @Author  : Your Name (you@example.org)
# @Link    : link
# @Version : 1.0.0
# 獲取貓眼熱門電影

import time
import requests
import random
from lxml import etree


class MaoYanHot:
    def __init__(self):
        self.session = requests.session()
        self.baseUrl = 'http://maoyan.com/films'
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
            'Accept': 'text/html;q=0.9,*/*;q=0.8',
            'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
            'Accept-Encoding': 'gzip',
            'Connection': 'close',
            'Referer': None  # 注意如果依然不能抓取的話,這裡可以設定抓取網站的host
        }
        cookie_dict = self.dictToCookie()
        self.session.cookies = requests.utils.cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True)

    # 轉為字典
    def dictToCookie(self):
        cookie_str = 'uuid=1A6E888B4A4B29B16FBA1299108DBE9C716675A309C3BAA9D55E347DEB6E83EC; _lxsdk_cuid=16367a2e6a9c8-0d58eea69a4d89-3c3c5905-1fa400-16367a2e6a9c8; _lxsdk=1A6E888B4A4B29B16FBA1299108DBE9C716675A309C3BAA9D55E347DEB6E83EC; _csrf=89ce8482c620a863330c9ffcaf2e20579a55a76cbd8637ac62a2b9f72c172a7b; _lx_utm=utm_source%3DBaidu%26utm_medium%3Dorganic; lt=DiIgzaThdLC5XZdcq4joMkZRpD8AAAAA3QUAAIpNTDuzpXNwUmtKXTXO6i9RayLLEM7KoYPwtHI1-tbRoMyue5EEa87kRKXrkA9SxQ; lt.sig=HiWqeKJoko8oWV-YaVVbIHt4iXQ; __mta=46043675.1526452119497.1526534209978.1526537087635.59; _lxsdk_s=1636cb36a83-aff-08c-b8a%7C%7C2'
        cookie_dict = dict()
        for item in cookie_str.split(';'):
            key, value = item.split('=', 1)  # 1代表只分一次,得到兩個資料
            cookie_dict[key] = value
        return cookie_dict

    # 獲取網頁內容
    def getHtmlContent(self, url):
        try:
            respone = self.session.get(url, headers=self.headers)
            if respone.status_code == 200:
                return respone.content.decode('utf-8')
            return False
        except Exception as e:
            print('err1:' + str(e))
            return False

    # 把html轉為xml
    def htmlToXml(self, contents):
        # 手動閉合dd標籤
        contents = contents.replace('<dd', '</dd><dd')
        return etree.HTML(contents)

    # 利用xpath解析html內容
    def parsingHtml(self, html):
        try:
            movieList = html.xpath('//dl[@class="movie-list"]/dd')

            if len(movieList):
                for item in movieList:
                    movieName = item.xpath('.//a[@data-act="movies-click"]/text()')[0]
                    moviePa = item.xpath('.//div[@class="channel-detail channel-detail-orange"]/text()')
                    if not moviePa:
                        integer = item.xpath('.//div[@class="channel-detail channel-detail-orange"]/i[@class="integer"]/text()')
                        fraction = item.xpath('./div[@class="channel-detail channel-detail-orange"]/i[@class="fraction"]/text()')
                        moviePa = integer + fraction
                    yield({
                        'movieName': movieName,
                        'moviePa': ''.join(moviePa)
                    })
            return
        except Exception as e:
            print('err2:' + str(e))
            return

    # 匹配下一頁
    def getNextPage(self, html):
        try:
            result = html.xpath('//a[text()="下一頁"]/@href')
            return result[0] if len(result) else False
        except Exception as e:
            print('err3:' + str(e))
            return False

    # 開始訪問
    def run(self, pages='?offset=18180'):
        url = self.baseUrl + pages
        print('獲取url:' + url)
        contents = self.getHtmlContent(url)
        if contents:
            xmls = self.htmlToXml(contents)
            movieList = self.parsingHtml(xmls)

            file_object = open('movie.txt', 'a+', encoding='utf-8')
            file_object.write('獲取url:' + url + "\n")
            for item in movieList:
                strs = '電影:%s -- 評分: %s' % (item['movieName'], item['moviePa'])
                print(strs)
                file_object.write(strs + "\n")
            file_object.close()

            NextPage = self.getNextPage(xmls)
            if NextPage:
                sleepTime = random.randint(5, 20)
                print('休眠時間:%s' % (sleepTime))
                # time.sleep(sleepTime)
                sarr = list(range(sleepTime))
                sarr.sort(reverse=True)
                for iss in sarr:
                    time.sleep(1)
                    print('倒數計時:' + str(iss))
                self.run(NextPage)
        else:
            print('網頁內容獲取失敗')


MaoYanHot = MaoYanHot()
MaoYanHot.run()

複製程式碼

相關文章