python豆瓣電影爬蟲
可以爬取豆瓣電影信息,能夠將電影信息存進mysql數據庫,還能夠下載電影預告片。2、3、 4功能使用到selenium庫
一個例程運行截圖
下載好的電影預告片
MySQL存儲的數據
數據表構造
這是程序流程圖,詳細寫明了本爬蟲的運行流程
爬蟲程序代碼
# Author:YFAN import random import requests import lxml.etree from time import sleep import re import pymysql from selenium import webdriver #瀏覽器請求頭 headerlist = [ 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/61.0', 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36', 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.5; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36', 'Mozilla/5.0 (Windows NT 6.1; rv:50.0) Gecko/20100101 Firefox/50.0', 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:50.0) Gecko/20100101 Firefox/50.0', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; Trident/5.0)', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/602.2.14 (KHTML, like Gecko) Version/10.0.1 Safari/602.2.14', 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36', 'Mozilla/5.0 (iPad; CPU OS 10_1_1 like Mac OS X) AppleWebKit/602.2.14 (KHTML, like Gecko) Version/10.0 Mobile/14B100 Safari/602.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:49.0) Gecko/20100101 Firefox/49.0', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0'] #---------分 --割 --線 ------------------- #數據庫類 #完成數據插入 查詢操作 class mysql(): # 查詢函數1 def select1(self, str): # 打開數據庫連接 db = pymysql.connect("localhost", 'root', 'yf123457.', 'yf') # 獲取游標 cursor = db.cursor() # SQL 查詢語句 sql = "select * from movie2 where name like '%{}%'".format(str) try: cursor.execute(sql) result = cursor.fetchall()#獲取查詢內容 print("搜索結果有{}個".format(len(result))) index = 1 for i in result: print("\nIndex:{}".format(index)) print("豆瓣鏈接:{}".format(i[10])) print("片名:{}".format(i[0])) print("導演:{}".format(i[1])) print("編劇:{}".format(i[2])) print("主演:{}".format(i[3])) print("類型:{}".format(i[4])) print("上映日期:{}".format(i[5])) print("片長:{}".format(i[6])) print("IMDb:{}".format(i[7])) print("簡介:\n{}".format(i[8])) print("預告片鏈接:{}".format(i[9])) index += 1 print() except: print("數據查詢異常") db.rollback()#回滾 db.close()#關閉數據庫連接 # 查詢函數2 def select2(self, str): # 打開數據庫連接 db = pymysql.connect("localhost", 'root', 'yf123457.', 'yf') # 獲取游標 cursor = db.cursor() # SQL 查詢語句 sql = "select * from movie2 where type like '%{}%'".format(str) try: cursor.execute(sql) result = cursor.fetchall()#獲取查詢內容 print("搜索結果有{}個".format(len(result))) index = 1 for i in result: print("\nIndex:{}".format(index)) print("豆瓣鏈接:{}".format(i[10])) print("片名:{}".format(i[0])) print("導演:{}".format(i[1])) print("編劇:{}".format(i[2])) print("主演:{}".format(i[3])) print("類型:{}".format(i[4])) print("上映日期:{}".format(i[5])) print("片長:{}".format(i[6])) print("IMDb:{}".format(i[7])) print("簡介:\n{}".format(i[8])) print("預告片鏈接:{}".format(i[9])) index += 1 print() except: db.rollback() print("數據查詢異常") db.close()#關閉數據庫連接 #插入函數1 def insert(self,top,moviename,director,writer,actors,type,date,timelong,IMDburl,introduction,tralerurl,movieurl): # 打開數據庫連接 db = pymysql.connect("localhost", "root", "yf123457.", "yf") # 使用cursor()方法獲取操作游標 cursor = db.cursor() # 電影簡介內容出現""雙引號 無法直接插入數據表中 需要進行處理 # 使用正則替換,便可以插入數據表中 temp = re.compile("\"") text = temp.sub("\\\"", introduction) #SQL 插入語句 sql = """INSERT INTO movie VALUES ({}, "{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}")""" \ .format(top,moviename, director, writer, actors, type, date, timelong, IMDburl, text, tralerurl, movieurl) # SQL 查詢語句 sql2 = "select name from movie where name='{}'".format(moviename) try: # 執行sql語句 cursor.execute(sql2) # 提交到數據庫執行 result = cursor.fetchall() if len(result)==0: # 執行sql語句 cursor.execute(sql) # 提交到數據庫執行 db.commit() print("數據插入成功") else: print("數據表中記錄已存在") except: # 如果發生錯誤則回滾 db.rollback() # 關閉數據庫連接 db.close() #插入函數2 def insert2(self, moviename, director, writer, actors, type, date, timelong, IMDburl, introduction, tralerurl, movieurl): # 打開數據庫連接 db = pymysql.connect("localhost", "root", "yf123457.", "yf") # 使用cursor()方法獲取操作游標 cursor = db.cursor() # 電影簡介內容出現""雙引號 無法直接插入數據表中 需要進行處理 # 使用正則替換,便可以插入數據表中 temp = re.compile("\"") text = temp.sub("\\\"", introduction) # SQL 插入語句 sql = """INSERT INTO movie2 VALUES ("{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}")""" \ .format(moviename, director, writer, actors, type, date, timelong, IMDburl, text, tralerurl, movieurl) sql2 = "select name from movie2 where name='{}'".format(moviename) try: # 執行sql語句 cursor.execute(sql2) # 提交到數據庫執行 result = cursor.fetchall() if len(result)==0: # 執行sql語句 cursor.execute(sql) # 提交到數據庫執行 db.commit() print("數據插入成功") else: print("數據表中記錄已存在") except: # 如果發生錯誤則回滾 db.rollback() # 關閉數據庫連接 db.close() #---------分 --割 --線 ------------------- #豆瓣爬蟲類 #豆瓣網 [url=https://movie.douban.com/]https://movie.douban.com/[/url] #豆瓣TOP250電影榜 [url=https://movie.douban.com/top250?start=0&filter=]https://movie.douban.com/top250?start=0&filter=[/url] class spider(): path=""#文件下載路徑 mysqlflag=True#是否將數據插入數據庫 downloadflag=True#是否下載預告片 # 構造函數 設置預告片存儲路徑 是否下載預告片? 是否插入數據表? def __init__(self,path,downloadflag,mysqlflag): self.path=path self.downloadflag=downloadflag self.mysqlflag=mysqlflag #遍歷頁面 def foreach(self): print() print("豆瓣電影TOP250榜電影") i=0 while i<10: #遍歷 #flag 為獲取當前頁電影鏈接返回的值 如果為1則遍歷下一頁 不為1繼續遍歷本頁 flag=self.foreachpPageurl(i) if flag==1: i+=1 else: print("重新訪問當前頁") # 獲取當前頁電影鏈接 def foreachpPageurl(self, page): url = "https://movie.douban.com/top250?start={}&filter=".format(page * 25) header = dict() header["user-agent"] = random.choice(headerlist) try: #通過requests.get()方法 獲取網頁信息 r = requests.get(url, headers=header) r.raise_for_status()#網頁狀態碼 200 2開頭表示正常 4或5開頭的拋出異常 #使用lxml將網頁的源碼轉換成xml,然后使用xpath()進行xml解析 xml = lxml.etree.HTML(r.text) #獲取xml下類名為hd的div標簽下的所有a標簽的href屬性 href裝着電影鏈接url a = xml.xpath("//div[@class='hd']/a/@href") i = 0 count = page * 25 + 1 while i < (len(a)): print("TOP {}".format(count)) #遍歷當前頁面的所有電影鏈接 flag = self.getMovieMessage(count, a[i],0) sleep(3) # 休息兩秒 應付反爬 if flag == 1: i += 1 count += 1 print("") else: print("重新獲取電影詳細信息") return 1 except: print("當前頁無法訪問!") return 0 #搜索豆瓣電影 def seachMovie(self,name): chrome_path = 'C:/Program Files (x86)/Google/Chrome/Application/chromedriver.exe' # os.environ['webdriver.chrome.driver'] = chrome_path#設置系統環境變量 drive = webdriver.Chrome(chrome_path) # 打開谷歌瀏覽器 # 打開一個網址 drive.get('https://movie.douban.com/subject_search?search_text={}&cat=1002'.format(name)) try: #通過xpath查找網頁標簽 drive.implicitly_wait(3) # 等待3秒 a = drive.find_element_by_xpath('//*[@id="root"]/div/div[2]/div[1]/div[1]/div[1]/div[1]/a') url = a.get_attribute('href') a.click()#點擊連接 #獲取電影詳細信息 self.getMovieMessage(0,url,1) drive.implicitly_wait(5) # 等待5秒 except: print("error") #drive.close()#關閉頁面 #最新上映電影 def LatestReleases(self): chrome_path = 'C:/Program Files (x86)/Google/Chrome/Application/chromedriver.exe' # os.environ['webdriver.chrome.driver'] = chrome_path#設置系統環境變量 drive = webdriver.Chrome(chrome_path) # 打開谷歌瀏覽器 drive.get('https://movie.douban.com/') # 打開一個網址 try: #等待3秒 給予時間給瀏覽器加載javascript drive.implicitly_wait(3) # 通過xpath查找網頁標簽 a = drive.find_elements_by_xpath('//*[@id="screening"]/div[2]/ul/li/ul/li[1]/a') #定義一個集合 用來存儲最新上映電影鏈接地址 s = set() for i in a: s.add(i.get_attribute("href")) print("最新上映的電影有{}個".format(len(s))) l=list(s)#set轉為list drive.implicitly_wait(3) # 等待3秒 for i in range(len(l)): print() print("第{}個".format(i+1)) # 獲取電影詳細信息 self.getMovieMessage(i+1,l[i],1) print() except: print("error") # drive.close()#關閉頁面 #最近熱門電影 def recentot(self): chrome_path = 'C:/Program Files (x86)/Google/Chrome/Application/chromedriver.exe' # os.environ['webdriver.chrome.driver'] = chrome_path#設置系統環境變量 drive = webdriver.Chrome(chrome_path)#打開谷歌瀏覽器 drive.get( 'https://movie.douban.com/' )#打開一個網址 try: # 等待3秒 給予時間給瀏覽器加載javascript drive.implicitly_wait(3) # 通過xpath查找網頁標簽 a=drive.find_elements_by_xpath('//*[@id="content"]/div/div[2]/div[4]/div[3]/div/div[1]/div/div//a') # 定義一個集合 用來存儲最近熱門電影鏈接地址 s= set() for i in a: s.add(i.get_attribute("href")) l=list(s)#set轉為list print("最近熱門電影有{}個".format(len(l))) for i in range(len(l)): print() print("第{}個".format(i+1)) #獲取電影詳細信息 self.getMovieMessage(0,l[i],1) print() drive.implicitly_wait(3)#等待3秒 except: print("error") # drive.close()#關閉瀏覽器 # 獲取電影詳細信息 def getMovieMessage(self, top, url,insertflag):#其中flag 用來決定插入哪個數據表 movie或movie2 用來判斷使用哪一個數據庫插入函數 print("豆瓣電影鏈接:{}".format(url)) header = dict() #獲取請求頭的user-agent字典,應付反爬處理 header["user-agent"] = random.choice(headerlist)#通過random.choice隨機抽取一個user-agent director = '' # 導演 writer = '' # 編劇 actors = '' # 主演 type = '' # 類型 date = '' # 上映時間 timelong = '' # 時長 IMDb = '' # IMDb鏈接 text = '' # 簡介 video = '' # 預告片 try: #通過requests.get獲取url鏈接 r = requests.get(url, headers=header) r.raise_for_status()#網頁狀態碼 200 xml = lxml.etree.HTML(r.text)#將網頁源碼轉為xml 用lxml庫進行解析 # 獲取電影名 n = xml.xpath("//div[@id='content']/h1/span")#通過xpath獲取網頁標簽 name = n[0].text + n[1].text print("片名:{}".format(name)) div1 = xml.xpath("//div[@id='info']//span[@class='attrs']") for i in range(len(div1)): if i == 0: #獲取電影導演 x1 = div1[0].xpath("a") for i in x1: director += i.text + " " elif i == 1: #獲取電影編劇 x2 = div1[1].xpath("a") for i in x2: writer += i.text + " " elif i == 2: #獲取電影的前幾個主演 x3 = div1[2].xpath("a") for i in range(5): if i >= len(x3): break actors += x3[i].text + " " # 以上這么寫原因:有些電影無編劇 無主演 健壯代碼 print("導演:{}".format(director)) print("編劇:{}".format(writer)) print("主演:{}".format(actors)) # 獲取電視;類型 x4 = xml.xpath("//span[@property='v:genre']") for i in x4: type += i.text + " " print("類型:{}".format(type)) # 獲取電視上映日期 x5 = xml.xpath("//span[@property='v:initialReleaseDate']") for i in x5: date += i.text + " " print("上映日期:{}".format(date)) # 獲取電影片長 x6 = xml.xpath("//span[@property='v:runtime']") for i in x6: timelong += i.text + ' ' print("片長:{}".format(timelong)) #獲取電影的IMDb鏈接 div2 = xml.xpath("//div[@id='info']/a/@href") if len(div2)!=0: IMDb = div2[0] print("IMDb鏈接:{}".format(IMDb)) #獲取電影簡介 x7 = xml.xpath("//span[@property='v:summary']/text()") for i in range(len(x7)): text += " " + x7[i].strip() if i < len(x7) - 1: text += '\n' print("簡介:\n{}".format(text)) #獲取預告片鏈接 video = xml.xpath("//a[@title='預告片']/@href") if len(video) >= 1: print("預告片鏈接:{}".format(video[0])) while True: sleep(2) # 休息2秒 #前往預告片鏈接頁面,獲取預告片的播放地址,實現下載預告片功能 flag = self.getMovieTrailer(name, video[0]) if flag == 1: #下載成功后,數據庫插入數據 #根據myusql來決定是否將數據插入數據表中 if self.mysqlflag: if insertflag==0: #將電影詳細信息插入數據庫 mysql().insert(top, name,director,writer,actors,type,date,timelong,IMDb,text,video[0],url) elif insertflag == 1: mysql().insert2(name,director,writer,actors,type,date,timelong,IMDb,text,video[0],url) return 1 else: print("重新獲取電影預告片") else: #有些電影沒有預告片,數據庫插入數據 # 根據myusql來決定是否將數據插入數據表中 print("該電影找不到預告片") if self.mysqlflag: if insertflag==0: #執行插入函數1 mysql().insert(top, name, director, writer, actors, type, date, timelong, IMDb, text,"", url) elif insertflag==1: #執行插入函數2 mysql().insert2(name, director, writer, actors, type, date, timelong, IMDb, text, "", url) return 1 except: print("無法訪問電影詳細信息") return 0 # 獲取電影預告片 def getMovieTrailer(self, name, url): header = dict() header['user-agent'] = random.choice(headerlist) try: r = requests.get(url, headers=header) xml = lxml.etree.HTML(r.text) #預告片頁面使用了javascript特性,預告片url地址藏在了<script></script>內 #獲取到指定的script標簽后,對script標簽下代碼轉換成字符串后進行字符串處理 #處理后便可以獲取到預告片的實際播放地址url #獲取url后便可以進行下載操作,獲取不到返回0表示該電影沒有預告片 script = xml.xpath("//script[@type='application/ld+json']/text()") str1 = str(script) start = str1.find("http://vt1") end = str1.find(".mp4") result = str1[start:end + 4] #根據downloadflag開關來決定是否下載預告片 if self.downloadflag : while True: sleep(2) # 休息2秒 # 下載預告片 flag = self.download(name, result) if flag == 1: return 1 else: print("下載失敗,重新下載") else: return 1 except: print("無法獲取電影預告片") return 0 # 下載預告片 def download(self, name, url): header = dict() header['user-agent'] = random.choice(headerlist) try: r = requests.get(url, headers=header, timeout=30) # 處理一下文件名 有些電影名中帶 /\*:"?<>| ,windows中的文件名是不能有這些字符 NAME = '' for i in range(len(name)): if name[i] not in '/\*:"?<>|': NAME += name[i] else: NAME += "" filename = self.path + '\\' + NAME + ".mp4"#預告片路徑 print("保存路徑:{}".format(filename)) #通過二進制寫入文件 with open(filename, 'wb') as f: f.write(r.content) f.close()#關閉流 print("下載成功") return 1 except: print("下載電影預告片失敗") return 0 #--------------------------- #----------------分割線----------- #菜單函數 def menu(): print("""------------------主-----菜-----單-------------------------- 1 豆瓣電影TOP250榜電影 2 豆瓣電影最新上映電影 3 豆瓣電影最近熱門電影 4 豆瓣電影動態搜索電影 5 查詢數據庫中電影信息 0 退-出-爬-蟲 """) try: i=int(input("輸入操作(0-5):")) if i < 0 or i > 5: print("操作有誤,重新操作") return 1 if i == 0: return 0 if i == 5: return menu2()#次級帶單2 s = menu1()#次級菜單1 if s != 0: if i == 1: s.foreach()#執行豆瓣電影TOP250榜遍歷函數 elif i == 2: s.LatestReleases()#獲取豆瓣最新上映電影 elif i == 3: s.recentot()#獲取豆瓣最近熱門電影 elif i == 4: s.seachMovie(input("輸入電影關鍵詞:"))#豆瓣搜索電影 else: print("操作有誤,重新操作") return 1 return 1 except: print("輸入有誤,請按規定輸入數字") #二級菜單1 獲取spider爬蟲類對象 def menu1(): print("""------------------次---級---菜---單----------------------- 1 不下載電影預告片,數據庫不存儲電影信息 2 下載電影預告片,數據庫存儲電影信息 3 數據庫只存儲電影信息 4 只下載電影預告片 """) try: i = int(input("輸入操作(1-4):")) if i == 1: return spider("", False, False)#返回一個spider類對象 elif i == 2: return spider(input("輸入預告片存儲路徑:"), True, True)#返回一個spider類對象 elif i == 3: return spider("", False, True) elif i == 4: return spider(input("輸入預告片存儲路徑:"), True, False)#返回一個spider類對象 else: print("操作有誤,返回上一級") return 0 except: print("輸入有誤,請按規定輸入數字") return 0 #二級菜單2 查詢數據庫電影信息 def menu2(): m=mysql() print("""------------------次---級---菜---單----------------------- 1 電影名查詢 2 類型查詢 """) try: i=int(input("輸入操作(1-2):")) except: print("輸入有誤,請按規定輸入數字") return 0 try: if i==1: m.select1(input("輸入查詢電影名關鍵字:")) elif i==2: m.select2(input("輸入查詢電影類型關鍵字:")) else: print("操作有誤,返回上次菜單") return 1 except: print("查詢異常") return 0 # ----------------分----割----線----------- #主函數 if __name__ == '__main__': while True: print() flag=menu()# if flag==0:break sleep(3)