天天看點

Python的原生爬蟲案例

完整的爬蟲:

反扒機制,自動登入,代理IP等等           

複制

示例爬蟲:

簡單的資料抓取,簡單的資料處理           

複制

目的:

不使用爬蟲架構完成資料爬取
鞏固知識、合理程式設計、内部原理           

複制

示例内容:

内容:
    爬取直播網站
确定工作:
    确定爬取資料:某個分類下各主播人氣的資料
    确定實作結果:将人氣進行排序
準備:
    分析網站結構
    尋找包含爬取資訊的頁面
    F12檢查網頁,定位資訊(主播姓名,人氣資料)
原理:
    對html檔案進行文本分析并從中提取資訊
使用技術
    正規表達式
具體步驟:
    模拟HTTP請求,向伺服器發送請求,擷取到伺服器傳回的HTML
    用正規表達式處理網頁文本,過濾出有用資料
    找到相關常量标簽,作為正則的定位邊界
    定位标簽:
        盡量選擇具有唯一辨別的辨別的标簽
        盡量選擇與目标資料相近的标簽
        盡量選擇将所有目标資料都包含的标簽(閉合的标簽),比如包含姓名+人氣的标簽
        上述即盡量選父标簽,不選兄弟标簽,為了易于構造正則提取内容
注意:
    構造正則不是難點,難點是應對反爬蟲的措施

整體書寫規範
    每行代碼不要過長
    推薦書寫一個入口程式
    推薦在入口中平行的調用邏輯代碼
    每個方法中代碼盡量少
    注意塊注釋和行級注釋的書寫格式


代碼結構:           

複制

'''
類注釋
'''
class spider():
	#抓取頁面内容(行注釋)
	def __fetch_content(self):
		'''
		方法注釋
		'''
		pass
	#資料抽取
	def __analysis(self, htmls):
		pass
	#資料精煉
	def __refine(self, pairs):
		pass
	#排序
	def __sort(self, pairs):
		pass
	#排序的算子
	def __seed(self, pairs):
		pass
	#資料展現
	def __show(self, pairs):
		pass
	
	#函數入口
	def go(self):
