欧美中文字幕第一页-欧美中文字幕一区-欧美中文字幕一区二区三区-欧美中文字幕在线-欧美中文字幕在线播放-欧美中文字幕在线视频

[Python爬蟲] 「暴力」破解貓眼電影票房數據的反爬蟲機制

我是創始人李巖:很抱歉!給自己產品做個廣告,點擊進來看看。  

12月28日,人民日報發文批評豆瓣、貓眼上對《長城》、《擺渡人》、《鐵道飛虎》等電影的差評傷害了中國電影產業。


第二天(12月29日),人民日報再次發文,說中國電影要有容得下一星的肚量。

[Python爬蟲] 「暴力」破解貓眼電影票房數據的反爬蟲機制

我對國產電影已經無話可說,所以咱們還是來聊一聊有關數據分析的話題。

01. 常見反爬蟲機制

01.01 通過Headers反爬蟲

Headers就像寄快遞填的那個單子,這些信息與正文無關,卻關系著通信能否成功。以下圖為例,當我訪問自己的知乎主頁時,消息頭就包括:

消息頭 簡介
請求網址 我們與之互動通信的網絡地址
請求方法 GET指從這個網址獲取內容,而輸入用戶名密碼登錄網站則是POST方法
遠程地址 115.159.241.95是知乎的服務器IP,443是SSL加密通信的端口號
狀態碼 200表示OK,另一個很有名的狀態碼是404 Not Found

[Python爬蟲] 「暴力」破解貓眼電影票房數據的反爬蟲機制

當然消息頭遠不止這些內容,還包括你的操作系統型號、瀏覽器型號、語言、cookie等。多年以前還有人專門做個網頁,搞得好像算命似的來“猜”你的電腦有什么信息,其實都是瀏覽器悄悄地出賣了你。

[Python爬蟲] 「暴力」破解貓眼電影票房數據的反爬蟲機制

如果直接調用Python的urllib等擴展包,也可以發起網絡請求,但是默認的Headers信息也會誠實地顯示出來。目標網站受到帶有Headers信息的請求,就可以知道請求的發起方是人是鬼還是爬蟲。

這是最基礎的一種反爬蟲機制,破解也很簡單,只要在發起請求時偽造Headers,裝的像個人就行了。

01.02 基于用戶行為反爬蟲

有時候網絡狀況不好,點擊一個按鈕沒有反應,我們就會連續點擊很多次,然后網站彈出個對話框說“您在短時間內執行了太多相同操作”,并暫時封禁了這一行為。

人類的手工操作上限是很低的,高橋名人每秒也不過17次,專業電競選手APM也只有幾百,而爬蟲程序則可以高出幾個數量級,這會給服務器帶來很大負擔。

除了操作頻率以外,真正的人類和程序還有很多行為模式上的差別,很多網站都會采取機器學習算法來鑒別請求的發起方是否為正常人。

破解手段也是很多樣的,比如建立“IP池”,把大量的請求分散到不同的IP地址來源上,這樣看起來好像是很多用戶在短時間內自然操作的。

01.03 其他手段

爬蟲與反爬蟲之間的消長已有幾十年歷史,發展出的技術理念和手段紛繁多樣,如通過AJAX、JS腳本等方式動態產生網頁元素。應對這些反爬蟲技術,我的習慣是使用自動化測試框架Selenium,驅動瀏覽器內核,完全模擬用戶行為,也就是“不是爬蟲的爬蟲”,所謂手中無蟲、心中有蟲也。

在實際應用中,不論爬蟲還是反爬蟲,都是多種方式結合起來的。

02. 貓眼的反爬蟲機制

打開瀏覽器控制臺可以發現,票房數據其實是加密過的生僻unicode編碼,而且每次訪問獲得的unicode是隨機生成的。也就是說,明文攻擊只對單次訪問有效。

[Python爬蟲] 「暴力」破解貓眼電影票房數據的反爬蟲機制

而前端的閱讀是正常的,這是因為貓眼使用了來自美團的特殊字體,把密文編碼對應的字符,通過樣式表渲染成為數字,這其實就是一個解密的過程。

如果手動把數據元素的 class="cs" 屬性去掉,那么在系統默認字體中,前端出來的也是生僻字符。

[Python爬蟲] 「暴力」破解貓眼電影票房數據的反爬蟲機制

破解這種機制,大致上有兩個方向:

  • 密碼學破譯: 采集足量的明文-密文對照樣本,挖掘映射關系。
  • 模式識別: 繞過加密系統,從圖像中“讀”出數字。

密文的unicode編碼位數不多,破解的難度不大。但是問題在于,就像上面提到過的,反爬蟲技術也是綜合了多種方式,要高頻、大量地采集數據,很有可能觸發其他防御手段。

所以這里我采用了第二個方向。

03. 網頁自動截圖

