美文网首页大数据 爬虫Python AI Sql
Python爬虫:Scrapy框架采集猫眼TOP100电影数据(

Python爬虫:Scrapy框架采集猫眼TOP100电影数据(

作者: 愤怒的it男 | 来源:发表于2023-01-02 21:41 被阅读0次

概述:本文采用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

免责声明:
本公众号所有源码均为个人学习所编写,仅可用于计算机技术学习及研究等合法行为,禁止利用本公众号的源码从事任何违反本国(地区)法律法规的业务,如有发现存在违法违规行为我会举报到网监部门。

相关文章

网友评论

    本文标题:Python爬虫:Scrapy框架采集猫眼TOP100电影数据(

    本文链接:https://www.haomeiwen.com/subject/sijkcdtx.html