1. 程式人生 > >基於Scrapy的B站爬蟲

基於Scrapy的B站爬蟲

# **基於Scrapy的B站爬蟲** 最近又被叫去做爬蟲了,不得不拾起兩年前搞的東西。 說起來那時也是突發奇想,想到做一個B站的爬蟲,然後用的都是最基本的Python的各種庫。 不過確實,實現起來還是有點麻煩的,單純一個下載,就有很多麻煩事。 這回要快速實現一個爬蟲,於是想到基於現成的框架來開發。 Scrapy是以前就常聽說的一個爬蟲框架,另一個是PySpider。 不過以前都沒有好好學過框架。 這回學習了一波,順便擼出來一個小Demo。 這個Demo功能不多,只能爬取B站的視訊列表,不過主要在於學習、記錄、交流,不在於真的要爬B站。。 然後程式碼都在GitHub了: https://github.com/wangzb96/Scrapy-Bilibili --- ## **爬蟲的定義** 爬蟲的定義有以下兩點: - 自動爬取網路資源 (html、json、...) - 模擬瀏覽器行為 第一點是常規的定義,第二點是進階版的定義,因為如果爬蟲要持久穩定地爬取資料,那麼就要模擬真人使用瀏覽器的行為,模擬得越像越好,越不容易被封。 --- ## **爬蟲的流程** - 頁面分析 - 工具 - 谷歌瀏覽器 - 360極速瀏覽器 - 問題 - 哪些資料需要爬取? - 這些資料存放在什麼檔案上? - 這些檔案的連結是什麼? - 連結的生成規則是什麼? - 存放在其他頁面檔案 - 通過某種簡單的規則生成 (如遞增的數字) - 獲取連結 - 通過解析網頁檔案得到連結 - 通過模版生成不同的連結 - 下載資源 - `requests` - `asyncio` - 頁面解析 - `json` - `bs4.BeautifulSoup` - `pyquery.PyQuery` - `re` - 資料儲存 - 檔案 - 資料庫 --- ## **Scrapy框架介紹** Scrapy是一個用於實現爬蟲的Python框架,它將爬蟲執行過程抽象成幾個元件,如圖: 其中主要包括: - Engine (不需要使用者實現) - 驅動元件執行 - Scheduler (不需要使用者實現) - 接收請求 - 排程請求 - 返回請求 - Downloader (不需要使用者實現) - 請求網路資源 - 返回響應 - Spider (需要使用者實現) - 返回初始請求 - 頁面解析 - 返回Item物件 - 返回新請求 - Item Pipeline (需要使用者實現) - Item物件清洗 - Item物件驗證 - Item物件儲存 - Middleware (需要使用者實現) - Downloader Middleware - Spider Middleware - 在元件執行的一些子過程中執行額外操作 當應用Scrapy實現爬蟲時,由於Scrapy已經實現了Engine、Scheduler、Downloader等元件,所以使用者無需實現這些元件,使用者主要要實現Spider,以及按需實現Item Pipeline、Middleware,另外還需要實現Item類。 --- ## **基於Scrapy的B站爬蟲實現** 以下介紹一個B站美食區視訊列表爬蟲實現的案例。 --- ### **開始一個Scrapy專案** 首先在命令列或終端中輸入: scrapy startproject scrapy_bilibili Scrapy會在當前目錄下生成如下的目錄: - ***scrapy_bilibili*** - *scrapy_bilibili* - *spiders* - \_\_init\_\_.py - \_\_init\_\_.py - items.py - pipelines.py - middlewares.py - settings.py - scrapy.cfg 其中斜體的是資料夾,我們把加粗的資料夾設定成專案的根目錄。 --- ### **B站美食區視訊列表頁面分析** B站美食區的連結地址是固定的: https://www.bilibili.com/v/life/food/?#/all/default/0 進去後裡面有個視訊列表,我們使用360極速瀏覽器分析:
分析後,發現一個“newlist”連結: https://api.bilibili.com/x/web-interface/newlist?rid=76&type=0&ps=100&pn=1 點開後,可以看到這個連結返回了一個json檔案,裡面記錄了視訊列表及其中每一個視訊的資訊,包括視訊的標題、id、播放量等: 分析一下這個連結的引數,`rid`是美食區的id,`type`是按日期排序還是按熱度排序,`ps`表示每頁視訊數量,`pn`表示第幾頁。 然後觀察B站的視訊頁面: 發現視訊頁面的連結地址是由固定模版生成的: https://www.bilibili.com/video/{bvid} 其中bvid是每個視訊的id,可以通過“newlist”連結獲得。 如果要爬取視訊頁面資訊,那麼應用以上方法分析一下就可以了。 --- ### **B站視訊列表Item類實現** Scrapy的Item類,在概念上相當於C/C++的結構體、Java的POJO。 這裡簡單起見,我們將視訊列表json檔案中每個元素感興趣的資訊均存放在一個Item物件中,程式碼如下:
點選檢視詳情
```Python from scrapy import Item, Field class BilibiliVideoListItem(Item): # 視訊資訊 aid = Field() # 視訊ID bvid = Field() # 視訊ID tid = Field() # 區 pic = Field() # 封面 title = Field() # 標題 desc = Field() # 簡介 duration = Field() # 總時長,所有分P時長總和 videos = Field() # 分P數 pubdate = Field() # 釋出時間 view = Field() # 播放數 danmaku = Field() # 彈幕數 reply = Field() # 評論數 like = Field() # 點贊數 dislike = Field() # 點踩數 coin = Field() # 投幣數 favorite = Field() # 收藏數 share = Field() # 分享數 cid = Field() # 未知 # UP主資訊 mid = Field() # UP主ID name = Field() # 暱稱 face = Field() # 頭像 ```
--- ### **B站Spider類實現** Spider類是實現爬蟲的關鍵。 首先返回初始連結,這裡我們直接返回“newlist”第一頁的連結;\ 然後實現頁面解析邏輯,由於返回的頁面是json檔案,我們直接將它轉成Python物件,之後依次取出感興趣的屬性,最後封裝成Item物件就可以了;\ 再之後要返回新的請求物件,這裡直接返回下一頁連結,並且判斷是否已將所有視訊都爬取了。
點選檢視詳情 ```Python from scrapy import Spider, Request from scrapy_bilibili.items import BilibiliVideoListItem from util import json2obj class BilibiliSpider(Spider): # Spider名字 name = 'BilibiliSpider' # 視訊列表連結模版 (三個引數) url_fmt = r'https://api.bilibili.com/x/web-interface/newlist?' \ r'rid={rid}&type=0&ps={ps}&pn={pn}' def __init__(self, *args, rid: int=None, ps: int=None, **kwargs): """初始化 Args: rid: 區ID,預設76,表示美食區 ps: 視訊列表每頁視訊數量,預設100 """ super().__init__(*args, **kwargs) if rid is None: rid = 76 if ps is None: ps = 100 self.rid = rid self.ps = ps # 視訊列表連結模版 (一個引數) self.url = self.url_fmt.format(rid=rid, ps=ps, pn='{}') # 初始連結 self.start_urls = [self.url.format(1)] def parse(self, response): """頁面解析""" url = response.url pn = int(url.rsplit('=', 1)[-1]) # 視訊列表頁碼 page = response.body.decode('UTF-8') # 響應物件中的json檔案 obj = json2obj(page) # 轉成Python物件 data = obj['data'] count = data['page']['count'] # 該區當前視訊總數 archives = data['archives'] for i in archives: aid = i['aid'] bvid = i['bvid'].strip() tid = i['tid'] pic = i['pic'].strip() title = i['title'].strip() desc = i['desc'].strip() duration = i['duration'] videos = i['videos'] pubdate = i['pubdate'] stat = i['stat'] view = stat['view'] danmaku = stat['danmaku'] reply = stat['reply'] like = stat['like'] dislike = stat['dislike'] coin = stat['coin'] favorite = stat['favorite'] share = stat['share'] cid = i['cid'] owner = i['owner'] mid = owner['mid'] name = owner['name'].strip() face = owner['face'].strip() # 封裝成Item物件 item = BilibiliVideoListItem( aid=aid, bvid=bvid, tid=tid, pic=pic, title=title, desc=desc, duration=duration, videos=videos, pubdate=pubdate, view=view, danmaku=danmaku, reply=reply, like=like, dislike=dislike, coin=coin, favorite=favorite, share=share, cid=cid, mid=mid, name=name, face=face, ) yield item if pn*self.ps