思路解析:
1、我們需要明確爬取數據的目的:為了按熱度查看主播的在線觀看人數
2、瀏覽網頁源代碼,查看我們需要的數據的定位標簽
3、在代碼中發送一個http請求,獲取到網頁返回的html(需要注意的是,許多網頁都有反爬蟲機制,所以需要在請求中添加user-agent,偽裝成客戶端訪問)
4、對獲取到的html進行分析,使用正則表達式提取我們需要的部分(需要注意的是要把主播名稱和觀看人數所在的塊整個提取,分別提取的話如果網頁設計不規律的話很難對應)
5、將得到的單個主播的數據存儲在字典中,並把所有主播的數據存儲在list中
6、如果抓取到的數據中包含空格換行等無用字符,還需要對數據進行精煉。
7、對抓取到的數據從大到小進行排序(需要注意的是:我們抓取到的數據是字符串,並且單位可能是人或者萬人,所以要對觀看人數進行處理)
8、將排好序的數據遍歷輸出。
由於斗魚網站的網頁是采用模板實現的,案例是抓取王者榮耀的主播的數據,想抓取別的類目的話,只需要修改url即可~
代碼實現:
''' 爬取斗魚網站的王者榮耀分類主播的觀看人數和主播名字,並按熱度排名 ''' from urllib import request from io import BytesIO import gzip import re class Spider(): url = 'https://www.douyu.com/g_wzry' # 根節點的字符串匹配正則表達式,匹配除了根節點中間的所有字符,非貪婪模式,找到第一個</div>就結束 root_pattern = '<div class="DyListCover-info">([\s\S]*?)</div>' headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36'} # 觀看人數匹配字符串 number_pattern_str = '<span class="DyListCover-hot is-template">([\s\S]*?)</span>' # 觀看人數的字符串,刪除前面的icon部分 number_pattern = '<svg><use xlink:href="#icon-hot_8a57f0b"></use></svg>' name_pattern_str = '<h2 class="DyListCover-user is-template">([\s\S]*?)</h2>' name_pattern = '<svg><use xlink:href="#icon-user_c95acf8"></use></svg>' # 抓取自定網頁內容並解碼 def __fetch_content(self): # 發送一個http的請求,獲取返回的html代碼 req = request.Request(Spider.url, headers=Spider.headers) htmls = request.urlopen(req).read() # 解碼 buff = BytesIO(htmls) f = gzip.GzipFile(fileobj=buff) htmls = f.read().decode('utf-8') return htmls # 分析抓取內容,選取標簽時盡量選取閉合標簽,標簽成組的選擇好對應 def __analysis(self, htmls): # 獲取到需要的全部數據 root_html = re.findall(Spider.root_pattern, htmls) # 由於網頁中一個塊有兩個相同的class類,其中第一個主播介紹 # 第二個才是需要的數據,所以選取奇數下標元素 root_info_html = root_html[1::2] # 最后獲取到的數據列表 anchors = [] # 遍歷列表,提取用戶名和觀看人數 for html in root_info_html: # 提取到的是帶icon的部分 watch_num_str = re.findall(Spider.number_pattern_str, html) # 剔除icon部分 watch_num = re.sub(Spider.number_pattern, '', watch_num_str[0]) # 提取帶icon的name的部分 name_str = re.findall(Spider.name_pattern_str, html) name = re.sub(Spider.name_pattern, '', name_str[0]) # 將名字和觀看人數用字典存儲,最后再用列表存儲每個主播的數據 anchor = {'name': name, 'number': watch_num} anchors.append(anchor) return anchors # 精煉函數 # def __refine(self, anchors): # lam = lambda anchor: {'name': anchor['name'][0], 'number': anchor['number'][0]} # return map(lam, anchors) # 排序 def __sort(self, anchors): anchors = sorted(anchors, key=self.__sort_key, reverse=True) return anchors # 排序規則 def __sort_key(self, anchor): # 提取數字並計算 r = re.findall('\d*', anchor['number']) number = float(r[0]) if '萬' in anchor['number']: number *= 10000 return number # 顯示數據 def __show(self, anchors): # for anchor in anchors: # print(anchor['name'], ':', anchor['number']) for rank in range(0, len(anchors)): print("Rank ", rank+1, ": ", anchors[rank]['name'], " ", anchors[rank]['number']) # 入口方法 def go(self): htmls = self.__fetch_content() anchors = self.__analysis(htmls) anchors = self.__sort(anchors) self.__show(anchors) spider = Spider() spider.go()