概述:本文采用scrapy爬虫框架对猫眼电影中的TOP100电影数据进行采集,过程采用了JavaScript逆向和字体反爬技术,其中字体反爬用到图像识别技术,可关注本人公众号【Python是我哥】下载源码,也可加入Q群(756788325)讨论学习。
万事开头难,但爬虫开头不难,先创建项目和爬虫。
scrapy startproject maoyan
cd maoyan
scrapy genspider top100 https://www.maoyan.com/
在频繁地点击几次链接之后,大家可以发现,网页会重定向到一个滑块缺口验证页面。
1.PNG这其实是猫眼电影设计的IP限制反爬,不能让你过度频繁请求。用随机IP和随机UA应该可以绕过,不过我这里只是在setting.py中设置一下请求并发和下载延时,这样可以通过降低请求的频繁度来避开验证页面。毕竟咱们只是学习,而且100条数据也不多,没必要硬刚。
CONCURRENT_REQUESTS = 1
DOWNLOAD_DELAY = 4
先观察TOP100电影列表页,它共有10页,每页10部电影,每页的URL不同点在最后的offset参数。offset参数应该代表的是当页第一条数据的排名偏移数,第一页offset=0,第二页offset=10,……第十页offset=90。
2.PNG因此我们只需要利用循环语法构造出URL,然后分别请求即可。由于整篇文章涉及的代码比较多,也不可能全部代码都贴出来,可以关注公众号【Python是我哥】获取源码,然后对照的这篇文章看会更好。
def start_requests(self):
for offset in range(10):
url = 'https://www.maoyan.com/board/4?offset={}'.format(offset*10)
yield scrapy.Request(url=url, callback=self.parse)
请求回来的列表页响应中,可以很容易找到每部电影详情页的URL。
3.PNG使用CSS选择器解析出所有详情页的URL
urls = response.css('p.name a::attr(href)').getall()
不过该URL是相对路径(如:/films/344881),可以补全成绝对路径(如:https://www.maoyan.com/films/344881)。分析请求回来的详情页响应,发现没有我们想要的数据。像电影名称、播放时长、上映地点这些数据应该是通过Ajax异步加载的,果断在网络资源中全局搜索查找。
4.PNG发现真正把数据请求回来的URL后面带有很多参数,甚至还有加密的(如:https://www.maoyan.com/ajax/films/344881?timeStamp=1672649608583&index=1&signKey=b6eca8c0b54f72c8d9c840007188e446&channelId=40011&sVersion=1&webdriver=false)
5.PNG有加密,咋办?别怂,干就是了,果断js逆向分析解密。先XHR打个断点,通过调用堆栈跟踪一下,发现所有请求就是在这里发起的,参数在这里已经准备好了。
channelId: 40011
index: 7
sVersion: 1
signKey: "66b11f57783161333ec6905b48677c35"
timeStamp: 1672650667533
webdriver: false
6.PNG
那就逆着js程序的逻辑,不断地打断点逆向跟踪分析,这里就不一一展开了,看得懂的不需要展开,看不懂的展开了也没有用。经过分析得到channelId固定为40011,sVersion固定为1,webdriver固定为false,index为10以内的随机整数,timeStamp为当前的时间戳,signKey是通过加密算法算出来的。
7.PNG 8.PNG 9.PNG既然分析出来了,那剩下的就是修改js代码,让其按照原来的加密算法跑出参数来。js代码写好后,通过python中的execjs库执行js代码,取得返回的参数,然后就可以拼接出正确的URL。
with open('maoyan.js', 'r', encoding='utf-8') as f:
js_code = f.read()
maoyan_js = execjs.compile(js_code)
params = self.maoyan_js.call('getParams')
ajax_url = 'https://www.maoyan.com/ajax' + url
ajax_url = ajax_url + '?timeStamp=' + str(params['timeStamp'])
ajax_url = ajax_url + '&index=' + str(params['index'])
ajax_url = ajax_url + '&signKey=' + str(params['signKey'])
ajax_url = ajax_url + '&channelId=' + str(params['channelId'])
ajax_url = ajax_url + '&sVersion=' + str(params['sVersion'])
ajax_url = ajax_url + '&webdriver=' + str(params['webdriver'])
yield scrapy.Request(url=ajax_url, callback=self.detail_parse)
其中的JavaScript代码的篇幅有点长,这里就不贴出来了,全部源代码都可以关注本人公众号【Python是我哥】获取。
到这里你以为这就完了?那你就想多了,顶多是完成了一半。你会发现请求回来详情页的响应中,有电影名称,播放时长,剧情简介等数据,但是猫眼口碑,评价人数和累计票房是一堆以&#x开头的转义序列。那咋办呢?还是一个字,干。
10.PNG这应该是猫眼电影设计的字体反爬,果断在响应的源码中搜索下看有没有woff或者ttf文件之类的,果不其然。
11.PNG根据链接下载字体文件并使用FontCreator打开,发现woff文件中字体编号的后四位与源码中转义序列的后四位是对应的。那么我只需要创建字体编号与数字对应关系的字典,问题就迎刃而解了。之后在网页源码中拿到转义序列,就可以根据转义序列转化成字体编号,再根据字体编号查找字典得到对应数字。
12.PNG字体文件中也就10个数字,手动创建一个字典还不简单吗?那你又想多了。在刷新网页之后,你会发现转义序列是变化的,字体文件也是变化的。不同的字体文件中,相同数字的编号不一样,数字的排列顺序不一样,连数字的字形都不一样(应该是做了随机的细微调整,使用TTFont将字体文件保存为xml文件可以看到)。也就是说你根据固定一个字体文件手动创建的字典,在网页刷新后就用不了了。下图是同一页面刷新前后两次的对比,都是9和2,但源码中的转义序列和字体文件中的编号都发生了变化。
14.PNG那怎么样才能让程序自动根据当前的字体文件创建字典呢?这就需要用到PIL和pytesseract了,前者可以把字体文件中的数字按编号的顺序写在一张图片上,后者可以根据图片识别出数字,那么就可以自动创建出编号和数字对应关系的字典了。
font_path = 'font/' + item['files'][0]['path']
font = TTFont(font_path)
code_list = font.getGlyphOrder()[2:]
for i in range(len(code_list)):
code_list[i] = code_list[i].replace("uni", "\\u").lower()
text_content = "".join(code_list)
text_content = text_content.encode('utf-8').decode('unicode_escape')
text_font = ImageFont.truetype(font_path, 100)
image = Image.new("RGB", (600, 100), (255, 255, 255))
image_draw = ImageDraw.Draw(image)
image_draw.text((0, 0), text_content, font=text_font, fill="#000000")
result = pytesseract.image_to_string(image)
code_map = dict(zip(code_list, list(result)))
有了这个字典,那在请求回来详情页的响应中取到转义序列之后,就可以转换成编号,然后再查找字典取得对应的数字。
def process_num(self, num, code_map):
if num:
all_num = num.encode('unicode_escape').decode('utf-8')
digits = re.findall(r'\\u[0-9a-z]{4}', all_num)
for digit in digits:
if digit == '\\u4e07':
d = '万'
else:
d = code_map[digit]
all_num = all_num.replace(digit, d)
return all_num
else:
return ''
能看到这里的人应该算是志同道合的人了,有什么问题也可以加Q群:756788325,大家一起讨论学习爬虫技术。最后展示一下采集到的数据。
15.PNG免责声明:
本公众号所有源码均为个人学习所编写,仅可用于计算机技术学习及研究等合法行为,禁止利用本公众号的源码从事任何违反本国(地区)法律法规的业务,如有发现存在违法违规行为我会举报到网监部门。
网友评论