貓眼電影
如果試圖有人采集過貓眼電影的票房數(shù)據(jù)杠茬,就會發(fā)現(xiàn)它的關鍵字段是加密的。
打開瀏覽器的控制面板宁赤,就會發(fā)現(xiàn)雖然在前端是人眼可識別的栓票,但是在網(wǎng)頁源代碼里面卻不是正常的數(shù)字,可見它們是被加密了。
可以通過返回的網(wǎng)頁源代碼看到厉斟,3105.65
在網(wǎng)頁源代碼里是.
擦秽,其中0
對應著
,這樣對于爬蟲而言是無法處理的感挥,所以需要解析出真正的數(shù)字
解決方法
我們可以看到class
名字是stonefont
触幼,這是一個自定義字體,我們可以在返回的頁面里面找到這個字體資源的地址
如果可以將字體和數(shù)值一一對應那么就可以解決問題了堂鲤,那么媒峡,如何通過
python
實現(xiàn)?答案就是一個有關字體的模塊:fonttools
from fontTools.ttLib import TTFont
font_maoyan = TTFont('maoyan.woff')
font_maoyan.saveXML('maoyan.xml')
通過saveXML()
方法可以把字體資源保存在xml
文件中半哟,打開xml
文件寓涨,可以發(fā)現(xiàn)下面的內(nèi)容
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
<GlyphID id="0" name="glyph00000"/>
<GlyphID id="1" name="x"/>
<GlyphID id="2" name="uniF07E"/>
<GlyphID id="3" name="uniED44"/>
<GlyphID id="4" name="uniE41D"/>
<GlyphID id="5" name="uniE26D"/>
<GlyphID id="6" name="uniF391"/>
<GlyphID id="7" name="uniEE67"/>
<GlyphID id="8" name="uniF83E"/>
<GlyphID id="9" name="uniED3B"/>
<GlyphID id="10" name="uniF5A0"/>
<GlyphID id="11" name="uniE080"/>
</GlyphOrder>
如果通過瀏覽器對比贱田,可以發(fā)現(xiàn),0
對應
蔬墩,1
對應
耗拓,...,9
對應
樟插,正好和上圖對應上了竿刁,而這個其實可以通過getGlyphOrder()
方法的到食拜。
print(font_maoyan.getGlyphOrder())
# ['glyph00000', 'x', 'uniF07E', 'uniED44', 'uniE41D', 'uniE26D', 'uniF391', 'uniEE67', 'uniF83E', 'uniED3B', 'uniF5A0', 'uniE080']
所以我們在請求頁面之后,解析出字體資源的鏈接流强,然后再請求字體資源,解析出字符的映射關系队腐,最后再把請求頁面替換成源字符即可奏篙。我們可以把字體資源的結(jié)果緩存起來,如果緩存中有的話悠就,就直接返回充易,如果沒有,就請求資源在解析炸茧。
python實現(xiàn)
import re
import requests
from cStringIO import StringIO
from fontTools.ttLib import TTFont
_pat_font_url = re.compile("'(//vfile.meituan.net/colorstone/([0-9a-f]{32}).+?woff)'")
_pat_font = re.compile('&#x[0-9a-f]{4};')
maps = {}
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7',
'Host': 'maoyan.com',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36',
}
def get_font_regx(digest, font_url):
if digest in maps: # 緩存
return maps[digest]
resp = requests.get(font_url)
font = TTFont(StringIO(resp.content))
mappings = {'&#x{};'.format(k[3:].lower()): str(idx) for idx, k in enumerate(font.getGlyphOrder()[2:])} # 生成映射關系
def callback(regx): # 替換策略
return mappings.get(regx.group(0), regx.group(0))
maps[digest] = callback
return callback
if __name__ == '__main__':
resp = requests.get('http://maoyan.com/', headers=headers)
url, digest = _pat_font_url.search(resp.text).groups()
font_url = 'http:' + url
callback = get_font_regx(digest, font_url)
text = _pat_font.sub(callback, resp.text)
print(text)
實習僧
上面的網(wǎng)站只有數(shù)字被替換了梭冠,比較容易處理改备,但是這個網(wǎng)站就不一樣了,里面還有一些漢字也被替換了:
<div class="list">
<div class="po-name">
<div class="names cutom_font">
<a href="/intern/inn_ywdtbpiwtwcp" target="_blank">(實習)</a></div>
<div class="part">
<a class="cutom_font" href="/com/com_eknouhdxcyqb" target="_blank">看見音樂</a>- Python</div></div>
<div class="po-detail">
<div class="addr">
<img src="https://sxsimg.xiaoyuanzhao.com/static/new_main/img/img_10.png?v=d48ec07cec9ddecce51147cecd216171" />
<span>上海</span></div>
<div class="xz">
<img src="https://sxsimg.xiaoyuanzhao.com/static/new_main/img/img_17.png?v=fac8e686636c06166246f62531a07b56" />
<span class="cutom_font">
<i class="money"></i>-/天</span>
<span class="line">|</span>
<img src="https://sxsimg.xiaoyuanzhao.com/static/new_main/img/img_19.png?v=2f3bd39eae1f92a6852388ff53e2f5e7" />
<span class="cutom_font">天/周</span>
<span class="line">|</span>
<img src="https://sxsimg.xiaoyuanzhao.com/static/new_main/img/img_21.png?v=c6726c9ad768ef881bb73ec1dba5e120" />
<span class="cutom_font">個月</span></div>
</div>
<div class="com-logo">
<a href="/com/com_eknouhdxcyqb" target="_blank">
<img src="https://sxsimg.xiaoyuanzhao.com/46/F9/46ACA43D16FD1A878A5114184CEE9DF9.png" alt="看見音樂實習招聘" /></a>
</div>
</div>
如果我們像之前那樣那樣操作,發(fā)現(xiàn)GlyphOrder
沒有什么有用的內(nèi)容:
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
<GlyphID id="0" name="glyph00000"/>
<GlyphID id="1" name="x"/>
<GlyphID id="2" name="uni30"/>
<GlyphID id="3" name="uni31"/>
<GlyphID id="4" name="uni32"/>
<GlyphID id="5" name="uni33"/>
<GlyphID id="6" name="uni34"/>
<GlyphID id="7" name="uni35"/>
<GlyphID id="8" name="uni36"/>
<GlyphID id="9" name="uni37"/>
<GlyphID id="10" name="uni38"/>
<GlyphID id="11" name="uni39"/>
<GlyphID id="12" name="uni4E00"/>
...
</GlyphOrder>
其實通過注釋可以看到碉渡,id
只是顯示字符的順序滞诺,并不是它所代表的字符环疼,貓眼里面也是這樣。但是我們可以在另一個結(jié)構(gòu)里面找到答案:
<cmap_format_4 platformID="0" platEncID="3" language="0">
<map code="0x78" name="x"/><!-- LATIN SMALL LETTER X -->
<map code="0xe040" name="uni45"/><!-- ???? -->
<map code="0xe053" name="uni6b"/><!-- ???? -->
<map code="0xe0d3" name="uni53"/><!-- ???? -->
<map code="0xe103" name="uni42"/><!-- ???? -->
<map code="0xe21a" name="uni57"/><!-- ???? -->
<map code="0xe21f" name="uni4E00"/><!-- ???? -->
<map code="0xe231" name="uni38"/><!-- ???? -->
<map code="0xe253" name="uni5a"/><!-- ???? -->
<map code="0xe25a" name="uni5E7F"/><!-- ???? -->
<map code="0xe272" name="uni77"/><!-- ???? -->
<map code="0xe2c3" name="uni4c"/><!-- ???? -->
...
</cmap_format_4>
比較了幾個之后可以發(fā)現(xiàn)淋叶,
對應1
,它的unicode
編碼是\u0031
等限,
對應廣
,它的unicode
編碼是\u5e7f
望门,和上面能夠正確對應。而這個映射關系可以通過getBestCmap()
得到筹误,因此我們的實現(xiàn)厨剪,只要稍微修改之前的代碼即可。
font = TTFont('shixiseng.woff')
font.getBestCmap()
{120: 'x',
57408: 'uni45',
57427: 'uni6b',
57555: 'uni53',
57603: 'uni42',
57882: 'uni57',
57887: 'uni4E00',
57905: 'uni38',
57939: 'uni5a',
57946: 'uni5E7F',
57970: 'uni77',
58051: 'uni4c',
58080: 'uni4b',
...
}
python實現(xiàn)
import re
import requests
from cStringIO import StringIO
from fontTools.ttLib import TTFont
_pat_font_content = re.compile('myFont; src: url\("data:application/octet-stream;base64,(.+?)"')
_pat_font = re.compile('&#x[0-9a-f]{4}')
maps = {}
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7',
'Host': 'www.shixiseng.com',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36',
}
def get_font_regx(content):
if content in maps:
return maps[content]
ctx = content.decode('base64')
font = TTFont(StringIO(ctx))
mappings = {}
for k, v in font.getBestCmap().items():
if v.startswith('uni'):
mappings['&#x{:x}'.format(k)] = unichr(int(v[3:], 16))
else:
mappings['&#x{:x}'.format(k)] = v
def callback(regx):
return mappings.get(regx.group(0), regx.group(0))
maps[content] = callback
return callback
if __name__ == '__main__':
resp = requests.get('https://www.shixiseng.com/interns?k=python&p=1', headers=headers)
content = _pat_font_content.search(resp.text).group(1)
callback = get_font_regx(content)
text = _pat_font.sub(callback, resp.text)
print(text)