如何爬取猫眼全部信息(电影信息、演员信息)

爬取猫眼的全部信息,这里主要指的是电影列表里的电影信息和演员信息,如下界面。

       爬去的时候有两个难点。一:字体加密(如今好像机制有更新来些,用网上的方法不行);二:美团检测。下面将分别讲述我解决的过程。

一、字体加密

关于字体加密,网络上介绍的很多,思路也都类似。猫眼每次加载的时候会动态的加载不同的字形编码。解决思路呢,就是先下载好它的一个字体文件(.woff结尾的,方法网上多有介绍,我就不在累述了),然后每次爬取新的界面的时候,将新的字体文件的字形坐标与之前下载好的的对比。网上很多方法还是很久之前的,当时猫眼的字体加密机制还很简陋,只是很单纯的比对坐标是否相等,现在的机制是每次加载的字形都是有细微变化的。网上很多人的方法是说其变化在一个范围内,只要比对他们的差异不超过一个范围就是同一个数字。我也用了这个方法,但发现很多数字都识别错误,比如3,5,9;1,4;0,8;这三组数字很容易混淆。说明这个问题不是那么简单的。我用的方法先是取三个基准字体文件,然后将每个基准字体文件分别与动态加载的进行坐标对比,我也设置了一个差异范围,对符合这个差异的进行计数,最后计数最大的就认证为对应的数字。这样做之后还是不免出现错误,然后我发现每个数的坐标数目有差异,比如3,5,9这个三个数的坐标数量明显有很大不同,由于每个数字自身的坐标数每次加载也是不同的,有细微的差别,所以我再进行一个坐标个数差异判断。还有一点比较重要的,就是这个差异值的选择,网上很多选择的是8、10,可能猫眼加密机制改了的原因,这几个预设差异值,识别率很低。我试的时候 5 的识别率最高,这些你们可以自己实验。之前我们选了三组基准,由于我们进行了前面的识别操作,识别率进行很高了,所以再进行一次三局两胜的操作,再次提高识别率。由于代码比较多,我放一些关键的代码。还有一种方法也许可行,我没试过,可以参考这篇博客参考博文,这篇博客使用knn算法来识别的,识别率应该挺高的。但这个需要多准备些字体文件来提高识别率。

def replace_font(self, response,res):

        #基准,比对三次,两次以上一致即默认正确
        #我是“我要出家当道士”,其他非原创
        base_font = TTFont('./fonts/base.woff')
        base_font.saveXML('./fonts/base_font.xml')
        base_dict = {'uniF870': '6', 'uniEE8C': '3', 'uniECDC': '7', 'uniE6A2': '1', 'uniF734': '5',
                     'uniF040': '9', 'uniEAE5': '0', 'uniF12A': '4', 'uniF2D2': '2', 'uniE543': '8'}
        base_list = base_font.getGlyphOrder()[2:]

        base_font2 = TTFont('./fonts/base2.woff')
        base_font2.saveXML('./fonts/base_font2.xml')
        base_dict2 = {'uniF230': '6', 'uniEBA1': '3', 'uniF517': '7', 'uniF1D2': '1', 'uniE550': '5',
                      'uniEBA4': '9', 'uniEB7A': '0', 'uniEC29': '4', 'uniF7E1': '2', 'uniF6B7': '8'}
        base_list2 = base_font2.getGlyphOrder()[2:]

        base_font3 = TTFont('./fonts/base3.woff')
        base_font3.saveXML('./fonts/base_font3.xml')
        base_dict3 = {'uniF8D3': '6', 'uniF0C9': '3', 'uniEF09': '7', 'uniE9FD': '1', 'uniE5B7': '5',
                      'uniF4DE': '9', 'uniF4F9': '0', 'uniE156': '4', 'uniE9B5': '2', 'uniEC6D': '8'}
        base_list3 = base_font3.getGlyphOrder()[2:]

        #网站动态加载的字体
        #我是“我要出家当道士”,其他非原创
        font_file = re.findall(r'vfile\.meituan\.net\/colorstone\/(\w+\.woff)', response)[0]
        font_url = 'http://vfile.meituan.net/colorstone/' + font_file
        #print(font_url)
        new_file = self.get_html(font_url)
        with open('./fonts/new.woff', 'wb') as f:
            f.write(new_file.content)
        new_font = TTFont('./fonts/new.woff')
        new_font.saveXML('./fonts/new_font.xml')
        new_list = new_font.getGlyphOrder()[2:]


        coordinate_list1 = []
        for uniname1 in base_list:
            # 获取字体对象的横纵坐标信息
            coordinate = base_font['glyf'][uniname1].coordinates
            coordinate_list1.append(list(coordinate))

        coordinate_list2 = []
        for uniname1 in base_list2:
            # 获取字体对象的横纵坐标信息
            coordinate = base_font2['glyf'][uniname1].coordinates
            coordinate_list2.append(list(coordinate))

        coordinate_list3 = []
        for uniname1 in base_list3:
            # 获取字体对象的横纵坐标信息
            coordinate = base_font3['glyf'][uniname1].coordinates
            coordinate_list3.append(list(coordinate))

        coordinate_list4 = []
        for uniname2 in new_list:
            coordinate = new_font['glyf'][uniname2].coordinates
            coordinate_list4.append(list(coordinate))

        index2 = -1
        new_dict = {}
        for name2 in coordinate_list4:#动态
            index2 += 1

            result1 = ""
            result2 = ""
            result3 = ""

            index1 = -1
            max = -1;
            for name1 in coordinate_list1: #本地
                index1 += 1
                same = self.compare(name1, name2)
                if same > max:
                    max = same
                    result1 = base_dict[base_list[index1]]

            index1 = -1
            max = -1;
            for name1 in coordinate_list2: #本地
                index1 += 1
                same = self.compare(name1, name2)
                if same > max:
                    max = same
                    result2 = base_dict2[base_list2[index1]]

            index1 = -1
            max = -1;
            for name1 in coordinate_list3: #本地
                index1 += 1
                same = self.compare(name1, name2)
                if same > max:
                    max = same
                    result3 = base_dict3[base_list3[index1]]

            if result1 == result2:
                new_dict[new_list[index2]] = result2
            elif result1 == result3:
                new_dict[new_list[index2]] = result3
            elif result2 == result3:
                new_dict[new_list[index2]] = result3
            else:
                new_dict[new_list[index2]] = result1

        for i in new_list:
            pattern = i.replace('uni', '&#x').lower() + ';'
            res = res.replace(pattern, new_dict[i])
        return res


    """
    输入:某俩个对象字体的坐标列表
    #我是“我要出家当道士”,其他非原创
    输出相似度
    """
    def compare(self, c1, c2):
        count = 0
        length1 = len(c1)
        length2 = len(c2)
        if abs(length2-length1) > 7:
            return -1
        length = 0
        if length1 > length2:
            length = length2
        else:
            length = length1
        #print(length)
        for i in range(length):
            if (abs(c1[i][0] - c2[i][0]) < 5 and abs(c1[i][1] - c2[i][1]) < 5):
                count += 1
        return count

