python爬取豆瓣首頁熱門欄目詳細流程
- 2019 年 10 月 3 日
- 筆記
記錄一下爬取豆瓣熱門專欄的經過,通過這篇文章,你能學會requests,HTMLParser,json的基本使用,以及爬取網頁內容的基本思路。
使用模組
1,獲取豆瓣首頁程式碼:首先我們需要訪問豆瓣頁面,獲取首頁的源碼。這裡推薦使用第三方庫:requests,相比python內置的 urllib 模組,requests使用起來更簡單,功能更全面
2,對獲取的程式碼進行解析:對於解析html程式碼,已經有很多功能強大的框架能使用,如Scrapy,PySpider,Beautiful Soup等,這裡我們只是學習下爬蟲的基本使用,所以內建的 HTMLParser 足夠使用了
3,對獲取的數據進行處理: json
思路分析
既然我們需要的只是熱門專欄模組的數據,那麼我們需要一個標誌來告訴我們:下面的程式碼就是專欄模組了,準備獲取數據。同樣我們需要知道當前
讀取的是圖片、標題還是欄目類別,以此將數據儲存到相應的欄位中。總的來說,我們最起碼應該通過程式碼來實現以下幾點:
1,獲取網頁源碼
2,通過自定義方法解析html
3,通過標誌位判斷當前數據是否是我們需要的數據
4,通過分析程式碼結構決定將要儲存的數據結構
5,將數據按照特定格式進行本地儲存
豆瓣官網:https://www.douban.com/,分析一下我們需要爬取模組的程式碼:
可以看到,我們需要爬取的數據都在 ul.time-list 這個程式碼塊里,那麼我們的標誌位就是:當開始標籤為 ul並且具有類名 time-list時,我們就要獲取數據了,當結束標籤為 ul 時,停止解析,繼續分析程式碼結構,每個 li 裡面包含了對應數據裡面的 詳情頁跳轉鏈接,圖片地址,標題以及專欄類別,那麼我們的數據結構到這裡也就很清楚了:一個 li 塊對應一條數據,每條數據由四個欄位組成:
詳情頁跳轉鏈接 href –> 這裡我們考慮了一下, 還是通過第二個a標籤來獲取,它具有統一的類名title,同時我們還能獲取 標題title,
圖片地址 imgUrl –> 通過每個li程式碼塊裡面唯一img標籤的src屬性可以輕鬆獲取,
標題 title –> 通過 a.title獲取,
專欄類別 type –> 唯一的 span 標籤獲取
tip:像上面我們選取數據的標誌位一樣,img的alt可以獲取標題,a標籤的文本也可以獲取標題,兩個a標籤都能獲取跳轉鏈接不管是爬蟲還是平時其他的開發,我們經常會遇到,同一個需求有多種方法實現,這時候我們就需要思考一下哪一種方法更簡潔,冷靜分析後的編碼不一定最優秀,但自己肯定印象深刻(說遠了,回歸正題)。
編碼實現
通過上面的準備工作,我們已經確定了需要引入的模組,解析事件觸發標誌位,需要獲取的數據,儲存的數據結構,可以正式開始編碼了:
requests是第三方庫,需要另外安裝,其他的是內置模組,直接引入即可:
1 import requests 2 from html.parser import HTMLParser 3 from html.entities import name2codepoint 4 import json
獲取豆瓣首頁源碼:
1 r = requests.get('https://www.douban.com/', timeout = 3)
是的,通過 requests獲取網頁只需要一行程式碼,timeout為獲取頁面超時時間,通過 r.text 就是我們需要的html源碼,r.encoding可以獲取網頁編碼格式,當然requests還有其他的方法供我們使用,
如 帶參數的url: r = requests.get(url, params={…..}),獲取數據等
解析豆瓣首頁源碼:
HTMLParser 里已經封裝好了針對html的各種事件處理,如 開始標籤,結束標籤,標籤屬性,標籤文本,注釋,特殊字元,不了解的可以看下這個:
https://www.liaoxuefeng.com/wiki/1016959663602400/1017784593019776,很簡單很清晰
1 class MyHTMLParser(HTMLParser): 2 def __init__(self): 3 super().__init__() 4 # 是否開始解析 5 self._allowRun = False 6 7 # 創建dist備用:儲存數據 8 self.hotList = {'data': []} 9 10 # 每一個 li 塊數據儲存 11 self.listItem = {} 12 13 # 當前解析標籤類型的標誌位 14 self.tagType = '' 15 16 # 開始標籤及 標籤屬性 17 def handle_starttag(self, tag, attrs): 18 if tag == 'ul' and ('class', 'time-list') in attrs: 19 self._allowRun = True 20 21 # 若當前是開啟解析狀態 22 if self._allowRun: 23 if tag == 'a' and ('class', 'title') in attrs: 24 self.tagType = 'a' 25 for (key, value) in attrs: 26 if key == 'href': 27 self.listItem[key] = value 28 if tag == 'img': 29 for (key, value) in attrs: 30 if key == 'src': 31 self.listItem['imgUrl'] = value 32 33 if tag == 'span': 34 self.tagType = 'span' 35 36 # 結束標籤 37 def handle_endtag(self, tag): 38 self.tagType = '' 39 if tag == 'ul': 40 self._allowRun = False 41 42 if tag == 'li': 43 if len(self.listItem) != 0: 44 self.hotList['data'].append(self.listItem) 45 self.listItem = {} 46 47 # 空標籤及 標籤屬性 48 def handle_startendtag(self, tag, attrs): 49 if self._allowRun: 50 if tag == 'img': 51 for (key, value) in attrs: 52 if key == 'src': 53 self.listItem['imgUrl'] = value 54 55 # 標籤文本 56 def handle_data(self, data): 57 if self._allowRun: 58 if self.tagType == 'a': 59 self.listItem['title'] = data 60 self.taga = False 61 elif self.tagType == 'span': 62 self.listItem['type'] = data 63 64 # 注釋 65 def handle_comment(self, data): 66 pass 67 68 # HTML entity 字元 69 def handle_entityref(self, name): 70 pass 71 72 # Numeric 字元 73 def handle_charref(self, name): 74 pass 75 76 parser = MyHTMLParser()
77 parser.feed(r.text)
程式碼說明:我們必須知道在解析過程中,實例方法是按照源碼順序循環執行的,也就是說在同一個實例方法里,我們可以針對不同的標籤或其他條件來進行不同的操作。我們所有的解析操作都是針對 ul.time-list 程式碼塊的,所以我們需要一個開關,當前程式碼是 ul.time-list時才執行我們自定義的解析操作,這個開關就是上面程式碼里的 _allowRun,當開始標籤是 ul.time-list的是否為 True,當結束標籤是 ul 的是否為False,而只有當 _allowRun 為 True的時候,我們才繼續解析當前的標籤是 a 還是 img 或者 span。由於我們要在 文本解析事件 handle_data 中獲取 a 標籤的文本作為欄位 title 的值,span標籤的文本作為欄位 type 的值,所以我們需要一個標誌位變數來供我們在執行 handle_data 的時候判斷當前解析的文本是屬於 a 還是 span,這個標誌位變數就是上面程式碼中 tagType,在 handle_starttag 中賦值,在 handle_endtag 中清空。我們將每一條數據儲存在 listItem 中,當結束標籤為 li 時,說明我們的對一個 li 程式碼塊解析完畢,listItem 儲存了我們需要的單挑數據,將 listItem 添加到 hotList中並清空 listItem 。執行上面程式碼,我們已經將數據儲存在實例屬性 hotList裡面,我們可以在終端輸出 parser.hotList:
儲存數據
接下來就是將數據儲存到本地文件中,而寫入數據也是非常簡單:
1 with open('hotList.json', 'w') as f: 2 json.dump(parser.hotList, f)
在當前目錄里打開 hotList.json 文件,可以看到如下數據:
數據倒是寫入了,但是中文卻沒有如願顯示,而且對於追求美觀的我們來說也無法接受,所以我們需要指定寫入編碼格式,以及格式化:
1 with open('hotList.json', 'w', encoding="utf-8") as f: 2 json.dump(parser.hotList, f, ensure_ascii = False, indent = 4)
我們在寫入的時候指定編碼格式為 utf-8: encoding=”utf-8″,在 json.dump寫入數據時增加了兩個參數:ensure_ascii = False 禁止進行 ascii轉碼,indent = 4:按縮進為 4個單位格式化數據,當然我們還可以將欄位進行排序,只需要加上欄位:sort_keys = True,按需選擇即可,再打開 hotList.json 文件查看:
1 { 2 "data": [ 3 { 4 "imgUrl": "https://img1.doubanio.com/dae/niffler/niffler/images/1c6e77ec-c493-11e9-84c0-0242ac110008.jpg", 5 "href": "https://m.douban.com/time/column/164?dt_time_source=douban-web_anonymous", 6 "title": "傷別離與共春風——唐宋詞的情感世界", 7 "type": "音頻專欄" 8 }, 9 { 10 "imgUrl": "https://img1.doubanio.com/dae/niffler/niffler/images/511ccf86-b8fc-11e9-b188-0242ac110008.jpg", 11 "href": "https://m.douban.com/time/column/163?dt_time_source=douban-web_anonymous", 12 "title": "世界記憶大師教你快速提升記憶力", 13 "type": "影片專欄" 14 }, 15 { 16 "imgUrl": "https://img1.doubanio.com/dae/niffler/niffler/images/74897a9e-880c-11e9-bd82-0242ac11001b.jpg", 17 "href": "https://m.douban.com/time/column/159?dt_time_source=douban-web_anonymous", 18 "title": "黑白之間:二十八堂書法練習課", 19 "type": "影片專欄" 20 }, 21 { 22 "imgUrl": "https://img3.doubanio.com/dae/niffler/niffler/images/6f488990-a773-11e9-b587-0242ac110011.jpg", 23 "href": "https://m.douban.com/time/column/161?dt_time_source=douban-web_anonymous", 24 "title": "馬伯庸的冷門書單", 25 "type": "音頻專欄" 26 }, 27 { 28 "imgUrl": "https://img1.doubanio.com/dae/niffler/niffler/images/6c46cb9c-ac61-11e9-97e2-0242ac11000c.jpg", 29 "href": "https://m.douban.com/time/column/162?dt_time_source=douban-web_anonymous", 30 "title": "聽!解說式音樂會——古典音樂聆聽指南", 31 "type": "影片專欄" 32 }, 33 { 34 "imgUrl": "https://img3.doubanio.com/dae/niffler/niffler/images/ebd421cc-9968-11e9-ad2c-0242ac110006.jpg", 35 "href": "https://m.douban.com/time/column/158?dt_time_source=douban-web_anonymous", 36 "title": "從格里菲斯到諾蘭——影迷都在看的電影結構大師課", 37 "type": "影片專欄" 38 }, 39 { 40 "imgUrl": "https://img3.doubanio.com/dae/niffler/niffler/images/fa83f054-9633-11e9-a82e-0242ac110006.jpg", 41 "href": "https://m.douban.com/time/column/157?dt_time_source=douban-web_anonymous", 42 "title": "打開電影聲音的魔盒——好萊塢聲音設計大師課", 43 "type": "影片專欄" 44 }, 45 { 46 "imgUrl": "https://img3.doubanio.com/dae/niffler/niffler/images/81788c8e-8e53-11e9-b51e-0242ac110010.jpg", 47 "href": "https://m.douban.com/time/column/156?dt_time_source=douban-web_anonymous", 48 "title": "一劇之本——好萊塢編劇教父大師課", 49 "type": "影片專欄" 50 }, 51 { 52 "imgUrl": "https://img3.doubanio.com/dae/niffler/niffler/images/5d7d70aa-8b25-11e9-a08f-0242ac110012.jpg", 53 "href": "https://m.douban.com/time/column/155?dt_time_source=douban-web_anonymous", 54 "title": "老葉說電影——90分鐘看懂中國電影產業", 55 "type": "影片專欄" 56 }, 57 { 58 "imgUrl": "https://img3.doubanio.com/dae/niffler/niffler/images/e2e59078-828e-11e9-a465-0242ac110012.jpg", 59 "href": "https://m.douban.com/time/column/154?dt_time_source=douban-web_anonymous", 60 "title": "好萊塢特效大師課——從概念藝術到3D建模", 61 "type": "影片專欄" 62 } 63 ] 64 }
這樣就只有兩個字:噓服。
總結
這個例子只是用來熟悉爬蟲基本操作和思維邏輯,真正用到項目中還是得結合其他框架,如 Beautiful Soup,就可以獲取指定程式碼片段進行解析而不需要像我們上面那樣設置開關或標誌位。有興趣的朋友可以自己動手試試。
與諸君共勉。