調用
s = spider()
s.go()  	
```			
	書寫代碼:
		抓取頁面:
			url = 'http://www.huya.com/g/lol'
		分析原網頁:           

複制

<li class="game-live-item" gid="1">
    <a href="http://www.huya.com/yanmie" class="video-info new-clickstat" target="_blank" report='{"eid":"click/position","position":"lol/0/1/5","game_id":"1","ayyuid":"380335691"}'>
        <img class="pic" data-original="//screenshot.msstatic.com/yysnapshot/1711a622dc4d670fe32de2018f78a2d030fcde37cfe8?imageview/4/0/w/338/h/190/blur/1" src="//a.msstatic.com/huya/main/assets/img/default/338x190.jpg" onerror="this.onerror=null; this.src='//a.msstatic.com/huya/main/assets/img/default/338x190.jpg';" alt="最強趙信折翼的直播" title="最強趙信折翼的直播">
                <div class="item-mask"></div>
        <i class="btn-link__hover_i"></i>
        <em class="tag tag-blue">藍光</em>    </a>
    <a href="http://www.huya.com/yanmie" class="title new-clickstat" report='{"eid":"click/position","position":"lol/0/1/5","game_id":"1","ayyuid":"380335691"}' title="可以用趙信上王者第一的男人" target="_blank">可以用趙信上王者第一的男人</a>
    <span class="txt">
        <span class="avatar fl">
            <img data-original="//huyaimg.msstatic.com/avatar/1081/63/c83bdc0701b64646c86065e273fd05_180_135.jpg" src="//a.msstatic.com/huya/main/assets/img/default/84x84.jpg" onerror="this.onerror=null; this.src='//a.msstatic.com/huya/main/assets/img/default/84x84.jpg';" alt="最強趙信折翼" title="最強趙信折翼">
            <i class="nick" title="最強趙信折翼">最強趙信折翼</i>
        </span>
                <span class="num"><i class="num-icon"></i><i class="js-num">1.5萬</i></span>
    </span>
</li>           

複制

1 找到含目标資料的最小公約子集:
<span class="txt">
    <span class="avatar fl">
        <img data-original="//huyaimg.msstatic.com/avatar/1081/63/c83bdc0701b64646c86065e273fd05_180_135.jpg" src="//a.msstatic.com/huya/main/assets/img/default/84x84.jpg" onerror="this.onerror=null; this.src='//a.msstatic.com/huya/main/assets/img/default/84x84.jpg';" alt="最強趙信折翼" title="最強趙信折翼">
        <i class="nick" title="最強趙信折翼">最強趙信折翼</i>
    </span>
            <span class="num"><i class="num-icon"></i><i class="js-num">1.5萬</i></span>
</span>           

複制

目标資料:
	最強趙信折翼,1.5萬
構造正規表達式:
	選出上面的最小公約子集:	
		root_pattern = '<span class="txt">([\s\S]*?)\r\n</li>' 
		注意:
			要加問号,聲明是非貪婪的比對
	
	然後選出,姓名:	
		name_pattern = '<i class="nick" title="([\s\S]*?)">'
	然後選出,人氣:
		num_pattern = '<i class="js-num">([\s\S]*?)</i>'
	
	注意:
		上述正則的邊界并不一定是完整的html标簽,因為使用正則即對字元進行比對,是以可以随意拆分。
		
幾個重要功能:
	擷取頁面内容:
		from urllib import request
		tmp = request.urlopen(spider.url)
		htmls = tmp.read() 
		注意:此處的結果是位元組碼:bytes
		必須要進行轉化:bytes->str
		htmls = str(htmls,encoding='utf-8')
		 
		注意:不推薦列印出頁面内容,會出現解碼問題,可以加斷點調試
 
	循環:
		此處的循環需要擷取到下标,而直接for i in list,擷取不到下标
		此時應該使用for index in range(0,len(list))這種形式
		代碼:
			for rank in range(0, len(pairs)):
				print('第',rank,'名:',pairs[rank]['name'],':',pairs[rank]['number'])
	排序:
		此處使用内置函數sorted(iterable, cmp=None, key=None, reverse=False)
		注意:
			key = 函數名,此函數應該傳回排序的比較值
			cmp = 函數名,此函數可以重寫排序規則
			reverse=False,從小到大正序排列
		代碼:
			sorted(pairs,key = seed,reverse = True)
			def seed(self, pairs):
				tmp = pairs['number'].replace('萬','')
				if('萬' in pairs['number']):
					tmp = float(tmp) * 10000
				return int(tmp)
完整的爬蟲代碼:           

複制

from urllib import request

import re

class spider():

url = 'http://www.huya.com/g/lol'
root_pattern = '<span class="txt">([\s\S]*?)\r\n</li>' #父級目錄比對
# 使用概括字元集 [\d] [\w]  [\s]  [.]
#注意:要加問号,聲明是非貪婪的比對
name_pattern = '<i class="nick" title="([\s\S]*?)">'
num_pattern = '<i class="js-num">([\s\S]*?)</i>'

def __fetch_content(self):  #加__的函數:私有方法
    tmp = request.urlopen(spider.url)
    htmls = tmp.read() #此處的結果是位元組碼:bytes
    # bytes->str
    htmls = str(htmls,encoding='utf-8')
    a = 1#如果不添加這一句,htmls指派發生在最後一句
    #那麼斷點停止時會得不到htmls的值,這時要人為多餘的添加一條語句并将斷點放到這裡即可
    #print(htmls)不推薦列印,會出現解碼問題
    return htmls

def __sort(self, pairs):
    #def sorted(iterable, cmp=None, key=None, reverse=False)
    #注意要指定key值
    return sorted(pairs,key=self.__seed,reverse=True)

def __show(self, pairs):
    #for循環中需要拿到序号,直接使用range形式的for循環
    for rank in range(0, len(pairs)):
        print('第',rank,'名:',pairs[rank]['name'],':',pairs[rank]['number'])

def __seed(self, pairs):
    tmp = pairs['number'].replace('萬','')
    if('萬' in pairs['number']):
        tmp = float(tmp) * 10000
    return int(tmp)

def __refine(self, pairs):
    f = lambda p: { 
        'name':p['name'][0].strip(),
        'number':p['number'][0]
         }
    return map(f, pairs)

def __analysis(self, htmls):
    root_htm = re.findall(spider.root_pattern,htmls)
    pairs = []
    for item in root_htm:
        name = re.findall(spider.name_pattern,item)
        num = re.findall(spider.num_pattern,item)
        pair = {'name':name,'number':num}
        pairs.append(pair)
    return pairs

#設定入口函數,這是一個主方法,裡面都是平級函數,推薦這種寫法 
def go(self):   
    htmls = self.__fetch_content()      #抓取頁面内容
    pairs = self.__analysis(htmls)      #抽取所需資料
    pairs = list(self.__refine(pairs))  #資料精煉
    pairs = self.__sort(pairs)          #資料排序
    self.__show(pairs)                  #資料顯示,或後續處理(入庫等)           

複制

#執行個體化并調用入口函數

s = spider()

s.go()

```

注意事項:

如果需要調試,不推薦站樁print,推薦使用斷點調試

調試方法:

啟動應用程式 F5

單步執行F10

跳到下一個斷點 F5

調到函數内部 F11

例如在 html = tmp.read() 處打斷點
    在目前斷點處,懸停滑鼠會顯示變量值,也可以在vscode左側的甲殼蟲選項中檢視變量的值


缺陷:
    雖然通過類進行了封裝,但是其實最基礎的封裝
    但是,複用性差,抵禦需求變化的能力太差,違反開閉原則

進階:
    可以使用更加面向對象的設計來完成功能
    借助構造函數__init__來對類進行帶參數的執行個體化:
    代碼:
        class Spider():
            '''
            This is a class
            '''
            url = ''
            root_pattern = ''
            name_pattern = ''
            num_pattern = ''

            def __init__(self,url,root_pattern,name_pattern,num_pattern): 
                Spider.url = url
                Spider.root_pattern = root_pattern
                Spider.name_pattern = name_pattern
                Spider.num_pattern = num_pattern    

        s = Spider(
            'http://www.huya.com/g/4',
            '<span class="txt">([\s\S]*?)\r\n</li>',
            '<i class="nick" title="([\s\S]*?)">',
            '<i class="js-num">([\s\S]*?)</i>'
        )
        s.go()  
    類封裝的意義:
        這樣封裝可以完成一個主播人氣排序的爬蟲類,參數有四個:
            爬取的直播網站;
            爬取的名稱人氣的父元素的正則
            爬取名稱的正則
            爬取人氣的正則

展望:
    爬蟲子產品或架構:
        BeautifulSoup子產品
        Scrapy架構(多線程、分布式、較臃腫,看需求謹慎使用)
    反反爬蟲技術:
        頻繁爬取會使IP被封,需要使用定時器!切記!!
        尋找代理IP庫,應對封IP
    整個流程的核心:    
        爬取的原始資料如何處理,精煉
        處理的結果如何存儲,分析           

複制