點贊再看,養(yǎng)成好習慣
Python版本3.8.0,開發(fā)工具:Pycharm
寫在前面的話
目前為止,你應該已經(jīng)了解爬蟲的三個基本小節(jié):
如果上面三小節(jié)還有問題债鸡,可以點回去再復習一下
亏娜。作為基礎內(nèi)容并不是要求大家一定都掌握,特別是第三小節(jié)嫉戚,網(wǎng)頁解析用法特別多刨裆,一般人很難都記住。
我在寫這篇的時候也會時不時的翻回去看一看之前的文章彬檀,可能有的方法并不是最簡單的方法帆啃,但是只要達成目的
就ok,這里你們自由發(fā)揮
窍帝。
“小一哥努潘,為什么你這里用的是 find 方法解析,我用正則表達式可以嗎坤学?”
“當然可以啊疯坤,或許你的正則表達式實現(xiàn)起來更簡單”
那么,作為我們爬蟲的第一個小項目拥峦,我會盡可能的講清楚每一步代碼
贴膘,就算沒講到,也會有注釋
略号,不用擔心跟不上看不懂刑峡。
另外洋闽,雖然說是第一篇爬蟲文章,但我還是會對爬蟲的結果進行數(shù)據(jù)分析突梦。對于項目而言比較簡單诫舅,目的是讓大家了解整個分析的過程
堪藐。
記住一點:爬蟲永遠不是我們的終點逞盆,最多算是我們數(shù)據(jù)分析之路的踏板
。
源碼獲取方式在文末
正文
明確需求
我們今天要爬的數(shù)據(jù)是豆瓣電影Top250
捷凄,是的娃闲,只有250條數(shù)據(jù)虚汛,你沒猜錯。
輸入網(wǎng)址 https://movie.douban.com/top250
我們可以看到網(wǎng)頁長這樣:
250條數(shù)據(jù)
清清楚楚皇帮,沒有問題卷哩。
可以看到,這個頁面其實已經(jīng)包含了影片的主要內(nèi)容
:影片名属拾、排序将谊、編劇、主演渐白、年份尊浓、類型、評論人數(shù)纯衍、評分栋齿,基本上都在這個頁面中。但我點開詳細影片
之后托酸,發(fā)現(xiàn)了這個:
似乎這個頁面數(shù)據(jù)更全一些褒颈,我們爬數(shù)據(jù)要的是什么,肯定是數(shù)據(jù)越多越好啊励堡。相比這個詳細內(nèi)容,更是多了每個星級的影評占比堡掏,那我們肯定選擇它了啊
好应结,那理一下我們的思路
- 首先,進入豆瓣電影Top250泉唁,一共10頁鹅龄,每頁25個影片。
- 然后亭畜,針對每一頁的25個影片扮休,進入其詳細內(nèi)容頁面
- 最后,解析每個影片的詳細內(nèi)容拴鸵,保存內(nèi)容到數(shù)據(jù)庫中
寫一下偽代碼
# 遍歷10頁
data_movies # 保存所有影片數(shù)據(jù)集
for per_page in pages:
# 爬取10頁的每一頁數(shù)據(jù)
movies = craw_page_info(per_page)
# 遍歷每一頁的25個影片
for movie in movies:
# 爬取每個影片的詳細內(nèi)容
data_per_movie = craw_detail_info(movie)
# 保存每個影片信息到數(shù)據(jù)集中
data_movies.append(data_per_movie)
# 保存結果到數(shù)據(jù)庫中
data_movies_to_mysql
稍微解釋一下:兩層循環(huán)
玷坠,第一層是遍歷10頁網(wǎng)頁
蜗搔,因為其中每個網(wǎng)頁分別有25個影片,所以八堡,第二層循環(huán)又依次遍歷25個影片
獲取詳細信息樟凄,最后保存結果到數(shù)據(jù)庫中!
是不是兄渺,很缝龄,簡,單挂谍!
但是叔壤,實操起來你可能會遇到各種各樣的問題,做好心理準備口叙!
開始實操
首先炼绘,確定我們要輸出的影片字段
主要數(shù)據(jù)
包括:影片排序、影片名稱庐扫、影片導演饭望、影片編劇、影片主演形庭、影片又名铅辞、影片鏈接
關鍵數(shù)據(jù)
包括:影片類型、制片國家萨醒、影片語言斟珊、上映日期、影片片長
核心數(shù)據(jù)
包括:影片評分富纸、評論人數(shù)囤踩、5/4/3/2/1各星級對應的評論占比
字段如下
:
movie_rank:影片排序
movie_name:影片名稱
movie_director:影片導演
movie_writer:影片編劇
movie_starring:影片主演
movie_type:影片類型
movie_country:影片制片國家
movie_language:影片語言
movie_release_date:影片上映日期
movie_run_time:影片片長
movie_second_name:影片又名
movie_imdb_href:影片IMDb 鏈接
movie_rating:影片總評分
movie_comments_user:影片評論人數(shù)
movie_five_star_ratio:影片5星占比
movie_four_star_ratio:影片4星占比
movie_three_star_ratio:影片3星占比
movie_two_star_ratio:影片2星占比
movie_one_star_ratio:影片1星占比
movie_note:影片備注信息,一般為空
然后晓褪,開始主流程
確認一下主要參數(shù)堵漱,起始頁碼(默認為0),每頁影片25個涣仿,共10頁勤庐,
參數(shù)如下
:
start_page:起始頁碼
page_size:每一頁大小
pages:總頁碼
定義類對象
這里我們將每個影片封裝成一個對象,傳入我們的主要參數(shù)好港,設置爬蟲頭部愉镰,并建立和數(shù)據(jù)庫的相關連接
類定義對象如下
:
class DouBanMovie:
def __init__(self, url, start_page, pages, page_size):
"""
初始化
@param url: 爬取主網(wǎng)址
@param start_page: 起始頁碼
@param pages: 總頁碼(截止頁碼)
@param page_size: 每頁的大小
"""
self.url = url
self.start_page = start_page
self.pages = pages
self.page_size = page_size
self.data_info = []
self.pymysql_engine, self.pymysql_session = connection_to_mysql()
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'
}
“小一哥,你這里的數(shù)據(jù)庫連接用的是什么啊钧汹,我怎么看不太懂丈探?”
“我封裝了一下,數(shù)據(jù)庫的連接這里選用了 SQLAlchemy拔莱。"
不要著急碗降,以后會專門寫一篇 SQLAlchemy 關于數(shù)據(jù)庫的相關操作
# 創(chuàng)建基類,
Base = declarative_base()
def connection_to_mysql():
"""
連接數(shù)據(jù)庫
@return:
"""
engine = create_engine('mysql+pymysql://username:passwd@localhost:3306/db_name?charset=utf8')
Session = sessionmaker(bind=engine)
db_session = Session()
# 創(chuàng)建數(shù)據(jù)表
Base.metadata.create_all(engine)
return engine, db_session
確定主框架:
# 如果當前頁碼小于0隘竭,異常退出
if self.start_page < 0:
return ""
# 如果起始頁面大于總頁碼數(shù),退出
if self.start_page > self.pages:
return ""
# 若當前頁其實頁碼小于總頁數(shù)遗锣,繼續(xù)爬取數(shù)據(jù)
while self.start_page < pages:
# 拼接當前頁的網(wǎng)址
# 主爬蟲代碼
# 下一頁
self.start_page = self.start_page + 1
拼接當前頁的網(wǎng)址
這里解釋一下货裹,當我們?nèi)ピL問第一頁的時候發(fā)現(xiàn)網(wǎng)址如下
https://movie.douban.com/top250
去訪問下一頁的時候發(fā)現(xiàn)網(wǎng)址變化如下
https://movie.douban.com/top250?start=25&filter=
而再下一頁的網(wǎng)址變化如下:
https://movie.douban.com/top250?start=50&filter=
可以發(fā)現(xiàn),新的網(wǎng)址只是變化了后面的 start 參數(shù)
精偿,于是我們拼接出每一頁的網(wǎng)址:
start_number = self.start_page * self.page_size
new_url = self.url + '?start=' + str(start_number) + '&filter='
爬取第一個頁面
確定好主框架之后弧圆,我們需要去爬取第一個網(wǎng)頁
,也就是包含25個影片的頁面
笔咽。
這時候搔预,我們前三節(jié)提到的爬蟲實現(xiàn)方式直接拿過來:
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
}
# 爬取當前頁碼的數(shù)據(jù)
response = requests.get(url=new_url, headers=self.headers)
成功獲取到頁面數(shù)據(jù)之后,我們需要對頁面解析
叶组,拿到每一個影片跳轉(zhuǎn)詳細頁面的超鏈接
通過谷歌瀏覽器 F12 開發(fā)者工具可查看網(wǎng)頁源碼
可以看到每個影片的詳細信息在一個li 標簽
中拯田,而每個 li 標簽中都有一個class='pic' 的 div
,在 div 里面存在這樣一個 a 標簽
中
而這個 a 標簽的 href 正是我們要需要的 詳細頁面信息的超鏈接
確定了超鏈接位置所在甩十,打開我們上一節(jié)的 BeautifulSoup 詳解船庇,定位、解析
soup = BeautifulSoup(response.text, 'html.parser')
# 定位到每一個電影的 div (pic 標記的 div)
soup_div_list = soup.find_all(class_="pic")
# 遍歷獲取每一個 div 的電影詳情鏈接
for soup_div in soup_div_list:
# 定位到每一個電影的 a 標簽
soup_a = soup_div.find_all('a')[0]
movie_href = soup_a.get('href')
print(movie_href)
拿到當前頁面的25 個影片的詳細內(nèi)容的超鏈接
我們離成功又進了一步侣监!
爬取詳細頁面
同樣鸭轮,一行代碼拿下頁面數(shù)據(jù)
'''爬取頁面,獲得詳細數(shù)據(jù)'''
response = requests.get(url=movie_detail_href, headers=self.headers)
創(chuàng)建一個有序字典橄霉,保存當前影片數(shù)據(jù)
# 生成一個有序字典窃爷,保存影片結果
movie_info = OrderedDict()
我們再來看一下這個頁面的的源碼是什么樣的,首先是影片排序和影片名稱姓蜂,我們可以從上個頁面?zhèn)鬟f過來按厘。但是,既然它這里有钱慢,我直接解析行不行逮京?
必須行啊束莫!
這個更簡單造虏,影片排名直接定位一個 class='top250-no' 的 span 標簽
,影片名稱定位一個 property='v:itemreviewed' 的 span 標簽
麦箍,獲取標簽內(nèi)容即可
# 解析電影排名和名稱
movie_info['movie_rank'] = soup.find_all('span', class_='top250-no')[0].string
movie_info['movie_name'] = soup.find_all('span', property='v:itemreviewed')[0].string
接下來是影片主要數(shù)據(jù):
這個時候我們需要先定位到 id='info' 的 div
中,然后可以看到整個 div 的數(shù)據(jù)
就是我們需要的主要數(shù)據(jù)陶珠。
# 定位到影片數(shù)據(jù)的 div
soup_div = soup.find(id='info')
“不對啊挟裂,小一哥,我發(fā)現(xiàn)編劇有時候是一個揍诽,有時候是多個诀蓉。多個的時候存在在多個 span 標簽中栗竖,這個怎么辦啊渠啤?”
“這個簡單狐肢,我寫一個小函數(shù),統(tǒng)一處理一下沥曹》菝“
def get_mul_tag_info(self, soup_span):
"""
獲取多個標簽的結果合并在一個結果中返回,并用 / 分割
"""
info = ''
for second_span in soup_span:
# 區(qū)分 href 和標簽內(nèi)容
info = ('' if (info == '') else '/').join((info, second_span.string))
return info
“對了妓美,你記得把最外層的 span 標簽給我就行僵腺。像這種:”
# 解析電影發(fā)布信息
movie_info['movie_director'] = self.get_mul_tag_info(soup_div.find_all('span')[0].find_all('a'))
movie_info['movie_writer'] = self.get_mul_tag_info(soup_div.find_all('span')[3].find_all('a'))
movie_info['movie_starring'] = self.get_mul_tag_info(soup_div.find_all('span')[6].find_all('a'))
movie_info['movie_type'] = self.get_mul_tag_info(soup_div.find_all('span', property='v:genre'))
movie_info['movie_country'] = soup_div.find(text='制片國家/地區(qū):').next_element.lstrip().rstrip()
movie_info['movie_language'] = soup_div.find(text='語言:').next_element.lstrip().rstrip()
movie_info['movie_release_date'] = self.get_mul_tag_info(soup_div.find_all('span', property='v:initialReleaseDate'))
movie_info['movie_run_time'] = self.get_mul_tag_info(soup_div.find_all('span', property='v:runtime'))
movie_info['movie_imdb_href'] = soup_div.find('a', target='_blank')['href']
“小一哥,又出問題了壶栋,有的影片沒有又名
標簽辰如,這個怎么處理呢?”
“這個我們做個異常檢測贵试,沒有的手動賦空值就行了琉兜。”
movie_second_name = ''
try:
movie_second_name = soup_div.find(text='又名:').next_element.lstrip().rstrip()
except AttributeError:
print('{0} 沒有又名'.format(movie_info['movie_name']))
movie_info['movie_second_name'] = movie_second_name
最后還剩下評分數(shù)據(jù)
評分數(shù)據(jù)不但有總評分毙玻,還有每個星級的評分豌蟋。
“小一哥,你說我們?nèi)∧膫€數(shù)據(jù)跋骸夺饲?”
“小孩才做選擇,我當然是全部都要施符!”
可以看到往声,總評分和總評論人數(shù)分別有一個唯一的 property
,分別是property='v:average' 的 strong 標簽
和 property='v:votes' 的 span 標簽
ok戳吝,接下來直接拿數(shù)據(jù):
# 獲取總評分和總評價人數(shù)
movie_info['movie_rating'] = soup.find_all('strong', property='v:average')[0].string
movie_info['movie_comments_user'] = soup.find_all('span', property='v:votes')[0].string
最后就剩下每個星級的評分占比
浩销,可以看到 5星/4星/3星/2星/1星
分別對應 力薦/推薦/還行/較差/很差
,可以看到他們都存在在一個class='ratings-on-weight' 的 div
中
所以听哭,先定位 div :
# 定位到影片星級評分占比的 div
soup_div = soup.find('div', class_="ratings-on-weight")
然后獲取每個星級評分占比數(shù)據(jù):
# 獲取每個星級的評分
movie_info['movie_five_star_ratio'] = soup_div.find_all('div')[0].find(class_='rating_per').string
movie_info['movie_four_star_ratio'] = soup_div.find_all('div')[2].find(class_='rating_per').string
movie_info['movie_three_star_ratio'] = soup_div.find_all('div')[4].find(class_='rating_per').string
movie_info['movie_two_star_ratio'] = soup_div.find_all('div')[6].find(class_='rating_per').string
movie_info['movie_one_star_ratio'] = soup_div.find_all('div')[8].find(class_='rating_per').string
打印一下看一下我們當前的影片
數(shù)據(jù):
對 movie_starring 字段只輸出部分顯示
OrderedDict(
[
('movie_rank', 'No.1'),
('movie_name', '肖申克的救贖 The Shawshank Redemption'),
('movie_director', '弗蘭克·德拉邦特'),
('movie_writer', '弗蘭克·德拉邦特/斯蒂芬·金'),
('movie_starring', '蒂姆·羅賓斯/摩根·弗里曼/鮑勃·岡頓/威廉姆·賽德勒/),
('movie_type', '劇情/犯罪'),
('movie_country', '美國'),
('movie_language', '英語'),
('movie_release_date', '1994-09-10(多倫多電影節(jié))/1994-10-14(美國)'),
('movie_run_time', '142分鐘'),
('movie_imdb_href', 'https://www.imdb.com/title/tt0111161'),
('movie_rating', '9.7'),
('movie_comments_user', '1720706'),
('movie_five_star_ratio', '84.8%'),
('movie_four_star_ratio', '13.6%'),
('movie_three_star_ratio', '1.4%'),
('movie_two_star_ratio', '0.1%'),
('movie_one_star_ratio', '0.1%'),
('movie_note', '')
]
)
搞定慢洋,成功拿到了想要的數(shù)據(jù),最后一步:保存數(shù)據(jù)庫
# 保存當前影片信息
self.data_info.append(movie_info)
# 獲取數(shù)據(jù)并保存成 DataFrame
df_data = pd.DataFrame(self.data_info)
# 導入數(shù)據(jù)到 mysql 中
df_data.to_sql('t_douban_movie_top_250', self.pymysql_engine, index=False, if_exists='append')
看一眼我們的數(shù)據(jù)庫陆盘,該有的數(shù)據(jù)都存進去了
到這里普筹,爬蟲就算是結束了。
總結一下:
準備工作:
- 首先我們定義了一個影片對象隘马,傳入了網(wǎng)址的參數(shù)信息太防,設置了爬蟲頭部,并建立了數(shù)據(jù)庫連接
- 我們通過下一頁分析出每個影片頁的超鏈接酸员,發(fā)現(xiàn)只是改變了參數(shù)
- 建立了主流程蜒车,并寫出了主流程的偽代碼
開始爬蟲:
- 爬取
第一頁
的網(wǎng)頁內(nèi)容 - 解析
第一頁
的內(nèi)容讳嘱,獲取每頁中25個影片的詳細超鏈接 - 爬取
詳細影片
的網(wǎng)頁內(nèi)容 - 解析
第二頁
的內(nèi)容,保存到每個影片對象中 -
保存數(shù)據(jù)
到數(shù)據(jù)庫中
思考:
以上就是我們今天爬蟲實戰(zhàn)的主要內(nèi)容酿愧,相對來說比較簡單沥潭。
第一個項目,旨在讓大家了解爬蟲流程
嬉挡,同時钝鸽,也可以思考一下以下幾點:
- 影片詳細頁面的短評論數(shù)據(jù)
- 影片詳細頁面的獲獎情況數(shù)據(jù)
- 影片詳細頁面的討論區(qū)數(shù)據(jù)
以上數(shù)據(jù)的獲取是否可以用今天的獲取方法
?如果不行棘伴,那應該通過什么方式獲取這些數(shù)據(jù)寞埠?
寫在后面的話
今天的實戰(zhàn)項目就結束了,需要源代碼的同學可以在公眾號后臺
回復 豆瓣電影
獲取焊夸,如果覺得小一哥講的還不錯的話仁连,不妨點個贊
?
開篇已經(jīng)提到阱穗,我們的目的不是爬數(shù)據(jù)饭冬。所以,我會利用這些數(shù)據(jù)做一個簡單數(shù)據(jù)分析揪阶,目的很簡單:了解數(shù)據(jù)分析的流程昌抠。下期見。
碎碎念一下
我發(fā)現(xiàn)寫技術文比寫軟文難了不止一個檔次鲁僚,雖然軟文沒啥技術含量炊苫,但是大家愛看啊。
技術性文章苦澀難懂冰沙,不過像我講這么詳細的侨艾,你確定不點個贊支持一下?
原創(chuàng)不易拓挥,歡迎點贊噢
文章首發(fā):公眾號【知秋小一】
文章同步:掘金唠梨,簡書