[586]使用requests来爬取大众点评

看完本文,你可以:

1、了解大众点评的CSS反爬虫机制
2、破解反爬虫机制
3、使用requests即可正确获取到评论数,平均价格,服务,味道,环境数据,评论文本数据;

1.前言

在工作生活中,发现越来越多的人对大众点评的数据感兴趣,而大众点评的反爬又是比较严格的。采取的策略差不多是宁可错杀一万,也不放过一个。有的时候正常浏览都会跳出验证码。

另外,在PC端的展示数据是通过CSS来控制的,从网页上看不出来太大的区别,但是用普通的脚本取获取时,会发现数据是获取不到的,具体的源代码是下面这样的:

image

在搜资料的时候,你会发现,很多教程都是用的selenium之类的方法,效率太低,没有啥技术含量。

所以,这篇文章的面向的对象就是PC端的大众点评;目标是解决这种反爬虫措施,使用requests获取到干净正确的数据;

2.正文开始

相信搞过大众点评网站的同学都应该知道上面的这种是一个css反爬的方法,具体的解析操作,即将开始。

找到藏着秘密的css

当我们的鼠标在上面框框内的span上面点击时,会发现右边部分会相应的发生变化:

image

这张图片很重要,很重要,很重要,我们要的值,几乎都从这里匹配出来。

这里我们看到了“vxt20”这个变量对应的两个像素值,前面的是控制用哪个数字,后面的是控制用哪一段的数字集合,先记下,后面要用,同时这里的值应该是6;

这里其实就是整个破解流程最关键的一步了。在这里我们看到了一个链接。

瞎猫当死耗子吧,点进去看看。
https://s3plus.meituan.net/v1/mss_0a06a471f9514fc79c981b5466f56b91/svgtextcss/f556c0559161832a4c6192e097db3dc2.svg

image.png

你会发现,返回的是一些数字,我一开始是不知道这是啥玩意的,看过一些大神的解析才知道,其实这里就是我们看到的数字的来源,也就是整个破解的源头,知道了这些数字的含义,也就可以直接破解了整个反爬的流程。

现在直接看这个svg源代码:

image

可以看到这里面的几个关键数字:font-size:字体大小;还有几个y的值,我到后面才知道原来这个y是个阈值,起的是个控制的作用。

所以,这一反爬的原理就是:

获取属性值与偏移量和阈值映射,然后从svg文件中找到真数据。

现在我们就要用到上面的像素值了。

1.把所有的值取绝对值;
2.用后面的值来选择用哪个段的数字,这里的值是103,所以使用第三个段的数字集合;
3.因为每个字体是12个像素,所以用163/12=13.58,约等于14,那么我们数一下第14个数字是啥,没错,是6,和预期一样。你可以多试验几次。

以上,就是整个破解的逻辑过程。

画个流程图:
image

3.Show Code

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 
# @Author  : 
# @File    : dzdp.py
# @Software: PyCharm
import requests,re,math,time
from lxml import etree
import lxml.html as H

"""
大众点评CSS破解
没有做太多的优化,因为没有大量的代理,爬不了太多的内容
这里只是跟大家分享一下处理的流程。
具体见:https://cuiqingcai.com/?p=6341
"""

headers = {
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
}
r = requests.get("http://www.dianping.com", headers=headers)
_cookie = ''
for cookie in  r.cookies:
    _cookie += "%s=%s;"%(cookie.name, cookie.value)

headers.update({"Cookie": _cookie})


def get_tag(_list, offset=1):
    _new_list = [data[0:offset] for data in _list]

    if len(set(_new_list)) == 1:
        # 说明全部重复
        offset += 1
        return get_tag(_list, offset)
    else:
        _return_data = [data[0:offset - 1] for data in _list][0]
        return _return_data

def get_css(content):
    matched = re.search(r'href="([^"]+svgtextcss[^"]+)"', content, re.M)
    if not matched:
        raise Exception("cannot find svgtextcss file")
    css_url = matched.group(1)

    css_url = "https:" + css_url
    class_tag = re.findall("<b><span class=\"(.*?)\"></span>", content)
    _tag = get_tag(class_tag)

    return css_url, _tag


