筆者認爲,數據的價值不僅僅只體現在企業中,個人也可以體會到數據的魅力,用技術力量探索行爲密碼,讓大數據助跑每一個人,歡迎直筒們關注我的公衆號,大家一起討論數據中的那些有趣的事情。
我的公衆號爲:livandata
0 慣性嘚瑟
剛開始搞爬蟲的時候聽到有人說爬蟲是一場攻堅戰,聽的時候也沒感覺到特別,但是經過了一段時間的練習之後,深以爲然,每個網站不一樣,每次爬取都是重新開始,所以,爬之前誰都不敢說會有什麼結果。
前兩天,應幾個小朋友的邀請,動心思玩了一下大衆點評的數據爬蟲,早就聽說大衆點評的反爬方式不一般,貌似是難倒了一片英雄好漢,當然也成就了網上的一衆文章,專門講解如何爬取大衆點評的數據,筆者一邊閱讀這些文章尋找大衆點評的破解思路,一邊爲大衆點評的程序員小哥哥們鳴不平,辛辛苦苦寫好的加密方式,你們這些爬蟲寫手們這是鬧哪樣?破解也就算了,還發到網上去,還發這麼多~
筆者在閱讀完這些文章之後,自信心瞬間爆棚,有如此多的老師,還有爬不了的網站,於是,筆者信誓旦旦的開始了爬大衆點評之旅,結果,一上手就被收拾了,各個大佬們給出的爬蟲方案中竟然有手動構建對照表的過程,拜託,如果我想手動,還要爬蟲做什麼?別說手動,半自動都不行。
大家看到這裏或許頭上有些霧水了,什麼手動?什麼半自動?還對照表?大佬,你這是什麼梗?再不解釋一些我就要棄劇了,葛優都拉不回來~
大家先不要着急,靜一靜~,對照表後面會講,這裏只需要知道我遇到困難了,就可以了,不過諮詢了幾個大佬之後,好在解決了,革命的路上雖有羈絆,終歸還是有同志的~
好,現在開始入正題,點評的程序員哥哥請不要寄刀片:
1 基礎環節
大衆點評的數據爬蟲開始還是很正常的,各個題目、菜單基本上都可以搞下來:
代碼如下:
#!/usr/bin/env python # _*_ UTF-8 _*_ # 個人公衆號:livandata import requests from lxml import etree header = {"Accept":"application/json, text/javascript", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", "Cookie":"cy=1; cye=shanghai; _lxsdk_cuid=16ca41d3344c8-050eb4ac8f1741-4d045769-1fa400-16ca41d3345c8; _lxsdk=16ca41d3344c8-050eb4ac8f1741-4d045769-1fa400-16ca41d3345c8; _hc.v=38ae2e43-608f-1198-11ff-38a36dc160a4.1566121473; _lxsdk_s=16ce7f63e0d-91a-867-5a%7C%7C20; s_ViewType=10" } url = 'http://www.dianping.com/beijing/ch10/g34060o2' response = requests.get(url, headers=header) data = etree.HTML(response.text) title = data.xpath('//*[@id="shop-all-list"]/ul/li[1]/div[2]/div[1]/a/@title') print(title)
爬取的結果爲:
按照常規的套路,爬蟲可以說是寫成了。但是,現在的網站大多使用了反爬,一方面擔心自己的服務器會被爬蟲搞的超負荷,另一方面也爲了保護自己的數據不被其他人獲取。
大衆點評就是衆多帶反爬的網站中的佼佼者,使用了比較高級的反爬手法,他們把頁面上的關鍵數字隱藏了起來,增加了爬蟲難度,不信~你看:
2 CSS加密
我們用如下字段爬取商店的評論數:
data = etree.HTML(response.text) title = data.xpath('//*[@id="shop-all-list"]/ul/li[1]/div[2]/div[2]/a[1]/b/svgmtsi[1]/text()') print(title)
結果得到的卻是如下字段:
一看傻了,這是什麼鬼?
我們緊接着審查了網站數據,看到的內容卻是:
這是什麼鬼?評論數呢?
查看了網站的源代碼:
發現原來顯示點評數的字段顯示成了:
這是爲什麼呢?
好在網上的大神們給出瞭解答,這就是CSS加密。
接下來我們就介紹如何破解CSS加密:
我們把源代碼上加密的部分取下來觀察一下:
<svgmtsi></svgmtsi>
我們發現了網上一直在討論的svgmtsi標籤,這個標籤是矢量圖的標籤,基本上意思就是顯示在這裏的文字是一個矢量圖,解析這個矢量圖需要到另外一個地方找一個對照表,通過對照表將編碼內容翻譯成人類可以識別的數字。
那麼,對照表在哪裏呢?
我們先記錄下標籤中的class值:shopNum(爲什麼記錄,先不要着急,後面會講到),然後在源代碼中查找svg,我們發現瞭如下內容:
大寶藏被挖掘了。
這好像是個鏈接,我們點擊一下,發現頁面跳轉到了一個全新的水月洞天:
這真是個偉大的發現,他預示着我們的爬蟲找到了門路,我們在這個頁面上查找剛纔class中的值shopNum,然後,我們看到了如下內容:
url("//s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/bc2c52b3.woff");}
.shopNum{font-family: 'PingFangSC-Regular-shopNum';}@font-face{font-family:
"PingFangSC-Regular-reviewTag";
src:url("//s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/07758223.eot");
在這段代碼中距離shopNum最近的地方,我們找到一個woff文件。
你沒有猜錯,這個woff文件就是我們的對照表。
同樣的思路,這是一個網址,我們可以把他下載下來,把這個網址複製到瀏覽器的地址欄中,點回車,會跳出如下快樂的界面。
下載完成後,我們在瀏覽器中打開woff的翻譯工具:
http://fontstore.baidu.com/static/editor/index.html
我們把前面的&#x去掉並替換成uni,後面的;去掉,得到字段爲:unif784。
祕密揭曉了:
是不是很眼熟?
是不是很驚喜?
是不是很意外?
恭喜你,第一步成功了~
這個編碼在woff文件中對應的值爲7。
就是我們要找的親人~
3 woff文件處理
事情到這裏其實就可以畫個句號了,因爲接下來的思路就變的非常簡單了,我們用上面的通用爬蟲下載下網站上所有編碼和對應的class值,然後根據class值找到對應的woff文件,再在woff文件中確定編碼對應的數字或漢字就可以。
但是,當我們擴充這一思路的時候卻遇到了兩個問題:
1)如何讀取出woff文件中的數字,大衆點評有多個woff文件,怎麼對照讀取呢?難不成要一個個寫出來?根據前面網站裏的文章來講,對的,你猜的很準,這就是我文章一開始寫的半自動,崩潰了吧,好在筆者找到了新的方法,取代了半自動的問題,這個新的方法就是OCR識別,後面我會仔細講解。
2)頁面的編碼是變動的,你沒有看錯,這個值是會變的,好在這個事件沒有發生在大衆點評中,但是汽車之家、貓眼等網站使用的CSS加密會隨頁面的刷新發生變動,有沒有驚到你?
如果你只需要大衆點評,第二個問題幾乎可以不用考慮了,但是筆者認爲要做一個有理想的爬蟲,儘量多的獲取知識點纔是正確的,所以,筆者研究了汽車之家、貓眼、天眼等幾個用CSS加密的網站,找到了一個通用的方法,下面我們來介紹一下這個通用方法。
先看一下貓眼網站上編碼的動態效果:
如圖:
我們先找到一個加密編碼,把他複製出來,看到的編碼如下:
然後我們刷新一下頁面,再看源碼:
不管你驚不驚,反正我是驚到了~
針對這一變化,筆者心中產生了一個疑惑,如果說編碼會變,那瀏覽器是怎麼獲取到準確的值的呢?說明一定存在一個統一的方法供瀏覽器調用,於是,筆者重新研究了編碼的調用方式,驚奇的發現了其中的祕密:
我們以如下兩個編碼來揭露這個今天大冪冪:
 -->unif0d5
