Python3 爬虫实战 — 豆瓣电影TOP250【requests、Xpath、正则表达式、CSV、二进制数据储存】
- 爬取時間:2019-09-27
- 爬取難度:★★☆☆☆☆
- 請求鏈接:https://movie.douban.com/top250 以及每部電影詳情頁
- 爬取目標:爬取榜單上每一部電影詳情頁的數(shù)據(jù),保存為 CSV 文件;下載所有電影海報到本地
- 涉及知識:請求庫 requests、解析庫 lxml、Xpath 語法、正則表達式、CSV 和二進制數(shù)據(jù)儲存、列表操作
- 完整代碼:https://github.com/TRHX/Python3-Spider-Practice/tree/master/BasicTraining/douban-top250
- 其他爬蟲實戰(zhàn)代碼合集(持續(xù)更新):https://github.com/TRHX/Python3-Spider-Practice
- 爬蟲實戰(zhàn)專欄(持續(xù)更新):https://itrhx.blog.csdn.net/article/category/9351278
文章目錄
- 【1x00】循環(huán)爬取網(wǎng)頁模塊
- 【2x00】解析模塊
- 【2x01】Xpath 解析排名、電影名、評分信息
- 【2x02】Xpath 解析參評人數(shù)
- 【2x03】正則表達式解析制片國家、語言
- 【3x00】返回解析數(shù)據(jù)
- 【4x00】數(shù)據(jù)儲存模塊
- 【5x00】完整代碼
- 【6x00】數(shù)據(jù)截圖
- 【7x00】程序不足的地方
【1x00】循環(huán)爬取網(wǎng)頁模塊
觀察豆瓣電影 Top 250,請求地址為:https://movie.douban.com/top250
每頁展示25條電影信息,照例翻頁觀察 url 的變化:
第一頁:https://movie.douban.com/top250
第二頁:https://movie.douban.com/top250?start=25&filter=
第三頁:https://movie.douban.com/top250?start=50&filter=
一共有10頁,每次改變的是 start 的值,利用一個 for 循環(huán),從 0 到 250 每隔 25 取一個值拼接到 url,實現(xiàn)循環(huán)爬取每一頁,由于我們的目標是進入每一部電影的詳情頁,然后爬取詳情頁的內(nèi)容,所以我們可以使用 Xpath 提取每一頁每部電影詳情頁的 URL,將其賦值給 m_urls,并返回 m_urls,m_urls 是一個列表,列表元素就是電影詳情頁的 URL
def index_pages(number):url = 'https://movie.douban.com/top250?start=%s&filter=' % numberindex_response = requests.get(url=url, headers=headers)tree = etree.HTML(index_response.text)m_urls = tree.xpath("//li/div/div/a/@href")return m_urlsif __name__ == '__main__':for i in range(0, 250, 25):movie_urls = index_pages(i)【2x00】解析模塊
定義一個解析函數(shù) parse_pages(),利用 for 循環(huán),依次提取 index_pages() 函數(shù)返回的列表中的元素,也就是每部電影詳情頁的 URL,將其傳給解析函數(shù)進行解析
def index_pages(number):expressionsdef parse_pages(url):expressionsif __name__ == '__main__':for i in range(0, 250, 25):movie_urls = index_pages(i)for movie_url in movie_urls:results = parse_pages(movie_url)詳細看一下解析函數(shù) parse_pages(),首先要對接收到的詳情頁 URL 發(fā)送請求,獲取響應(yīng)內(nèi)容,然后再使用 Xpath 提取相關(guān)信息
def parse_pages(url):movie_pages = requests.get(url=url, headers=headers)parse_movie = etree.HTML(movie_pages.text)【2x01】Xpath 解析排名、電影名、評分信息
其中排名、電影名和評分信息是最容易匹配到的,直接使用 Xpath 語法就可以輕松解決:
# 排名 ranking = parse_movie.xpath("//span[@class='top250-no']/text()")# 電影名 name = parse_movie.xpath("//h1/span[1]/text()")# 評分 score = parse_movie.xpath("//div[@class='rating_self clearfix']/strong/text()")【2x02】Xpath 解析參評人數(shù)
接下來準備爬取有多少人參與了評價,分析一下頁面:
如果只爬取這個 <span> 標簽下的數(shù)字的話,沒有任何提示信息,別人看了不知道是啥東西,所以把 人評價 這三個字也爬下來的話就比較好了,但是可以看到數(shù)字和文字不在同一個元素標簽下,而且文字部分還有空格,要爬取的話就要把 class="rating_people" 的 a 標簽下所有的 text 提取出來,然后再去掉空格:
這樣做太麻煩了,我們可以直接提取數(shù)字,得到一個列表,然后使用另一個帶有提示信息的列表,將兩個列表的元素合并,組成一個新列表,這個新列表的元素就是提示信息+人數(shù)
# 參評人數(shù) value = parse_movie.xpath("//span[@property='v:votes']/text()") # 合并元素 number = [" ".join(['參評人數(shù):'] + value)]# 此時 number = ['參評人數(shù):1617307']【2x03】正則表達式解析制片國家、語言
接下來嘗試爬取制片國家/地區(qū)、語言等信息:
分析頁面可以觀察到,制片國家/地區(qū)和語言結(jié)構(gòu)比較特殊,沒有特別的 class 或者 id 屬性,所包含的層次關(guān)系也太復(fù)雜,所以這里為了簡便,直接采用正則表達式來匹配信息,就沒有那么復(fù)雜了:
【3x00】返回解析數(shù)據(jù)
其他剩下的信息皆可利用以上方法進行提取,所有信息提取完畢,最后使用 zip() 函數(shù),將所有提取的對象作為參數(shù),將對象中對應(yīng)的元素打包成一個個元組,然后返回由這些元組組成的列表
return zip(ranking, name, score, number, types, country, language, date, time, other_name, director, screenwriter, performer, m_url, imdb_url)【4x00】數(shù)據(jù)儲存模塊
定義一個數(shù)據(jù)保存函數(shù) save_results()
def save_results(data):with open('douban.csv', 'a', encoding="utf-8-sig") as fp:writer = csv.writer(fp)writer.writerow(data)注意:編碼方式要設(shè)置為 utf-8-sig,如果設(shè)置為 utf-8,則文件會亂碼,不設(shè)置編碼,則可能會報一下類似錯誤:
UnicodeEncodeError: 'gbk' codec can't encode character '\ub3c4' in position 9: illegal multibyte sequence可以看到錯誤出現(xiàn)在 \ub3c4 上,將該 Unicode 編碼轉(zhuǎn)換為中文為 ?,發(fā)現(xiàn)正是排名第 19 的電影:熔爐 ???,因為標題有韓文,所以在儲存為 CSV 文件時會報編碼錯誤,而將編碼設(shè)置為 utf-8-sig 就不會報錯,具體原因參見:《Python 中文日文漢字亂碼處理utf-8-sig》
接下來是保存電影的海報到本地:
# 保存電影海報 poster = parse_movie.xpath("//div[@id='mainpic']/a/img/@src") response = requests.get(poster[0]) name2 = re.sub(r'[A-Za-z\:\s]', '', name[0]) poster_name = str(ranking[0]) + ' - ' + name2 + '.jpg' dir_name = 'douban_poster' if not os.path.exists(dir_name):os.mkdir(dir_name) poster_path = dir_name + '/' + poster_name with open(poster_path, "wb")as f:f.write(response.content)解析電影詳情頁,使用 Xpath 提取海報的 URL,向該 URL 發(fā)送請求
圖片以 排名+電影名.jpg 的方式命名,但是由于提取的電影名部分含有特殊字符,比如排名第 10 的電影:忠犬八公的故事 Hachi: A Dog’s Tale,其中有個冒號,而 Windows 文件命名是不能包含這些字符的,所以我們直接去除電影名包含的英文字符、空白字符、特殊字符,只留下中文,代碼實現(xiàn): name2 = re.sub(r'[A-Za-z\:\s]', '', name[0])
定義一個文件夾名稱 douban_poster,利用 os 模塊判斷當前是否存在該文件夾,若不存在就創(chuàng)建一個
最后以二進制形式保存海報到當前目錄的 douban_poster 文件夾下
【5x00】完整代碼
# ============================================= # --*-- coding: utf-8 --*-- # @Time : 2019-09-27 # @Author : TRHX # @Blog : www.itrhx.com # @CSDN : https://blog.csdn.net/qq_36759224 # @FileName: douban.py # @Software: PyCharm # =============================================import requests from lxml import etree import csv import re import time import osheaders = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}def index_pages(number):url = 'https://movie.douban.com/top250?start=%s&filter=' % numberindex_response = requests.get(url=url, headers=headers)tree = etree.HTML(index_response.text)m_urls = tree.xpath("//li/div/div/a/@href")return m_urlsdef parse_pages(url):movie_pages = requests.get(url=url, headers=headers)parse_movie = etree.HTML(movie_pages.text)# 排名ranking = parse_movie.xpath("//span[@class='top250-no']/text()")# 電影名name = parse_movie.xpath("//h1/span[1]/text()")# 評分score = parse_movie.xpath("//div[@class='rating_self clearfix']/strong/text()")# 參評人數(shù)value = parse_movie.xpath("//span[@property='v:votes']/text()")number = [" ".join(['參評人數(shù):'] + value)]# value = parse_movie.xpath("//a[@class='rating_people']")# string = [value[0].xpath('string(.)')]# number = [a.strip() for a in string]# print(number)# 類型value = parse_movie.xpath("//span[@property='v:genre']/text()")types = [" ".join(['類型:'] + value)]# 制片國家/地區(qū)value = re.findall('<span class="pl">制片國家/地區(qū):</span>(.*?)<br/>', movie_pages.text)country = [" ".join(['制片國家:'] + value)]# 語言value = re.findall('<span class="pl">語言:</span>(.*?)<br/>', movie_pages.text)language = [" ".join(['語言:'] + value)]# 上映時期value = parse_movie.xpath("//span[@property='v:initialReleaseDate']/text()")date = [" ".join(['上映日期:'] + value)]# 片長value = parse_movie.xpath("//span[@property='v:runtime']/text()")time = [" ".join(['片長:'] + value)]# 又名value = re.findall('<span class="pl">又名:</span>(.*?)<br/>', movie_pages.text)other_name = [" ".join(['又名:'] + value)]# 導演value = parse_movie.xpath("//div[@id='info']/span[1]/span[@class='attrs']/a/text()")director = [" ".join(['導演:'] + value)]# 編劇value = parse_movie.xpath("//div[@id='info']/span[2]/span[@class='attrs']/a/text()")screenwriter = [" ".join(['編劇:'] + value)]# 主演value = parse_movie.xpath("//div[@id='info']/span[3]")performer = [value[0].xpath('string(.)')]# URLm_url = ['豆瓣鏈接:' + movie_url]# IMDb鏈接value = parse_movie.xpath("//div[@id='info']/a/@href")imdb_url = [" ".join(['IMDb鏈接:'] + value)]# 保存電影海報poster = parse_movie.xpath("//div[@id='mainpic']/a/img/@src")response = requests.get(poster[0])name2 = re.sub(r'[A-Za-z\:\s]', '', name[0])poster_name = str(ranking[0]) + ' - ' + name2 + '.jpg'dir_name = 'douban_poster'if not os.path.exists(dir_name):os.mkdir(dir_name)poster_path = dir_name + '/' + poster_namewith open(poster_path, "wb")as f:f.write(response.content)return zip(ranking, name, score, number, types, country, language, date, time, other_name, director, screenwriter, performer, m_url, imdb_url)def save_results(data):with open('douban.csv', 'a', encoding="utf-8-sig") as fp:writer = csv.writer(fp)writer.writerow(data)if __name__ == '__main__':num = 0for i in range(0, 250, 25):movie_urls = index_pages(i)for movie_url in movie_urls:results = parse_pages(movie_url)for result in results:num += 1save_results(result)print('第' + str(num) + '條電影信息保存完畢!')time.sleep(3)【6x00】數(shù)據(jù)截圖
【7x00】程序不足的地方
程序不足的地方:豆瓣電影有反爬機制,當程序爬取到大約 150 條數(shù)據(jù)的時候,IP 就會被封掉,第二天 IP 才會解封,可以考慮綜合使用多個代理、多個 User-Agent、隨機時間暫停等方法進行爬取
總結(jié)
以上是生活随笔為你收集整理的Python3 爬虫实战 — 豆瓣电影TOP250【requests、Xpath、正则表达式、CSV、二进制数据储存】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python3 爬虫学习笔记 C01 【
- 下一篇: 2017年平安银行微信申请信用卡秒批方法