def get_svg_threshold_and_int_dict(css_url, _tag):
    con = requests.get(css_url, headers=headers).content.decode("utf-8")
    index_and_word_dict = {}
    # 根据tag值匹配到相应的svg的网址

    find_svg_url = re.search(r'span\[class\^="%s"\].*?background\-image: url\((.*?)\);' % _tag, con)
    if not find_svg_url:
        raise Exception("cannot find svg file, check")
    svg_url = find_svg_url.group(1)
    svg_url = "https:" + svg_url
    svg_content = requests.get(svg_url, headers=headers).content
    svg_doc = H.document_fromstring(svg_content)
    datas = svg_doc.xpath("//text")
    # 把阈值和对应的数字集合放入一个字典中
    last = 0
    for index, data in enumerate(datas):
        y = int(data.xpath('@y')[0])
        int_set = data.xpath('text()')[0]
        index_and_word_dict[int_set] = range(last, y+1)
        last = y
    return index_and_word_dict


def get_css_and_px_dict(css_url):
    con = requests.get(css_url, headers=headers).content.decode("utf-8")
    find_datas = re.findall(r'(\.[a-zA-Z0-9-]+)\{background:(\-\d+\.\d+)px (\-\d+\.\d+)px', con)
    css_name_and_px = {}
    for data in find_datas:
        # 属性对应的值
        span_class_attr_name = data[0][1:]
        # 偏移量
        offset = data[1]
        # 阈值
        position = data[2]
        css_name_and_px[span_class_attr_name] = [offset, position]
    return css_name_and_px


def get_data(url ):
    """
    :param page_url: 待获取url
    :return:
    """
    con = requests.get(url, headers=headers).content.decode("utf-8")
    # 获取css url,及tag
    css_url, _tag = get_css(con)
    # 获取css对应名与像素的映射
    css_and_px_dict = get_css_and_px_dict(css_url)
    # 获取svg的阈值与数字集合的映射
    svg_threshold_and_int_dict = get_svg_threshold_and_int_dict(css_url, _tag)

    doc = etree.HTML(con)
    shops = doc.xpath('//div[@id="shop-all-list"]/ul/li')
    for shop in shops:
        # 店名
        name = shop.xpath('.//div[@class="tit"]/a')[0].attrib["title"]

        comment_num = 0
        price_num = taste = service= environment = 0
        comment_and_price_datas = shop.xpath('.//div[@class="comment"]')
        for comment_and_price_data in comment_and_price_datas:
            _comment_data = comment_and_price_data.xpath('a[@class="review-num"]/b/node()')
            # 遍历每一个node,这里node的类型不同,分别有etree._ElementStringResult(字符),etree._Element(元素),etree._ElementUnicodeResult(字符)
            for _node in _comment_data:
                # 如果是字符,则直接取出
                if isinstance(_node, etree._ElementStringResult):
                    comment_num = comment_num * 10 + int(_node)
                else:
                    # 如果是span类型,则要去找数据
                    # span class的attr
                    span_class_attr_name = _node.attrib["class"]
                    # 偏移量,以及所处的段
                    offset, position = css_and_px_dict[span_class_attr_name]
                    index = abs(int(float(offset) ))
                    position = abs(int(float(position)))
                    # 判断
                    for key, value in svg_threshold_and_int_dict.items():
                        if position in value:
                            threshold = int(math.ceil(index/12))-1
                            number = int(key[threshold])
                            comment_num = comment_num * 10 + number

            _price = comment_and_price_data.xpath('a[@class="mean-price"]/b/node()')
            for price_node in _price:
                if isinstance(price_node, etree._ElementUnicodeResult):
                    if len(price_node) > 1:
                        price_num = price_num * 10 + int(price_node[1:])
                elif isinstance(price_node, etree._ElementStringResult):
                    price_num = price_num * 10 + int(price_node)
                else:
                    span_class_attr_name = price_node.attrib["class"]
                    # 偏移量,以及所处的段
                    offset, position = css_and_px_dict[span_class_attr_name]
                    index = abs(int(float(offset)))
                    position = abs(int(float(position)))
                    # 判断
                    for key, value in svg_threshold_and_int_dict.items():
                        if position in value:
                            threshold = int(math.ceil(index / 12))-1
                            number = int(key[threshold])
                            price_num = price_num * 10 + number

        others_num_node = shop.xpath('.//span[@class="comment-list"]/span')
        for others_datas in others_num_node:
            if others_datas.xpath('text()') and others_datas.xpath('text()')[0] == u"口味":
                _taste_data = others_datas.xpath('b/node()')
                for _taste in _taste_data:
                    if isinstance(_taste, etree._Element):
                        css_class = _taste.attrib["class"]
                        # 偏移量,以及所处的段
                        offset, position = css_and_px_dict[css_class]
                        index = abs(int(float(offset)))
                        position = abs(int(float(position)))
                        # 判断
                        for key, value in svg_threshold_and_int_dict.items():
                            if position in value:
                                threshold = int(math.ceil(index / 12))-1
                                number = int(key[threshold])
                                taste = taste * 10 + number
                    else:
                        if len(_taste) > 1:  #
                            taste = taste * 10 + int(_taste[1:])

            if others_datas.xpath('text()') and others_datas.xpath('text()')[0] == u"服务":
                _taste_data = others_datas.xpath('b/node()')
                for _taste in _taste_data:
                    if isinstance(_taste, etree._Element):
                        css_class = _taste.attrib["class"]
                        # 偏移量,以及所处的段
                        offset, position = css_and_px_dict[css_class]
                        index = abs(int(float(offset)))
                        position = abs(int(float(position)))
                        # 判断
                        for key, value in svg_threshold_and_int_dict.items():
                            if position in value:
                                threshold = int(math.ceil(index / 12))-1
                                number = int(key[threshold])
                                service = service * 10 + number
                    else:
                        if len(_taste) > 1:  #
                            service = service * 10 + int(_taste[1:])

            if others_datas.xpath('text()') and others_datas.xpath('text()')[0] == u"环境":
                _taste_data = others_datas.xpath('b/node()')
                for _taste in _taste_data:
                    if isinstance(_taste, etree._Element):
                        css_class = _taste.attrib["class"]
                        offset, position = css_and_px_dict[css_class]
                        index = abs(int(float(offset)))
                        position = abs(int(float(position)))
                        # 判断
                        for key, value in svg_threshold_and_int_dict.items():
                            if position in value:
                                threshold = int(math.ceil(index / 12))-1
                                number = int(key[threshold])
                                environment = environment * 10 + number
                    else:
                        if len(_taste) > 1:
                            environment = environment * 10 + int(_taste[1:])
        print("restaurant: {}\n, "
              "comment total num: {}\n, "
              "price num: {}\n,"
              "taste score:{}\n,"
              "service socre:{}\n, "
              "environment_score:{}, "
              "\n ".
              format(name,comment_num, price_num, taste, service, environment))


