猫眼电影
如果试图有人采集过猫眼电影的票房数据,就会发现它的关键字段是加密的。
打开浏览器的控制面板,就会发现虽然在前端是人眼可识别的,但是在网页源代码里面却不是正常的数字,可见它们是被加密了。
可以通过返回的网页源代码看到,3105.65
在网页源代码里是.
,其中0
对应着
,这样对于爬虫而言是无法处理的,所以需要解析出真正的数字
解决方法
我们可以看到class
名字是stonefont
,这是一个自定义字体,我们可以在返回的页面里面找到这个字体资源的地址
如果可以将字体和数值一一对应那么就可以解决问题了,那么,如何通过
python
实现?答案就是一个有关字体的模块:fonttools
from fontTools.ttLib import TTFont
font_maoyan = TTFont('maoyan.woff')
font_maoyan.saveXML('maoyan.xml')
通过saveXML()
方法可以把字体资源保存在xml
文件中,打开xml
文件,可以发现下面的内容
<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>
如果通过浏览器对比,可以发现,0
对应
,1
对应
,...,9
对应
,正好和上图对应上了,而这个其实可以通过getGlyphOrder()
方法的到。
print(font_maoyan.getGlyphOrder())
# ['glyph00000', 'x', 'uniF07E', 'uniED44', 'uniE41D', 'uniE26D', 'uniF391', 'uniEE67', 'uniF83E', 'uniED3B', 'uniF5A0', 'uniE080']
所以我们在请求页面之后,解析出字体资源的链接,然后再请求字体资源,解析出字符的映射关系,最后再把请求页面替换成源字符即可。我们可以把字体资源的结果缓存起来,如果缓存中有的话,就直接返回,如果没有,就请求资源在解析。
python实现
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)
实习僧
上面的网站只有数字被替换了,比较容易处理,但是这个网站就不一样了,里面还有一些汉字也被替换了:
<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>
如果我们像之前那样那样操作,发现GlyphOrder
没有什么有用的内容:
<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
只是显示字符的顺序,并不是它所代表的字符,猫眼里面也是这样。但是我们可以在另一个结构里面找到答案:
<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>
比较了几个之后可以发现,
对应1
,它的unicode
编码是\u0031
,
对应广
,它的unicode
编码是\u5e7f
,和上面能够正确对应。而这个映射关系可以通过getBestCmap()
得到,因此我们的实现,只要稍微修改之前的代码即可。
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实现
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)