(這一部分涉及較多本地文件處理,因各人操作系統差別較大,暫時不將代碼在線互動化)

03.01 擴展包

這一步用到的擴展包主要有兩個:

  • selenium: 自動化測試框架
  • PIL: Python Image Library,即Python圖片處理庫
				from selenium import webdriver
				from PIL import Image
			

03.02 獲取數字圖像

				driver = webdriver.Firefox() # 創建webdriver對象
				url = "http://piaofang.maoyan.com" # 定義目標url
				driver.get(url) # 打開目標頁面
				# 獲取當前電影名稱列表
				movie_names = [driver.find_element_by_xpath(".//*[@id='ticket_tbody']/ul[{}]/li[1]/b".format(i)).text for i in range(1,24)]
				# 獲取實時票房列表
				current_piaofang = [driver.find_element_by_xpath(".//*[@id='ticket_tbody']/ul[{}]/li[2]/b/i".format(i)).text for i in range(1,24)]
			

接下來是如何自動截圖并存儲,注意FireFox內核的截圖API只能截取當前可視頁面,而貓眼票房超過一屏,就必須加一個向下滾動操作。

從整個頁面的圖像中,再根據實時票房數據的位置和尺寸,單獨把數字截取出來。為了區分這兩個步驟,前一步叫“截圖”,后一步叫“摳圖”。

				# 定義截圖函數
				def snap_shot(url, image_path, scroll_top=90):
				# 打開頁面,窗口最大化
				driver.get(url)
				driver.maximize_window()
				# 調用JS腳本滾動頁面
				scroll_js = "var q=document.documentElement.scrollTop={}".format(scroll_top)
				driver.execute_script(scroll_js)
				# 截圖存儲
				driver.save_screenshot(image_path)
				# 定義摳圖函數
				def crop_image(image_path, pattern_xpath, crop_path, scroll_top=90):
				# 獲取頁面元素及其位置、尺寸
				element = driver.find_element_by_xpath(pattern_xpath)
				location = element.location
				size = element.size
				# 計算摳取區域的絕對坐標
				left = location['x']
				top = location['y'] - scroll_top
				right = location['x'] + size['width']
				bottom = location['y'] + size['height'] - scroll_top
				# 打開圖片,摳取相應區域并存儲
				im = Image.open(image_path)
				im = im.crop((left, top, right, bottom))
				im.save(crop_path)
				# 獲取當前時間戳
				now = datetime.datetime.now()
				now_sign = str(now.day)+str(now.hour)+str(now.minute)+str(now.second)
				# 啟動截圖函數,獲取當前頁面
				snap_shot_path_1 = "snap_shot/maoyan_{0}_{1}.png".format('1', now_sign)
				snap_shot_path_2 = "snap_shot/maoyan_{0}_{1}.png".format('2', now_sign)
				snap_shot(url, snap_shot_path_1, scroll_top=90)
				snap_shot(url, snap_shot_path_2, scroll_top=720)
				# 啟動摳圖函數
				for i in range(1,12):
				pattern = ".//*[@id='ticket_tbody']/ul[{}]/li[2]/b/i".format(i)
				crop_path = "snap_shot/crop/current_piaofang_{}.png".format(movie_names[i-1])
				crop_image(snap_shot_path_1, pattern, crop_path, scroll_top=90)
				for i in range(13,24):
				pattern = ".//*[@id='ticket_tbody']/ul[{}]/li[2]/b/i".format(i)
				crop_path = "snap_shot/crop/current_piaofang_{}.png".format(movie_names[i-1])
				crop_image(snap_shot_path_2, pattern, crop_path, scroll_top=720)
			

[Python爬蟲] 「暴力」破解貓眼電影票房數據的反爬蟲機制

03.03 建立訓練集

如此我們已經獲得了實時票房數據的圖像,但是這些數據少則三位(有效數字),多則六位,趕上大片的話七八位都是有可能的。對于程序來說要認識這么多數字,需要很長的訓練過程和極大的訓練集,那是不理智、不合適的。

其實我們地球人認識數字也不是這樣認的,而是先認識0~9這十個阿拉伯數字,再結合數位的知識,組成一個多位數字。

在這一案例中,加密字體是按位加密的,但是整個數字的結構沒有變,有效數字和小數點位置都是可以通過爬蟲直接獲取的信息。這就相當于,程序已經學會了關于“數位”的知識,那么只需要再讓程序學會0~9十個數字就行了。

