解決貓眼網反爬蟲策略的爬蟲
專案程式碼:Github
[目錄]
一.引入問題
可以看到,貓眼網電影評分,票房等的資料在響應的html中並不是直接提供給你的。這裡的xefcf,xef87等資料,是以‘特殊符號’的形式顯示出來的。
可以發現這裡請求了一個woff字型檔案,而xefcf,xef87等資料的規則就是在這其中的。所以我們只需要在請求這個網頁的同時,截去這部分style,然後獲得woff地址,將它下載到本地,進行解析,就可以實現對評分的解密了。
但是再來看下面,當同一部電影的頁面重新整理時。
這裡的評分編碼改變了,下面請求的woff的url也改變了。所以每次請求電影頁面使用的woff都不是同一個。
在這種情況下,如何才可以實現評分的爬取呢?
二.分步實現
1.頁面爬取
第一步我們需要先將整個html頁面獲取,其中重要的資料就是評分所在的那個span,還有woff的url所在的那個style.
這裡使用requests獲取網頁內容,用BeautifulSoup進行關鍵內容解析.
# 請求頭設定 header = { 'Accept': '*/*;', 'Connection': 'keep-alive', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Accept-Encoding': 'gzip, deflate, br', 'Host': 'maoyan.com', 'Referer': 'http://maoyan.com/', 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36' } def web(url): db_data = requests.get(url, headers=header) # 這裡直接將其編碼之前部分替換掉,防止之後使用轉義 soup = BeautifulSoup(db_data.text.replace("&#x",""), 'lxml') titles = soup.select( 'body > div.banner > div > div.celeInfo-right.clearfix > div.movie-stats-container > div > div > span > span') wotfs = soup.select('head > style') wotflist = str(wotfs[0]).split('\n') maoyanwotf就是當前頁面所使用的woff地址了。 = wotflist[5].replace(' ','').replace('url(\'//','').replace('format(\'woff\');','').replace('\')','')
此時titles就是一個獲取了評分span的list,當然它只有一個元素。
maoyanwotf就是當前頁面所使用的woff地址了。
2.woff下載
上一步我們得到了maoyanwotf,其中是當前頁面所使用的woff地址,所以接下來就要對這個url進行下載,儲存到本地。
# 下載請求電影頁面的woff字型到本地 def downfont(maoyanwotf): r = requests.get('http://'+maoyanwotf) with open("demo.woff", "wb") as code: code.write(r.content) font = TTFont("demo.woff") font.saveXML('to.xml')
這樣就下載到了這次請求頁面所用的woff了,儲存為demo.woff。
3.字型解析規則
那麼這個to.xml有什麼用呢?
這就是我們在每次重新整理都會改變woff的情況下,實現爬取的關鍵所在了。
font.saveXML('to.xml')就是將TTFont開啟的woff轉換為xml的形式,在xml中我們可以直接找到字型的相關資料,比如一個字元的筆畫資訊等。
來看一個字型xml檔案的重要內容。完整內容參考:to.xml
<!--GlyphOrder 這個標籤中就是這個字型xml包含的字元種類了,glyph00000和x這兩個不用考慮 -->
<GlyphOrder>
<GlyphID id="0" name="glyph00000"/>
<GlyphID id="1" name="x"/>
<GlyphID id="2" name="uniEE29"/>
<GlyphID id="3" name="uniE6A3"/>
....
<GlyphID id="9" name="uniE47E"/>
<GlyphID id="10" name="uniE9BA"/>
<GlyphID id="11" name="uniEEB4"/>
</GlyphOrder>
<!--glyf 這個標籤中是這些字型的具體座標畫法,一個TTGlyph對應一個字型,而其中contour標籤的座標資料,就是唯一確定這個字型是什麼的最好的確定方法-->
<glyf>
<TTGlyph name="glyph00000"/><!-- contains no outline data -->
<TTGlyph name="uniE47E" xMin="0" yMin="-13" xMax="511" yMax="719">
<contour>
<pt x="130" y="201" on="1"/>
...
<pt x="42" y="189" on="1"/>
</contour>
<instructions/>
</TTGlyph>
...
...
<TTGlyph name="uniE6A3" xMin="0" yMin="0" xMax="503" yMax="719">
<contour>
<pt x="503" y="84" on="1"/>
...
<pt x="152" y="84" on="1"/>
</contour>
<instructions/>
</TTGlyph>
</glyf>
GlyphOrder 這個標籤中就是這個字型xml包含的字元種類了,glyph00000和x這兩個不用考慮。
glyf 這個標籤中是這些字型的具體座標畫法,一個TTGlyph對應一個字型,而其中contour標籤的座標資料,就是唯一確定這個字型是什麼的最好的確定方法。
所以可以得出結論,不管每次請求的woff怎麼變,它裡面的字元的contour標籤內的座標畫法是不會改變的。
既然如此,我們只需要一個模板,在這個模板中,我們先手動解碼一個充當模板的woff。
如何製作這樣一個模板呢?
我們先來找一個woff和woff生成的xml,woff可以用字型檢視器High-Logic FontCreator開啟。效果如下。
之後我們將模板xml裡面的glyf中TTGlyph標籤的name屬性與開啟的woff對應比較,得到這個數值列表(我這裡woff和xml用的不是同一個,所以不要用這個比較!!!)
之後我們就可以根據glyf中TTGlyph的順序得到解碼數值的順序,之所以要一一對應,是為了方便之後用下標索引。
num = [8,6,2,1,4,3,0,9,5,7] #這個值是直接硬編碼進去的。
data = []
# 此處以解析xml的方式得到這個模板xml裡面contour標籤的內容。
xmlfilepath_temp = os.path.abspath("temp.xml")
domobj_temp = xmldom.parse(xmlfilepath_temp)
elementobj_temp = domobj_temp.documentElement
subElementObj = elementobj_temp.getElementsByTagName("TTGlyph")
for i in range(len(subElementObj)):
rereobj = re.compile(r"name=\"(.*)\"")
find_list = rereobj.findall(str(subElementObj[i].toprettyxml()))
data.append(str(subElementObj[i].toprettyxml()).replace(find_list[0],'').replace("\n",''))
最後data裡面是glyf中TTGlyph按順序的內容,其中當然就是contour了,而它的下表與num中一一對應,也就是說,這時候給你一個contour的座標內容,那就可以在data中查詢它的對應下標,根據這個下標找到num中下標的元素,這個元素就是解碼後的值了!!!
‘
獲得了模板,我們接下來就可以將請求這次頁面時獲得的woff轉化為xml進行對應了。
font = TTFont("demo.woff")
font.saveXML('to.xml')
#本次請求下載的字型的contour
new_font = []
xmlfilepath_find = os.path.abspath("to.xml")
domobj_find = xmldom.parse(xmlfilepath_find)
elementobj_find = domobj_find.documentElement
tunicode = elementobj_find.getElementsByTagName("TTGlyph")
for i in range(len(tunicode)):
th = tunicode[i].toprettyxml()
report = re.compile(r"name=\"(.*)\"")
find_this = report.findall(th)
get_code = th.replace(find_this[0], '').replace("\n", '')
for j in range(len(data)):
if not cmp(get_code,data[j]):
new_font.append(num[j])
其中get_code就是這次頁面時獲得的字型資料的list元素之一了,通過迴圈遍歷,最終得到了一個本次xml的對應數字的list : new_font 。
接下來獲得一個編碼與具體數字對應的font_list
font = TTFont("demo.woff")
font_list = font.getGlyphNames()
font_list.remove('glyph00000')
font_list.remove('x')
此時,有了new_font中的具體數字和font_list中的編碼值就可以完全解碼這個頁面中因為woff編碼反爬蟲的評分了。
# 匹配
star_woff = re.findall(re.compile(r">(.*)<"), str(titles[0]))[0].replace(';','').split('.') ;titles[0]是在第一步網頁解析時獲得的評分所在span
for i in star_woff:
getthis = i.upper()
for j in range(len(font_list)):
if not cmp(getthis,font_list[j].replace("uni","")):
print(new_font[j])
這樣就會輸出評分的整數部分和小數部分了。雖然這裡只是輸出了這兩個數字,但是反爬蟲評分,票房,人數等資料均可以使用前一步的new_font和font_list進行處理,就不再加以闡述了。