二、美团防爬

关于美团防爬,网上也有很多博客,但内容大家应该知道的,你抄我,我抄你的,最后差不多都是一样的。但还是有很多优质的博文值得拜读。我水平也有限,给的参考借鉴也有限,所以我这里只给两种我自己用过的,而且成功爬取我需要的3000部电影和9000位演员数据。我是两种方法交织使用来爬取数据的,我使用了正常的requests来爬取和selenium自动工具(配合mitm——proxy)。速度最快的是requests,但很容易被检测;性能最稳定的是selenium,不易被检测。

1,正常的requests

     requests对于爬取猫眼还是很有用的,只是被美团检测后,需要很长时间的冷却,具体时间未知,我使用request配置已经登录过后的cookie成功爬取了全部的电影详情信息。参考代码如下。其实比较麻烦的就是xpath解析网页源码了。

class getFilmsData(object):

    def __init__(self):
        self.headers = {}
        self.headers['User-Agent'] = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.06'
        self.headers['Cookie'] = '填写你的cookie,不知道的话,留言,我会快速告诉你'
        self.dataManager = mongoManager()
        self.fontDecode = FontDecode()

    #根据url,获取数据,
    #limitItem为限制页,即其页item之前的已经获取完成
    #我是“我要出家当道士”,其他非原创
    def getData(self,url,limitItem):

        s = requests.session()
        s.headers = self.headers
        s.keep_alive = False

        content = s.get(url).text
        #print("URLTEXT is :",response.text)

        if "验证中心" in content:
            print("目录界面美团验证")
            return False
        sel = etree.HTML(content)

        count = 0

        urls = sel.xpath('//div[@class="movie-item"]')
        scores = sel.xpath('//div[@class="channel-detail channel-detail-orange"]')
        for box in urls:
            #抓取列表界面的电影url
            count += 1
            if count < limitItem:
                continue
            print("begin ",count,"th item")
            scoreCheck = scores[count-1].xpath('.//text()')[0]
            #无评分的电影不爬取
            if scoreCheck == "暂无评分":
                break

            urlBack = box.xpath('.//a/@href')[0]
            #获取电影详情url
            url = "https://maoyan.com"+urlBack

            #获取电影名称、时长、上映日期、票房、评分、演员、海报url
            resp = s.get(url)
            realUrl = resp.url
            res = resp.text
            if "验证中心" in res:
                print("信息界面美团验证")
                return False
            #res2= self.replace_font(res)
            selTmp = etree.HTML(res)
            #电影票房
            #我是“我要出家当道士”,其他非原创
            money = selTmp.xpath('//div[@class="movie-index-content box"]/span[1]/text()')
            unit = selTmp.xpath('//div[@class="movie-index-content box"]/span[2]/text()')
            filmMoney = ""
            if len(money) == 0:
                #无票房的电影不爬取
                continue
            else:
                ascll = str(money[0])
                #print("money ascll is:",ascll)
                utfs = str(ascll.encode('unicode_escape'))[1:].replace("'","").replace("\\\\u",";&#x").split('.')
                unicode = ""
                if len(utfs)>1:
                    unicode = utfs[0][1:]+";."+utfs[1][1:]+";"
                else:
                    unicode = utfs[0][1:]+";"
                filmMoney = self.fontDecode.replace_font(res,unicode)
                if len(unit) > 0:
                    filmMoney += unit[0]
            #电影名称
            filmName = selTmp.xpath('//div[@class="movie-brief-container"]/h1[1]/text()')[0]
            #电影海报
            filmImage = selTmp.xpath('//div[@class="avatar-shadow"]/img[1]/@src')[0]
            #电影时长
            filmTime = selTmp.xpath('//div[@class="movie-brief-container"]/ul[1]/li[2]/text()')[0].replace('\n', '').replace(' ', '')
            #电影上映时间
            filmBegin = selTmp.xpath('//div[@class="movie-brief-container"]/ul[1]/li[3]/text()')[0].replace('\n', '')
            #电影评分
            score = selTmp.xpath('//div[@class="movie-index-content score normal-score"]/span[1]/span[1]/text()')
            #由于票房和评分字体编码加密的缘故,所以需要先编码为unicode,在进行解密
            #我是“我要出家当道士”,其他非原创
            filmScore = ""
            if len(score) == 0:
                filmScore = "评分暂无"
            else:
                ascll = str(score[0])
                #print("score ascll is:",ascll)
                utfs = str(ascll.encode('unicode_escape'))[1:].replace("'","").replace("\\\\u",";&#x").split('.')
                unicode = ""
                if len(utfs)>1:
                    unicode = utfs[0][1:]+";."+utfs[1][1:]+";"
                else:
                    unicode = utfs[0][1:]+";"
                filmScore = self.fontDecode.replace_font(res,unicode)+"分"
            print(filmMoney,filmScore)
            #获取电影演员表,只获取前10个主要演员
            actorSol = selTmp.xpath('//div[@class="tab-celebrity tab-content"]/div[@class="celebrity-container"]/div[@class="celebrity-group"][2]/ul/li')
            #print(actors)
            actors = []
            actorUrls = []
            num = len(actorSol)
            for i in range(10):
                num -= 1
                if num < 0:
                    break
                actorUrl = "https://maoyan.com"+actorSol[i].xpath('.//div[@class="info"]/a/@href')[0]
                actorItem = actorSol[i].xpath('.//div[@class="info"]/a/text()')[0].replace('\n', '').replace(' ', '')
                if len(actorSol[i].xpath('.//div[@class="info"]/span[1]/text()')) > 1:
                    actorItem += (" "+actorSol[i].xpath('.//div[@class="info"]/span[1]/text()')[0].replace('\n', '').replace(' ', ''))
                actorUrls.append(actorUrl)
                actors.append(actorItem)
            #获取电影简介
            introductionT = ""
            introductionF = selTmp.xpath('//span[@class = "dra"]/text()')
            if len(introductionF) > 0:
                introductionT = introductionF[0]
            print(count,filmName,filmImage,filmBegin,filmTime,filmScore,filmMoney,actors,introductionT)
            time.sleep(1)
        s.close()