if __name__ == '__main__':
    url = "https://www.dianping.com/suzhou/ch10/g110"
    get_data(url)

image.png

参考:https://cuiqingcai.com/6341.html

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
爬虫(Web Crawler)是一种自动化程序,用于从互联网上收集信息。其主要功能是访问网页、提取数据并存储,以便后续分析或展示。爬虫通常由搜索引擎、数据挖掘工具、监测系统等应用于网络数据抓取的场景。 爬虫的工作流程包括以下几个关键步骤: URL收集: 爬虫从一个或多个初始URL开始,递归或迭代地发现新的URL,构建一个URL队列。这些URL可以通过链接分析、站点地图、搜索引擎等方式获取。 请求网页: 爬虫使用HTTP或其他协议向目标URL发起请求,获取网页的HTML内容。这通常通过HTTP请求库实现,如Python中的Requests库。 解析内容: 爬虫对获取的HTML进行解析,提取有用的信息。常用的解析工具有正则表达式、XPath、Beautiful Soup等。这些工具帮助爬虫定位和提取目标数据,如文本、图片、链接等。 数据存储: 爬虫将提取的数据存储到数据库、文件或其他存储介质中,以备后续分析或展示。常用的存储形式包括关系型数据库、NoSQL数据库、JSON文件等。 遵守规则: 为避免对网站造成过大负担或触发反爬虫机制,爬虫需要遵守网站的robots.txt协议,限制访问频率和深度,并模拟人类访问行为,如设置User-Agent。 反爬虫应对: 由于爬虫的存在,一些网站采取了反爬虫措施,如验证码、IP封锁等。爬虫工程师需要设计相应的策略来应对这些挑战。 爬虫在各个领域都有广泛的应用,包括搜索引擎索引、数据挖掘、价格监测、新闻聚合等。然而,使用爬虫需要遵守法律和伦理规范,尊重网站的使用政策,并确保对被访问网站的服务器负责。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周小董

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值