-->unie765
這兩個字段都是表示數字中的1,那他們有什麼規律呢?
我們首先解碼woff文件成xml格式:
from fontTools.ttLib import TTFont font = TTFont('e765.woff') font.saveXML('e765.xml')
在pycharm中我們打開xml文件:
找到unie765所在的位置:
這一串代碼是字形座標,瀏覽器就是根據這個字形座標翻譯出我們能夠識別的漢字:1。
同樣的思路,我們再去解析unif0d5的值,得到如下圖:
我們驚奇的發現,這兩個竟然一樣,是不是所有的值對應的字形座標都是唯一的呢,答案是肯定的,變化的只是上圖name中的編碼,座標與數字之間是一對一的,所以,我們的思路來了,我們只需要找到編碼所對應的字形座標,然後想辦法解析出這個字形座標所對應的數字就可以了。
4 完整思路
問題展示基本上清楚了,我們接下來看一下怎麼自動化解決上面兩個問題:
首先展示一下思路:
這是在excel裏面畫的,大家可以只關注內容,忽略掉背景線。
解釋一下上面的思路:
首先:我們從頁面上獲取到文字編碼和woff文件,注意,這裏的字形編碼和woff文件一定要一起獲取,因爲每個編碼對應一個woff文件,一旦刷新頁面,編碼在woff文件中的對應關係就會變化,找不到對應的字形座標。
data = etree.HTML(response.text) title = data.xpath('//*[@id="shop-all-list"]/ul/li[1]/div[2]/div[2]/a[1]/b/svgmtsi[1]/text()') print(title)
其次:我們把字形編碼轉化成uni開頭的編碼,並獲取到woff文件中的字形座標。
from fontTools.ttLib import TTFont font = TTFont('f0d5.woff') coordinate = font['glyf']['uniF0D5'].coordinates print(coordinate)
第三:用matplotlib解析這一座標,並保存成圖片。
#!/usr/bin/env python # _*_ UTF-8 _*_ # 個人公衆號:livandata from fontTools.ttLib import TTFont import matplotlib.pyplot as plt font = TTFont('f0d5.woff') coordinate = font['glyf']['uniF0D5'].coordinates coordinate = list(coordinate) fig, ax = plt.subplots() x = [i[0] for i in coordinate] y = [i[1] for i in coordinate] plt.fill(x, y, color="k", alpha=1) # 取消邊框 for key, spine in ax.spines.items(): if key == 'right' or key == 'top' or key == 'bottom' or key == 'left': spine.set_visible(False) plt.plot(x, y) # 取消座標: plt.axis('off') plt.savefig('uniF0D5.png') plt.show()
通過上面的解析,我們可以得到1的圖片:
這個1好難看,不過好在解析出來了~
第四:使用OCR解析這個數字:
# 圖片轉化成string: try: from PIL import Image except ImportError: import Image import pytesseract captcha = Image.open(r'uniF0D5.png') print(captcha) result = pytesseract.image_to_string(captcha, lang='eng', config='--psm 6 --oem 3 -c tessedit_char_whitelist=0123456789').strip() print(result)
自此,我們的文字就可以直接識別出來了,我們就再也不需要用半自動的小米加步槍了,我們可以直接使用衝鋒槍了
不過需要注意的是使用OCR解碼文字需要一定的時間,耗時還是比較長的,如果經常使用這一思路,建議可以構建一個“字形座標:文字”的數據庫表,下次使用時解析出字形座標,直接到數據庫裏匹配對應的文字就可以了。
介紹一篇OCR的文章吧,可以瞭解一下如何解析文字:
http://www.inimei.cn/archives/770.html
讚美時間
每一位看到這篇文章的帥哥美女都是天上的星星,而且是最亮的一顆~
看都看到這裏了,加個關注再走唄~
筆者堅持數據的力量,讓大數據賦能每個人~