2,selenium配合mitmproxy

第二种方法就是selenium配合mitmproxy,selenium是一个自动化的工具,与request爬取网页内容不同,selenium可以模仿用户打开浏览器来浏览网页,所见的都可爬取。这个也多用在爬取大量数据避免检测时。但如果单纯的使用selenium,还是很容易被检测出来,这里我理解也不是特别深,只是单纯会用而已。大致就是浏览器设置的几个回复参数,如果使用selenium的话,这几个参数就会被赋值,而正常用户浏览的话,这几个参数是未定义的;还有一些其他的防selenium的,我没继续深入了解。

具体的配置方法可以参考我之前写的博客:mitmproxy配合selenium

我发现,我在爬取的时候,这个方法比第一个稳定,出现美团验证的几率很低,而且出现之后复制网站网址手动到对应的浏览器里打开,就会出现验证,你手动多试几次就过去了,然后就可以继续爬取。当然这个速度肯定没request快。举个例子吧,比如我在爬取https://maoyan.com/films/celebrity/28936的时候出现了美团检测,我用的是谷歌的驱动,那么复制这个网址到谷歌浏览器中,这时候一般会出现美团检测,你手动的验证通过后(不通过就关闭浏览器再来几次),再继续爬取就行了。其实selenium自动化使用浏览器还是被网址认为是人在浏览,只有出现检测的时候,才会被网址识别出是selenium,所以我们只要认为的过检测就可以了。

以上就是我的方法,想要源码的私信我

猜你喜欢

转载自blog.csdn.net/qq_37437983/article/details/104382704