聲明:本文內容和涉及到的代碼僅限於個人學習,任何人不得作為商業用途。轉載請附上此文章地址
本篇文章Python初學者之網絡爬蟲的繼續,最新代碼已提交到https://github.com/octans/PythonPractice
1. 上篇回顧
上篇文章Python初學者之網絡爬蟲中我從花椒的熱門推薦頁面入手,進而獲取到主播個人信息和對應的直播歷史視頻。
首先看一下上一篇文章中對huajiao.com的主播和視頻的爬取成果:
# getUserCount 10179 # getLiveCount 111574
到目前為止我新做了如下事情:
- 對MySql的讀寫操作進行了封裝
- 編碼風格遵從PEP8
- 爬取沃米優選網(http://video.51wom.com/)的主播信息
- 爬取一下網(http://www.yixia.com/)的主播信息和視頻信息
其中對MySql的封裝代碼單獨放到了文件mysql.py下,做為一個module使用,這個module雖然簡單,但已經實現了select,insert,delete等操作,對MySql封裝感興趣的同學可以參考, 但請不要用於生產環境。推薦去使用和閱讀數據庫類peewee。
接下來將繼續講述我在數據抓取上的開發經歷。
2. 爬取的數據源和邏輯
最終目標:收集到各大直播平台的主播信息和歷史播放記錄,進而對數據進行聚合分析。
當前已完成:對花椒網的數據收集。
沃米優選網(http://video.51wom.com/)是一個網紅數據聚合的網站,它收集了各個直播平台(花椒,熊貓,秒拍,斗魚,映客,一直播,美拍)的熱門主播信息。所以我希望能從它這里獲取到各個平台的熱門主播信息,之后拿着主播id去對應的直播平台去爬取更詳細的信息。
3. 爬取沃米優選網的主播列表頁
列表頁http://video.51wom.com/截圖如下:
初看這是一個列表頁,並且底部有分頁鏈接,點擊分頁時觸發表單提交
3.1 分析結論和構思程序邏輯
當點擊底部分頁時,使用chrom開發者工具,看到有XHR請求如下截圖:
從截圖和一些測試可以分析出:
- a) 要請求第二頁以后的數據,需要將相應的cookie和csrf數據提交給網站;
- b) 提交的方式是POST的”multipart/form-data”;
- c) 提交的參數有_csrf, stage-name, platform, industry等;
- d) 請求的返回結果是一個表格列表的html代碼;
對於cookie容易拿到,但_csrf如何獲取呢?
查看頁面源碼,發現網站在生成列表頁時已經將csrf的值寫入了表單;同一個csrf值在后續請求中可以多次使用
<input type="hidden" name="_csrf" value="aWF6ZGMzclc9EAwRK3Y4LhobNQo6eEAdWwA0IFd1ByUDNTgwClUEZw==">
由以上分析,程序的邏輯應該這樣,
- a) 先請求主播列表的首頁,獲取到csrf值和cookie
- b) 將csrf和cookie值保存,用於下次請求
- c) 請求主播列表的第二頁,第三頁等
- d) 將獲取到的表格列表的html代碼使用BeautifulSoup進行解析,遍歷每個行,行里的每個列
- e) 將獲取到的數據寫入mysql
3.2 python編碼獲取沃米優選網的主播信息
a) 構造基礎類class Website, 之后為每個網站建立一個class,繼承Website
- 有些請求返回的是html代碼,類里設置好html解析器;
- 有些請求返回的是json串,基類里設置好json的解析器;
- 請求每個網站時,需要設置不同的header,將header放在基類;
- 對post的Content-Type:multipart/form-data方式進行函數封裝;
- 對post的Content-Type: application/x-www-form-urlencoded方式分別進行函數封裝;
- 這里面盡量把各種不同的請求方式寫成函數,而不使用type參數的形式,便於子類清晰的調用;
注意以下代碼為了節省篇幅,不是完整代碼,也非PEP8代碼規范
class Website: ### 使用requests.session()能夠自動處理cookies session = requests.session() ### 設置html解析器 htmlParser = BeautifulSoup ### 設置json解析器 jsonParser = json ### 設置headers headers = { 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/' '54.0.2840.98 Safari/537.36' } ### 直接發起get請求 def get(self, url, params=None): if params is None: params = {} return self.session.get(url, params=params, headers=self.headers) ### 發起get請求並返回解析后的html對象 def get_html(self, url, params=None): r = self.get(url, params) return self.htmlParser(r.text, 'html.parser') ### 發起get請求並返回解析后的json對象 def get_json(self, url, params=None): r = self.get(url, params) return self.jsonParser.loads(r.text) ### 發起post請求,以Content-Type:multipart/form-data方式 def post_multi_part(self, url, params): kwargs = dict() for (k, v) in params.items(): kwargs.setdefault(k, (None, v)) r = self.session.post(url, files=kwargs, headers=self.headers) return self.htmlParser(r.text, "html.parser")
b) 構造class WoMiYouXuan, 封裝對網站沃米優選的請求
- 方法first_kiss()用於第一次請求網站,獲取到csrf值由屬性self.csrf保存;
- first_kiss()另一個作用是獲取到cookie,雖然沒有顯示處理,因為requests.session()幫我們處理了,自動獲取自動提交;
- 注意在一個實例里,只需調用一次first_kiss()即可,之后就可以多次調用其他的頁面請求函數了;
- csrf和cookie是由關聯的,網站會校驗,都要提交;
- 方法parse_actor_list_page()是具體分析主播的列表html代碼,這是一個細致活;
- 方法spider_actors是骨架函數,循環訪問每個分頁數據並將結果寫入mysql;
class WoMiYouXuan(Website): ### 發起post請求時需要將csrf發給網站 csrf = '' def __init__(self): self.first_kiss() ### 首次訪問該網站獲取到csrf值並保存到self.csrf, 供其他post請求直接使用 def first_kiss(self): url = 'http://video.51wom.com/' html = self.get_html(url) self.csrf = html.find('meta', {'name': 'csrf-token'}).attrs['content'] ### 從主播列表頁獲取主播信息 def parse_actor_list_page(self, page=1): ### 構造參數->發起post請求 url = 'http://video.51wom.com/media/' + str(page) + '.html' keys = ('_csrf', 'stage-name', 'platform', ' industry', 'price', 'follower_num', 'follower_area', 'page', 'is_video_platform', 'sort_by_price', 'type_by_price') params = dict() for key in keys: params.setdefault(key, '') params['_csrf'] = self.csrf params['page'] = str(page) html = self.post_multi_part(url, params) ### 解析主播列表 trs = html.find('div', {'id': 'table-list'}).table.findAll('tr') trs.pop(0) # 去除標題行 actor_list = list() for tr in trs: ### 后面太多了,有興趣的同學去看源碼吧 ### 骨架函數,循環訪問每個分頁數據並將結果寫入mysql def spider_actors(self): page = 1 tbl_actor = WMYXActor() while True: ret = self.parse_actor_list_page(page) for actor in ret['items']: actor['price_dict'] = json.dumps(actor['price_dict']) tbl_actor.insert(actor, replace=True) if ret['items_count'] * ret['page'] < ret['total']: page += 1 else: break
方法parse_actor_list_page()具體分析主播列表的html代碼,這是一個細致活;感受一下代碼截圖
3.3 知識點總結
a) 表單提交的POST方式
通常只提交一些kv數據時,使用application/x-www-form-urlencoded方式;
通常上傳文件時,使用multipart/form-data方式,但此種方式也是可以提交kv類數據的,比如上面的獲取主播列表數據時就是使用此方式。
b) Python的網絡請求庫Requests
這個庫太好用了!並且能夠對cookie自動處理,比如我在基類Website中的使用方式; 並且使用它構造multipart/form-data方式的post請求也很方便,比如方法Website::post_multi_part()
c) Python中使用正則匹配字符串中的整數,如下代碼:
avg_watched = tds[6].get_text(strip=True) # 平均觀看人數 mode = re.compile(r'\d+') tmp = mode.findall(avg_watched)
d) 使用try, except機制來實現類似php里的isset(),如下代碼:
# 判斷是否有逗號,比如8,189 try: index = string.index(',') string = string.replace(',', '') except ValueError: string = string
e) 一定要注意python中的’1’和1是不一樣的,需要你自己來做字符串和數字的類型轉換
4. 爬取秒拍網的主播和視頻信息
在沃米優選網拿到了各個直播平台的主播id, 先實現對一下網(http://www.yixia.com/)的抓取,獲取對應的主播和視頻信息。
一下網的個人主頁地址為http://www.yixia.com/u/uid, 這個uid就是主播id, 如下截圖:
4.1 分析結論和構思程序邏輯
- a) 在主播個人主頁能夠拿到主播的個人信息,如頭像,昵稱,粉絲數等,還能拿到主播的視頻列表;
- b) 視頻列表的加載方式是瀑布流方式,意味着走的是ajax接口;
- c) 視頻列表接口返回的數據是html代碼,仍然需要用BeautifulSoup解析;
- d) 請求視頻列表接口時需要提交suid參數,這個參數值需要用uid在主播個人頁獲取;
4.2 python編碼一下網的主播信息和視頻列表
- 構造class YiXia(Website),
- 方法parse_user_page()拿着uid去獲取主播個人信息;
- 方法get_video_list()按分頁獲取視頻列表數據
class YiXia(Website): ### 訪問主播頁面,也是視頻列表頁,從該頁面獲取到suid和主播個人信息 def parse_user_page(self, uid): print(self.__class__.__name__ + ':parse_user_page, uid=' + uid) user = dict() user['uid'] = uid url = 'http://www.yixia.com/u/' + uid bs = self.get_html(url) div = bs.find('div', {'class': 'box1'}) user['nickname'] = div.h1.a.get_text(strip=True) # 昵稱 stat = div.ol.get_text(strip=True) stat = re.split('關注\||粉絲', stat) user['follow'] = stat[0].strip() # 關注數 user['followed'] = stat[1].strip() # 粉絲數 ### ------這里省略很多代碼---- return user ### AJAX請求視頻列表 def get_video_list(self, suid, page=1): url = 'http://www.yixia.com/gu/u' payload = { 'page': page, 'suid': suid, 'fen_type': 'channel' } json_obj = self.get_json(url, params=payload) msg = json_obj['msg'] msg = BeautifulSoup(msg, 'html.parser') ### 解析視頻標題 titles = list() ps = msg.findAll('p') for p in ps: titles.append(p.get_text(strip=True)) # 視頻標題 ### 解析視頻贊和評論數 stats = list() divs = msg.findAll('div', {'class': 'list clearfix'}) for div in divs: tmp = div.ol.get_text(strip=True) tmp = re.split('贊|\|評論', tmp) stats.append(tmp) ### 解析視頻其他數據 videos = list() divs = msg.findAll('div', {'class': 'D_video'}) for (k, div) in enumerate(divs): video = dict() video['scid'] = div.attrs['data-scid'] ### ------這里省略很多代碼------ return videos ### 骨架函數,獲取每個視頻的每個分頁數據 def spider_videos(self, suid, video_count): page = 1 current = 0 tbl_video = YiXiaVideo() while current < int(video_count): print('spider_videos: suid=' + suid + ', page=' + str(page)) videos = self.get_video_list(suid, page) for video in videos: tbl_video.insert(video, replace=True) current += len(videos) page += 1 return True
4.3 知識點總結
大部分還是3.3里的知識點,這里重點注意字符串和整形,浮點型數字的轉換。比如粉絲數’2.3萬’是一個字符串,需要轉成浮點數2.3或者整數23000;再比如’8,189’需要轉成8189.
5. 程序結果
以下截圖為采集到的一下網視頻數據:
6. 知識點參考
這里列出我記錄下來的參考鏈接:
- 解析json: JSON encoder and decoder
- 解析html: BeautifulSoup
- 構造http請求:Requests
- 文件操作:Reading and Writing Files
- 數據庫的ORM庫: peewee
- 對字符串的操作:Python 字符串操作
- 對list的操作:More on Lists|Python 列表(List)操作方法詳解
- 對dict的操作:Dictionaries|Python中dict詳解
- 對True和Flase的使用:Python的Boolean操作
- POST提交方式:四種常見的 POST 提交數據方式
- Python編碼風格:PEP8