要建立這樣的訓練集,還需要進一步處理上面得到的圖片,就是單獨把每一位數字切出來。

				# 獲取實時票房數據的有效數字長度列表
				curpf_lenths = [len(current_piaofang[i-1]) for i in range(1,24)]
				# 定義切圖函數
				def single_digit(index=1):
				lenth = curpf_lenths[index-1]
				name = movie_names[index-1]
				im = Image.open("snap_shot/crop/current_piaofang_{}.png".format(name))
				# 轉換為灰度圖像
				im = im.convert('L')
				# 切分整數部分
				for j in range(lenth-3):
				locals()['digit_'+ str(j)] = im.crop((0+j*6, 0, 6+j*6, 12))
				# 切分小數部分
				for j in range(lenth-3, lenth-1):
				locals()['digit_'+ str(j)] = im.crop((j*6+4.8, 0, 6+j*6+4.8, 12))
				# 對每部電影,按位存儲圖片
				for k in range(0, lenth-2):
				locals()['digit_'+ str(k)].save("snap_shot/train/digit_{0}_{1}_{2}.png".format(k, name, now_sign))
				# 啟動切圖函數
				for i in range(1,24):
				single_digit(i)
			

將得到的數字圖像分類整理(這一步真的只能人工完成了),因為貓眼的刷新頻率不高,所以沒有積累太多的訓練樣本,每個數字只有2、30個。但因為是印刷體,遠比手寫體容易識別,所以準確率還勉強可以接受。

[Python爬蟲] 「暴力」破解貓眼電影票房數據的反爬蟲機制

04. 模式識別

04.01 擴展包

  • os: 操作系統API
  • PIL: 圖像處理
  • numpy: 基礎數值計算
  • pandas: 結構化數據處理
  • scikit-learn: 機器學習

因為本案例識別難度不高,所以沒有使用專門的圖像處理模塊如keras, mxnet等。

04.02 圖像矢量化

這里的“圖像矢量化”跟“矢量圖”不是一個概念,后者是平面設計中常見的圖片格式。“矢量化”是一個特征提取的過程,對于scikit-learn,每一個訓練樣本(一張圖)都是一個高維矢量。

之前獲得的訓練集是由6×12的灰度圖片構成的,灰度的值域是0~255,每個像素點的灰度值都構成一個特征維度,這樣每個樣本就有18432維的特征,這就太高了。如果不轉換為灰度圖像,而是保留色彩,那維度又會高出幾個數量級。

所以對圖片需要進行特征壓縮,比如數字識別可以說是白紙黑字,那么不需要知道具體的灰度值,只要確定一個像素點是背景還是字體就可以了,也就是二值化,非0即1。

				# 二值化函數
				def binary_image(im):
				threshold = 200 # 閾值設為200
				table = []
				for i in range(256):
				if i < threshold:
				table.append(0)
				else:
				table.append(1)
				out = im.point(table,'1')
				return out
				# 矢量化函數
				def buildvector(im):
				v = []
				for i in im.getdata():
				v.append(i)
				return v
			

經過二值化與矢量化,每張圖片就變成了1行、72列的矢量,每一列都是0或1,代表紙或字。

04.03 機器學習

最后的過程我沒有弄得太復雜,直接調用了Scikit-learn里的支持向量分類器,基本上保留了默認設置,交叉驗證得分在 0.85 左右。

實際體驗是比85%準確率要好的,因為票房數據中0,1,2的出現頻率更高,訓練樣本多、識別率就更高。

End.

轉載請注明來自36大數據(36dsj.com): 36大數據 ? [Python爬蟲] 「暴力」破解貓眼電影票房數據的反爬蟲機制

隨意打賞

python 爬蟲python爬蟲貓眼票房專業版貓眼票房
提交建議
微信掃一掃,分享給好友吧。
主站蜘蛛池模板: 狠狠操福利视频 | 日本-区二区三区免费精品 日本热久久 | 亚洲国产成人久久综合一 | 国产成人精品三级91在线影院 | 国产成人免费观看在线视频 | 琪琪色在线视频 | 亚洲一区二区精品 | 99er这里只有精品 | 尹人综合网| 狠狠干奇米 | 久久手机在线视频 | 亚洲国产成人久久综合区 | 99久久免费精品国产免费 | 69国产成人综合久久精品91 | 青草国产| 一区二区三区在线观看视频 | 色综合久久一本首久久 | 精品91自产拍在线观看99re | 色综合中文 | 一级啪啪片 | 国产亚洲精品一区久久 | 日韩欧美毛片免费看播放 | 本地毛片| 久久免费视频播放 | 日本一区二区三区四区在线观看 | 亚洲欧美日韩专区一 | 久久88香港三级台湾三级中文 | 欧美综合色网 | 久草精品在线播放 | 精品国产亚洲一区二区三区 | 精品热久久 | 亚洲综合一区二区三区四区 | 99久久精品免费视频 | 天天天干干干 | 亚洲专区在线视频 | 免费在线不卡视频 | 国产精品亚欧美一区二区三区 | 日韩一区二区三区在线视频 | 豆国产97在线 | 中国 | 成人精品第一区二区三区 | 亚洲网站免费 |