小白學 Python 爬蟲(24):2019 豆瓣電影排行
人生苦短,我用 Python
前文傳送門:
小白學 Python 爬蟲(1):開篇
小白學 Python 爬蟲(2):前置準備(一)基本類庫的安裝
小白學 Python 爬蟲(3):前置準備(二)Linux基礎入門
小白學 Python 爬蟲(4):前置準備(三)Docker基礎入門
小白學 Python 爬蟲(5):前置準備(四)資料庫基礎
小白學 Python 爬蟲(6):前置準備(五)爬蟲框架的安裝
小白學 Python 爬蟲(7):HTTP 基礎
小白學 Python 爬蟲(8):網頁基礎
小白學 Python 爬蟲(9):爬蟲基礎
小白學 Python 爬蟲(10):Session 和 Cookies
小白學 Python 爬蟲(11):urllib 基礎使用(一)
小白學 Python 爬蟲(12):urllib 基礎使用(二)
小白學 Python 爬蟲(13):urllib 基礎使用(三)
小白學 Python 爬蟲(14):urllib 基礎使用(四)
小白學 Python 爬蟲(15):urllib 基礎使用(五)
小白學 Python 爬蟲(16):urllib 實戰之爬取妹子圖
小白學 Python 爬蟲(17):Requests 基礎使用
小白學 Python 爬蟲(18):Requests 進階操作
小白學 Python 爬蟲(19):Xpath 基操
小白學 Python 爬蟲(20):Xpath 進階
小白學 Python 爬蟲(21):解析庫 Beautiful Soup(上)
小白學 Python 爬蟲(22):解析庫 Beautiful Soup(下)
小白學 Python 爬蟲(23):解析庫 pyquery 入門
引言
從本篇的標題各位同學應該已經猜到了,本篇又到了實戰環節~~~
2019 已經快過完了,按照本文推送的時間預估,到 2020 應該還有十來天的時間,又到了各個公司出各種 2019 榜單的時間,小編這裡呢,就先幫豆瓣搞一個 2019 電影評分排行榜,希望豆瓣官方看到不要打我。
鄭重宣告: 本文僅限用作學習等目的。
分析
還是先看一下我們要爬取的頁面:
連結:
https://movie.douban.com/explore#!type=movie&tag=%E7%83%AD%E9%97%A8&sort=time&page_limit=20&page_start=0
思維敏捷的同學看著上面這個連結可能就已經發現了什麼,對的,這個連結上已經有分頁資訊了。
page_limit
應該是一頁的元素, page_start
應該是這一頁開始的一個序號。
我們往下翻一下頁面,看看下面有沒有下一頁之類的按鈕,翻幾頁看下位址列的變化是否和我們推測的一致。
emmmmmmmmm
小編猜錯了,這裡不是下一頁,是載入更多,不過問題不大,一個意思,先點一下我們看下位址列:
https://movie.douban.com/explore#!type=movie&tag=%E7%83%AD%E9%97%A8&sort=time&page_limit=20&page_start=20
和前面一個地址作對比可以發現,只有最後的 page_start
引數有變化,說明我們剛才上面的猜測沒有問題。
載入更多多點幾次,可以發現,這裡的電影是可以一直往後排的,可以載入到 2018 年的資料:
emmmmmmmmm,有點尷尬,這個資料竟然手動翻出來了,理論上是應該程式自己判斷的。
這裡的懸浮層上已經顯示了我們想要的資料,接下來的問題是,我們如何獲得這個懸浮層上的資料,直接從 DOM 節點來取可以麼?
顯然是不行的,不信可以自己動手試試,每個電影的懸浮層其實都是同一個 DOM 節點,只是裡面填充的資料不同,顯然這個 DOM 節點中的資料是滑鼠挪上去的時候才動態加載出來的。
那麼我們從哪裡能看到載入資料的來源呢?
如果上一篇實戰有仔細看實操過的同學應該已經想到了, Chrome 瀏覽器開發者模式中的 Network 標籤。
沒錯,就是這裡,我們看一下:
首先選擇 Network 標籤,然後在下面的標籤上選擇 XHR 。然後滑鼠在不同的電影上移動,可以看到滑鼠每次移到一張圖片上,就會有一個請求,我們看一下這個請求的響應資訊:
{"r":0,"subject":{"episodes_count":"","star":"40","blacklisted":"available","title":"我身體裡的那個傢伙 내안의 그놈 (2019)","url":"https:\/\/movie.douban.com\/subject\/27088750\/","collection_status":"","rate":"7.2","short_comment":{"content":"男主真的他媽帥 但是我真的接受不了和羅美蘭打k ","author":"SOUL"},"is_tv":false,"subtype":"Movie","directors":["姜孝鎮"],"actors":["鄭振永","樸聖雄","羅美蘭","李垂珉","李俊赫","金光奎","閔智雅","尹敬浩","金賢穆","樸慶惠","趙賢榮","尹頌雅","智燦","金凡振 ","鄭元昌","孫光業","黃仁俊","Dae-han Kim"],"duration":"122分鐘","region":"韓國","playable":false,"id":"27088750","types":["劇情","喜劇"],"release_year":"2019"}}
那麼我們剩下要關心的就是這個請求的地址了,先看下這個請求的地址:
https://movie.douban.com/j/subject_abstract?subject_id=27088750
這裡看起來好像只有最後一個 subject_id
引數是變化的,其他的都是定死的,多看幾個請求檢驗下我們的推測,小編這裡就不檢驗了,免得嫌棄說小編水內容。
還有一個問題,最後這個 subject_id
的資料從哪裡來,好像沒見過的,小編憑藉自己多年豐富的開發經驗,猜測這個資料應該是在頁面的上的。我們接著看下這個電影的頁面 DOM 結構。
看到了沒,這裡的資料是來源於 DOM 結構上的 data-id
屬性。
PS:更新一件事情,一件異常尷尬的事情,小編偶然發現,點選載入更多的時候,實際上是對應了一個 API 介面,這個介面的訪問地址如下:
https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&sort=time&page_limit=20&page_start=20
這個在 NetWork 中有看到,如下圖:
從圖中可以看到,這裡直接返回了 JSON 資料,並且這個 JSON 資料返回後,順便還修改了位址列的資料。
這裡得到的資料如下:
{
"subjects":[
{
"rate":"6.7",
"cover_x":1382,
"title":"在無愛之森吶喊",
"url":"https://movie.douban.com/subject/30337760/",
"playable":false,
"cover":"https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2571542101.jpg",
"id":"30337760",
"cover_y":2048,
"is_new":false
}
]
}
因為整體資料有 20 條,太長了放不下,小編這裡僅保留了一條資料。
編碼
有了上面的分析,其實寫程式碼就已經很簡單了,我們所有需要用到的資料都可以直接從 API 介面中直接獲取到 JSON 的資料。
屢一下思路,首先我們從
https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&sort=time&page_limit=20&page_start=0
這個連結中直接獲取電影的相關資料,這裡對我們有用的資料是 id
,獲取到這個 id
後,再從
https://movie.douban.com/j/subject_abstract?subject_id=27088750
這個連結中獲取到電影的詳情資料,用上面得到的 id
替換這裡的 subject_id
。
好像沒頁面 DOM 解析啥事兒了,哎,真的是一次失敗的選題,下次再也不選豆瓣了。
程式碼內容有些簡單,小編直接貼出來吧,資料還是在 Mysql 中開了一張表做存放:
import requests
import pymysql
# 資料庫連線
def connect():
conn = pymysql.connect(host='localhost',
port=3306,
user='root',
password='password',
database='test',
charset='utf8mb4')
# 獲取操作遊標
cursor = conn.cursor()
return {"conn": conn, "cursor": cursor}
connection = connect()
conn, cursor = connection['conn'], connection['cursor']
sql_insert = "insert into douban2019(id, title, rate, short_comment, duration, subtype, region, release_year, create_date) values (%(id)s, %(title)s, %(rate)s, %(short_comment)s, %(duration)s, %(subtype)s, %(region)s, %(release_year)s, now())"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'
}
flag = True
def get_movie_list(page_start):
r = requests.get('https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&sort=time&page_limit=20&page_start=' + str(page_start), headers = headers)
for item in r.json()['subjects']:
get_movie_info(item['id'])
def get_movie_info(subject_id):
r = requests.get('https://movie.douban.com/j/subject_abstract?subject_id=' + str(subject_id), headers=headers)
subject = r.json()['subject']
if subject['release_year'] != '2019':
global flag
flag = False
return
print(subject)
insert_data = {
"id": subject['id'],
"title": subject['title'],
"rate": subject['rate'],
"short_comment": subject['short_comment']['content'],
"duration": subject['duration'],
"subtype": subject['subtype'],
"region": subject['region'],
"release_year": subject['release_year']
}
cursor.execute(sql_insert, insert_data)
conn.commit()
print(subject['title'], '寫入完成')
def main():
num = 0
while(flag):
get_movie_list(num)
num += 20
if __name__ == '__main__':
main()
小結
最後小編做了一下簡單的統計,截止目前, 2019 豆瓣電影共計 312 部,評分超過 8.0 分的共計 37 部,超過 8.5 分的共計 14 部,超過 9.0 分的只有 1 部。
下表為評分超過 8.0 分的,還有沒看過的小夥伴可以抓緊時間看一下咯~~
名稱 | 評分 |
---|---|
愛爾蘭人 The Irishman (2019) | 9.1 |
銀河英雄傳說 Die Neue These 星亂 第1章 銀河英雄伝説 Die Neue These 星亂 第1章 (2019) | 8.9 |
小丑 Joker (2019) | 8.8 |
婚姻故事 Marriage Story (2019) | 8.8 |
玩具總動員4 Toy Story 4 (2019) | 8.7 |
寄生蟲 기생충 (2019) | 8.7 |
代號基亞斯:復活的魯路修 コードギアス 復活のルルーシュ (2019) | 8.7 |
82年生的金智英 82년생 김지영 (2019) | 8.7 |
克勞斯:聖誕節的祕密 Klaus (2019) | 8.6 |
痛苦與榮耀 Dolor y gloria (2019) | 8.6 |
青春期豬頭少年不做懷夢少女的夢 青春ブタ野郎はゆめみる少女の夢を見ない (2019) | 8.6 |
復仇者聯盟4:終局之戰 Avengers: Endgame (2019) | 8.5 |
哪吒之魔童降世 (2019) | 8.5 |
普羅米亞 プロメア (2019) | 8.5 |
少年的你 (2019) | 8.4 |
少年泰坦出擊大戰少年泰坦 Teen Titans Go! vs Teen Titans (2019) | 8.4 |
悲慘世界 Les misérables (2019) | 8.4 |
我的一級兄弟 나의 특별한 형제 (2019) | 8.3 |
燃燒女子的肖像 Portrait de la jeune fille en feu (2019) | 8.3 |
再見鍾情 Mon inconnue (2019) | 8.3 |
我在雨中等你 The Art of Racing in the Rain (2019) | 8.2 |
羅小黑戰記 (2019) | 8.2 |
行騙天下JP:浪漫篇 コンフィデンスマンJP (2019) | 8.2 |
阿鬆 劇場版 劇場版 えいがのおそ鬆さん (2019) | 8.2 |
蠟筆小新:新婚旅行颶風之遺失的野原廣志 映畫クレヨンしんちゃん 新婚旅行ハリケーン ~失われたひろし~ (2019) | 8.2 |
馭風男孩 The Boy Who Harnessed the Wind (2019) | 8.1 |
對不起,我們錯過了你 Sorry We Missed You (2019) | 8.1 |
續命之徒:絕命毒師電影 El Camino: A Breaking Bad Movie (2019) | 8.1 |
我失去了身體 J'ai perdu mon corps (2019) | 8.1 |
最初的夢想 Chhichhore (2019) | 8.1 |
千子2 センコロール コネクト (2019) | 8.0 |
地久天長 (2019) | 8.0 |
金髮男子 Un rubio (2019) | 8.0 |
心理測量者SS2:第一衛士 PSYCHO-PASS サイコパス Sinners of the System Case.2「First Guardian」 (2019) | 8.0 |
漫長的告別 長いお別れ (2019) | 8.0 |
我的喜馬拉雅 (2019) | 8.0 |
小委託人 어린 의뢰인 (2019) | 8.0 |
示例程式碼
本系列的所有程式碼小編都會放在程式碼管理倉庫 Github 和 Gitee 上,方便大家取用。
示例程式碼-Github
示例程式碼-Gi