精华内容
下载资源
问答
  • 最近发现面试题热度 挺好的,不过大家博客都只有面试题,从来都不答案,顺手就码了点收集到的博客问题的答案 共69道,2W,耗时两天(疯狂暗示)欢迎催更吹水,来一个人就是一份催更动力点击并输入暗号:CSDN ...

    最近发现面试题热度 挺好的,不过大家博客都只有面试题,从来都不带答案,顺手就码了点收集到的博客问题的答案

    共69道,2W字,耗时两天(疯狂暗示)在这里插入图片描述欢迎催更吹水,来一个人就是一份催更动力点击并输入暗号:CSDN

    目录

    1. 按你的理解,软件接口是什么?

    答:
    就是指程序中具体负责在不同模块之间传输或接受数据的并做处理的类或者函数。

    2.HTTP 和 HTTPS 协议区别?

    答:
    https 协议需要到 CA(Certificate Authority,证书颁发机构)申请证书,一般免费证书
    较少,因而需要一定费用;
    http 是超文本传输协议,信息是明文传输,Https 协议是由 SSL+Http 协议构建的可进行加
    密传输、身份认证的网络协议,比 http 协议安全;
    http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443;

    3.HTTPS 在哪一层?

    以前我面试很喜欢提网络协议的问题,有朋友说我装 X,不实用。稍有点研究网络知识,实
    际就不难回答
    答:HTTPS 在应用层。

    4.get 和 post 区别是什么?

    答:POST 和 GET 都是向服务器提交数据,并且都会从服务器获取数据。
    区别:
    1)传送方式:get 通过地址栏传输,post 通过报文传输
    2)传送长度:get 参数有长度限制(受限于 url 长度),而 post 无限制
    3)GET 产生一个 TCP 数据包(对于 GET 方式的请求,浏览器会把 http header 和 data 一并
    发送出去,服务器响应 200 返回数据),POST 产生两个 TCP 数据包(对于 POST,浏览器先
    发送 header,服务器响应 100 continue,浏览器再发送 data,服务器响应 200 ok 返回数
    据)
    4)get 请求参数会被完整保留在浏览历史记录里,而 post 中的参数不会被保留
    5)在做数据查询时,建议用 GET 方式;而在做数据添加、修改或删除时,建议用 post 方式

    5.常见的 POST 提交数据方式

    答:
    主要有四种方式:application/x-www-form-urlencoded、multipart/form-data、 application/json、text/xml 等。

    6.什么是 Http 协议无状态协议?怎么解决 HTTP 协议无状态协议

    答:
    无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。
    即我们给服 务器发送 HTTP 请求之后,服务器根据请求,会给我们发送数据过来,但是,发送完,不会 记录任何信息。
    HTTP 是一个无状态协议,这意味着每个请求都是独立的,Keep-Alive 没能 改变这个结果。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导 致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
    HTTP 协议这种特性有优点也有缺点,优点在于解放了服务器,每一次请求“点到为止”不会造成 不必要连接占用,缺点在于每次请求会传输大量重复的内容信息。
    客户端与服务器进行动态 交互的 Web 应用程序出现之后,HTTP 无状态的特性严重阻碍了这些应用程序的实现,毕竟
    交互是需要承前启后的,简单的购物车程序也要知道用户到底在之前选择了什么商品。
    于是, 两种用于保持 HTTP 连接状态的技术就应运而生了,一个是 Cookie,而另一个则是Session。

    7.cookie 和 session 的区别

    答:
    cookie 数据存放在客户的浏览器上,session 数据放在服务器上 cookie 不是很安全,别人可以分析存放在本地的 cookie 并进行 cookie 欺骗,考虑到安全 应当使用 session

    session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑 到减轻服务器性能方面应当使用 cookie

    单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个 cookie 可以将登陆信息等重要信息存放为 session;其他信息需要保存,可以放在 cookie

    8.请求接口中常见的返回状态码

    答:1xx – 信息提示(表示临时的响应。客户端在收到常规响应之前,准备接收一个或多个 1xx
    响应)
    2xx – 成功(表明服务器成功地接受了客户端请求)
    3xx – 重定向(客户端浏览器必须采取更多操作来实现请求。例如,浏览器可能不得不请
    求服务器上的不同的页面,或通过代理服务器重复该请求)
    4xx – 客户端错误(发送错误,客户端有问题。例如,客户端请求不存在的页面,客户端
    未提供有效的身份证验证信息)
    5xx – 服务器错误(服务器由于遇到错误而不能完成该请求)
    常见的返回码有:
    200 OK - [GET]:服务器成功返回用户请求的数据
    201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功
    202 Aceepted - []:表示一个请求已经进入后台排队(异步任务)
    204 NO CONTENT - [DELETE]:用户删除数据成功
    400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行 新建或修改数据的操作
    401 Unauthorized -[
    ] :表示用户没有权限(令牌、用户名、密码错误)
    403 Forbidden -[] :表示用户得到授权(与 401 错误相对),但是访问被禁止
    404 NOT FOUND -[
    ]:用户发出的请求针对得到是不存在的记录,服务器没有进行操作, 该操作是幂等的
    406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求 JSON 格式,但 是只有 XML 格式)
    500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是

    9.什么是 DNS?

    答:DNS 是域名系统 (Domain Name System),DNS 是用来做域名解析的,它会在你上网输入
    网址后,把它转换成 IP,然后去访问对方服务器;没有它,你想上百度就要记住百度的 IP,
    但有了 DNS 的处理,你只需要记住对应网站的域名,即网址就可以了。

    10.请问你们公司是如何做接口测试的?

    答:
    接口测试实际跟一般测试不同就是测试用例的设计部分。
    ①获取接口规范。
    ②设计接口测试功能用例(主要从用户角度出发看接口能否实现业务需求,用例设计就是黑
    盒用例那一套)。
    ③各种入参验证(正常情况,异常情况包括输入参数个数不对,类型不对,可选/必选,还 有考虑参数有互斥或关联的情况)。
    ④接口返回值各种验证(符合接口文档需求)
    ⑤了解接口实现逻辑,实现逻辑覆盖(语句/条件/分支/判定/…)
    ⑥接口能并发执行吗、安全吗,性能满足要求吗?
    ⑦采用工具或者自写代码来验证。
    ⑧发现问题跟功能测试一样,该报 bug 报 bug,该跟踪状态的跟踪状态。

    11.怎么设计接口测试用例?

    答:
    通常,设计接口测试用例需要考虑以下几个方面:
    ①是否满足前提条件
    有些接口需要满足前提,才可成功获取数据。常见的,需要登录 Token
    逆向用例:针对是否满足前置条件(假设为 n 个条件),设计 0~n 条用例
    ②是否携带默认值参数
    正向用例:带默认值的参数都不填写、不传参,必填参数都填写正确且存在的“常规”值,
    其他不填写,设计 1 条用例
    ③业务规则、功能需求
    这里根据时间情况,结合接口参数说明,可能需要设计 N 条正向用例和逆向用例
    ④参数是否必填
    逆向用例:针对每个必填参数,都设计 1 条参数值为空的逆向用例
    ⑤参数之间是否存在关联
    有些参数彼此之间存在相互制约的关系
    ⑥参数数据类型限制
    逆向用例:针对每个参数都设计 1 条参数值类型不符的逆向用例
    ⑦参数数据类型自身的数据范围值限制
    正向用例:针对所有参数,设计 1 条每个参数的参数值在数据范围内为最大值的正向用例

    12.你做接口测试,测什么?

    答:
    可用性测试
    根据约定的协议、方法、格式内容,传输数据到接口经处理后返回期望的结果:
    接口功能是否正确实现;
    返回值测试 - 返回值除了内容要正确,类型也要正确,保证调用方能够正确地解析;
    参数值边界值、等价类测试;
    错误和异常处理测试
    输入异常值(空值、特殊字符、超过约定长度等),接口能正确处理,且按预期响应;
    输入错误的参数,接口能正确处理,并按预期响应;
    多输入、少输入参数,接口能正确处理,且按预期响应;
    错误传输数据格式(如 json 格式写成 form 格式)测试;
    安全性测试,主要指传输数据的安全性:
    敏感数据(如密码、秘钥)等是否加密传输;
    返回数据是否含有敏感数据,如用户密码、完整的用户银行账号信息等;
    接口是否对传入的数据做安全校验,如身份 ID 加 token 类似校验;
    接口是否防止恶意请求(如大量伪造请求接口致使服务器崩溃);
    性能测试,如接口的响应时间、并发处理能力、压测处理情况:
    并发请求相同的接口(特别为 POST 请求),接口的处理情况(如插入了相同的记录导致 数据出错,引发系统故障);

    接口响应时长在用户可忍受的范围内;
    对于请求量大的接口做压测,确定最大的瓶颈点是否满足当前业务需要;

    13.平常用什么工具测接口的?

    答:常用 http 协议接口测试工具,如:postman、fiddler、jmeter;webService 接口用 SoapUI、
    jmeter 等。

    14.没有接口文档,如果做接口测试?

    本题主要考情商,通俗来说就是忽悠能力,先唬住面试官了再说,进去了也是瞎测测,随时 做好背锅的准备,当然,你肯定不能回答面试官不测(心理 mmp,脸上笑嘻嘻),接下来就是 扯犊子时间
    答:用抓包工具把接口抓取处理,然后针对性进行测试;接口中字段信息不清楚的,找时间
    集中寻求开发解答。(常用抓包工具 Fiddler、Charles 等)

    15.在手工接口测试或者自动化接口测试的过程中,上下游接口有数据依赖如何处理?

    答:用一个全局变量来处理依赖的数据,比如登录后返回 token,其它接口都需要这个 token,
    那就用全局变量来传 token 参数。

    16.依赖于第三方数据的接口如何进行测试?

    答:mock
    接着面试官会问你,如果 mock 的,然后你就顺着坑继续挖,搭建 mock 服务,参考这篇
    http://www.51ste.com/share/det-485.html

    17.接口测试中,依赖登录状态的接口如何测试?

    答:依赖登录状态的接口的本质上是在每次发送请求时需要带上 session 或者 cookie 才能
    发送成功,在构建 POST 请求时添加必要的 session 或者 cookie

    18.如何模拟弱网做测试?

    答:Fiddler 和 charles 都可以模拟弱网测试,平常说的模拟丢包,也是模拟弱网测试。具
    体可以看《几种弱网模拟方法,总有一种适合你》

    19.你平常做接口测试的过程中发现过哪些 bug?

    面试官出这个题,主要是想知道你是不是真的做过接口测试,毕竟现在很多小伙伴简历经过 包装(不包装连面试机会都没有,没办法,为了生存,能理解)
    答:
    常规错误,接口没实现,没按约定返回结果,边界值处理出错等。
    输入异常值(空值、特殊字符、超过约定长度等),接口抛错,没做封装处理;
    输入错误的参数、多输入、少输入参数,接口可能出现的错误;
    安全性问题,如明文传输、返回结果含有敏感信息,没对用户身份信息做校验,没做恶意请 求拦截等;
    性能问题,如接口并发插入多条相同操作,响应时间过长,接口压测出现瓶颈等;

    20.当一个接口出现异常时候,你是如何分析异常的?

    答:
    先抓包,用 fiddler(charles)工具抓包,或者浏览器上 F12 调试工具;APP 上的话,那就 用 Fiddler 做代理,通过手机设置代理去看请求和返回报文;
    查看后端日志,如 Linux 系统通过 xhell 连上服务器,查看接口日志,查看是否有报错信息 (命令:tail -f 日志文件);

    21.如何分析一个 bug 是前端还是后端的?

    答:
    平常提 bug 的时候,前端开发和后端开发总是扯皮,不承认是对方的 bug。
    这种情况很容易判断,先抓包看请求报文,对着接口文档,看请求报文有没问题,有问题就 是前端发的数据不对;
    请求报文没问题,那就看返回报文,返回的数据不对,那就是后端开发的问题咯。

    22.你们做接口测试自动化吗?

    答:现在针对大量应用,普遍推崇做接口测试自动化,维护成本低、收益高。常用的工具有 许多,如 Jmeter、Robot Framework、pytest 等。

    23.列出几个 JMeter 监听器?

    一些 JMeter 监听器是:
    集合报告
    汇总报告
    查看结果树
    用表格查看结果
    图形结果
    BeanShell Listener
    摘要报告等

    24.在 python 中进行数据驱动测试

    在 unittest 中,没有自带的数据驱动,我们得借助 ddt 来实现,首先,我们得在 python 运行环境中安装 ddt,用下列命令安装 pip install ddt 另 外 一 个 测 试 框 架 pytest , 它自带数据驱动实现,是通过@pytest.mark.parametrize(argnames,argvalues) 来实现参数化的。
    也可以根据自己需求用 python 实现数据的读取和驱动。

    25.接口自动化中的关联怎么处理?

    把上一个请求返回的结果传入到下一个请求的参数中,将请求的结果反射到一个类属性(使 用 setattr()函数),下一个请求去调用这个类属性

    26.自动化测试怎么校验结果?

    断言 ,预期结果与实际结果对比
    数据库校验,根据测试场景来查询数据库里的数据和请求之前的数据进行比对

    27.自动化使用的测试框架是什么?简述自动化框架的设计、维护

    测试框架:python+unittest+requests+ddt+openpyxl+pymysql+logging
    python:入门简单,语法简洁
    unittest :定义一个测试用例类,具体的方法来维护测试用例的生命周期,测试场景行为,
    测试用例 前置场景,行为,期望结果,实际结果,断言方法,Setup teardown 方法
    requests:接口调用 ,支持 http 请求的库,API 简洁,提供不同的 http 请求方法,支持 session,cookies,
    ddt :数据驱动,ddt 类装饰器,data 测试方法装饰器 unpack 解包可迭代的数据类型 普通用户,数据库,配置文件—(基础数据)
    openpyxl: 数据管理 excel 管理数据,使用 openpyxl 模块来进行 excel 数据的读和写 (excle,csv, json, yaml, txt 都可以管理测试数据)
    pymysql:数据库交互,数据校验
    eval,json:数据格式的转换 Eval 将 python 支持的格式转换成对应的格式
    logging:日志处理, 统一日志输出格式,渠道,级别,执行结果的记录,便于定位问题
    jenkins:持续集成
    2/框架设计思路:数据驱动+结构分层(可读性,可维护性,可扩展性)
    数据驱动:将维护数据与代码分离,接口调用行为一致,针对不同的参数组合驱动不同的测 试场景,减少代码冗余
    结构分层:数据层+用例层+逻辑层
    数据层:测试数据的支撑 data.xls
    用例层:用例的执行 test_register.py test_recharge.py
    逻辑层:公用的方法的封装与提取 doexcle.py do_mysql.py http_requests.py logger.py 等模块
    3/框架设计步骤:
    准备测试数据: EXCEL 表准备测试用例—excel 数据的读取—参数值的替换
    发起请求:请求方法(get/post 方法进行封装—URL 的拼接(不同—参数转化为字典 拿到请求的返回值:解析返回值 code,status,msg 信息)
    断言
    好处:
    1、自动化测试用例和手工测试用例的完美结合,减少重复工作
    2、配置灵活,可以自主切换测试环境,执行测试用例
    3、常用功能进行封装,逻辑清晰,易于维护
    4、统一执行入口,管理测试用例集:
    run.py 模块通过模糊查找来选择需要执行的测试用例
    5、持续集成,定时构建,快速反馈

    28.具体的在这个项目中自动化怎么应用到实际的,您对自动化结果的分析

    完成所有的自动化测试框架的设计和实现后,进行接口测试,然后集成到 jenkins,配置定时执行,生成 html 报表,查看测试通过率,查看接口的功能
    每次发版时,进行回归测试,新功能开发未提测前

    29.什么是 HTTP 请求,HTTP 请求方式有哪些

    答:HTTP,即超文本传输协议,是 HyperText Transfer Protocol 的缩写。
    浏览网页时在浏览器地址栏中输入的 URL 前面都是以"http://"开始的。HTTP 定 义了信息如何被格式化、如何被传输,以及在各种命令下服务器和浏览器所采取 的响应。
    HTTP 请求方式:GET、POST、PUT、HEAD、DELETE、OPTIONS、 TRACE、CONNECT

    30.常用工具的使用,postman,jmeter,Fiddler

    (此处常用工具的使用是检查一名测试人员基本技能的基础,也是项目组最为关
    注的点,这里只简单介绍操作步骤,候选人若能答出流程即可)
    Postman:postman 是一个开源的接口测试工具,无论是做单个接口的测试
    还是整套测试脚本的拨测都非常方便。
    1)get 请求:Get 请求是最简单的请求方式,输入 URL 就能完成。第一步:新建一个 tab 页面
    第二步:输入 URL ,选择请求方式为 GET
    第三步:点击“send”按钮
    第四步:查看返回码是否异常。
    2)post 请求: Post 请求跟 Get 的区别除了请求方式不同之外, 还需要添加请求体,请求体内容多半为 Json 格式。
    第一步:新建一个 tab 页面
    第二步:输入 URL ,选择请求方式为 POST
    第三步:输入请求体内容
    第四步:点击“send”按钮
    第五步:查看返回码,返回信息等
    3)带 cookie 的请求: 该请求需要在 Heards 里面添加 Cookie
    第一步:新建一个 tab 页面
    第二步:输入 URL ,选择请求方式为 POST
    第三步:输入请求体内容
    第四步:在 Heard 里面添加 Cookie 信息
    第五步:点击“send”按钮
    第六步:查看返回码,返回信息等
    4)带 Header 的请求:该请求需要在 Heards 里面添加 Cookie。
    第一步:新建一个 tab 页面
    第二步:输入 URL ,选择请求方式为 POST
    第三步:输入请求体内容第四步:在 Heard 里面对应的内容
    第五步:点击“send”按钮
    第六步:查看返回码,返回信息等
    5)文件上传的请求:发送请求前需要先上传文件。
    第一步:新建一个 tab 页面
    第二步:输入 URL ,选择请求方式为 POST
    第三步:输入请求体内容,文件内容选择 file, 选择本地的文件上传
    第四步:点击“send”按钮
    第五步:查看返回码,返回信息等
    Jmeter:Apache JMeter 是 Apache 组织开发的基于 Java 的压力测试工具。用 于对软件做压力测试,它最初被设计用于 Web 应用测试,但后来扩展到其 他测试领域。它可以用于测试静态和动态资源,例如静态文件、Java 小服务 程序、CGI 脚本、Java 对象、数据库、FTP 服务器, 等等。
    操作步骤如下:
    在这里插入图片描述

    首先,添加线程组,单击右键->添加->Threads(Users)->线程组

    右键“线程组” -> “添加” -> “取样器” -> “HTTP 请求”, 输入“服务器名称或 IP”,对应的端口号,http 默认端口号 80,可以不写。
    以下请求为 POST, 所有“方法”那选择“POST”,输入对应的路径,添加 参数及值。
    在这里插入图片描述

    右键“线程组” -> “添加” -> “监听器” -> “察看结果数”, 添加“察
    看结果数”,以察看运行后的结果,如图所示。
    在这里插入图片描述

    数据补充完整后可点击菜单栏目中“启动”按钮以启动测试
    简单来说,jmeter 操作过程就是:
    1、新建一个线程组
    2、输入 HTTP 请求
    3、设置一个或者多个断言,增加监听器
    4、运行后查看结果树

    31.你对http请求跟webservice请求的了解

    1、webService接口:是走soap协议通过http传输,请求报文和返回报文都是xml格式的,我们在测试的时候都用通过工具才能进行调用,测试。可以使用的工具有SoapUI、jmeter、loadrunner等;

    2、http api接口:是走http协议,通过路径来区分调用的方法,请求报文都是key-value形式的,返回报文一般都是json串,有get和post等方法,这也是最常用的两种请求方式。可以使用的工具有postman、RESTClient、jmeter、loadrunner等

    32.接口测试为你什么要参数化

    举个例子,例如购物车接口请求数据需要登录接口里面返回的token值,这个存在接口之间依赖关系的时候,我们需要把依赖字段值进行参数化
    再比如接口中会有一些公共参数,每个接口里面都存在,如果值是固定的,那我们可以把这些进行参数化记录,减少在每个接口里面书写出错的问题
    再比如手机号,我传131开头的、133开头的、135开头的,如果不会参数化,就要写三个http请求,分别传这三种参数,学会了参数化,只写一个http请求就够了,以此来减少重复的工作

    33描述一下Http协议

    http协议又叫做超文本传输协议,在做网络请求的时候,我们基本上是使用http协议。
    http协议包括请求和响应。
    请求中包括:请求地址,请求方式,请求方式包括get请求和post请求,get和post的区别是get请求是在地址栏后边跟随请求参数,但是请求参数大小是有限制,不同浏览器是不同的。一般是4KB。post请求主要用于向服务器提交请求参数。post请求的参数是放到请求实体内容中的,相对get请求较为安全一些。

    另外,请求中会有各种请求头信息,比如支持的数据类型,请求的来源位置,以及Cookie头等相关头信息。

    响应,主要包含响应的状态码,像200(),404(),500(),304(),307()
    还有各种响应头信息,比如设置缓存的响应头,Content-Type内容类型,设置cookie头信息。

    35.请详细阐述接口测试和UI测试在测试活动中是如何协同测试的?

    接口测试和UI测试这两块其实是有一部分是重叠的,UI测试是通过前端写的界面,来调用接口,而接口测试是直接调接口。所以排除前端的处理的逻辑和调用的正确性,在理论上接口测试是可以覆盖所有的UI测试。但实际过程中,如果只是在接口层覆盖所有的业务流,在UI上只测试前端的逻辑,最终的结果可能会是忽视很多原有的功能点,导致了UI测试的不充分。所以存在多人分工且时间充分的时候可以尝试接口去做业务流的全覆盖,否则不要轻易尝试。

    36.系统间的接口联调测试

    例如:两个系统之间的部分数据是相互读取的

    1. 在一个甲系统增加,修改A数据后,乙系统也会相应的呈现这个改动的数据;在乙系统增加,修改B数据后,甲系统也会相应的呈现这个改动的数据;
      即A部分的数据,是由甲系统来维护的,乙系统读入数据并同步;B部分的数据,是由乙系统来维护的,甲系统读取数据并同步;
    2. 在具体的操作过程中,在甲系统增加,修改A数据后,然后在乙系统查看对应的数据是否同步一致;反之亦然;
      在乙系统查看对应的数据是否同步一致?分为三个层面;

    (1)甲系统传输过来的数据和乙系统接收到的数据是否一致;
    (2)乙系统接收到数据后,会存入到自己的数据库,这个存储过程是否成功?(主要是考虑到甲系统传过来的数据格式是否和乙系统的格式一致)且数据存储成功与否乙系统会返回一条信息(例如:返回1,表示数据正确传输并存储到数据库了;返回0及错误信息表示数据传输或存储出了问题)
    (3)然后在乙系统的界面查看,新增或修改的数据是否和接收到的数据一致;

    37.参数化具体用在哪些路径上?

    凡是请求,基本都要带参数,带参数,就有可能进行参数化。
    购物车:带参数,买的什么商品,买了几个。
    搜索,关键词,排序方式,类别,热度,价格区间
    分类:参数信息参数化

    38.你们公司接口中常见的错误码及含义,及返回信息参数有哪些等,举例说明

    名称说明编码
    1操作成功
    2操作失败
    33参数解析失败一般指json格式错误
    44订单重复提交(会返回提交成功时的承运信息)
    401身份安全验证失败
    500系统错误
    999需要登录

    返回值为json格式,如下:

    {
        "status": 1,
    "message": "处理成功",    
        "result": "请求结果",
        "totalRow": 100,
    "totalPage": 5
    }
    

    status 1表示成功,
    message 提示信息 (必有,字符串型)
    result 详细结果 (可选,类型各接口不同,相见接口内定义)
    totalRow 总记录条数,用于分页使用 (可选,整型)
    totalPage 总页数 (可选,整型)

    39. 常见接口:

    http api接口:是走http协议,通过路径来区分调用的方法,请求报文都是key-value形式的,返回报文一般都是json串,有get和post等方法,这也是最常用的两种请求方式。可以使用的工具有postman、RESTClient、jmeter、loadrunner等;

    40.接口组成

    接口都有那些部分组成呢?
    首先,接口文档应该包含以下内容:
    1、接口说明
    2、调用url
    3、请求方法(get\post)
    4、请求参数、参数类型、请求参数说明
    5、返回参数说明
      由接口文档可知,接口至少应有请求地址、请求方法、请求参数(入参和出参)组成,部分接口有请求头header。
      标头 (header):是服务器以HTTP协议传HTML资料到浏览器前所送出的字串,在标头与 HTML 文件之间尚需空一行分隔,一般存放cookie、token等信息
      有同学问我header和入参有什么关系?它们不都是发送到服务器的参数吗?
    首先,它们确实都是发送到服务器里的参数,但它们是有区别的,header里存放的参数一般存放的是一些校验信息,比如cookie,它是为了校验这个请求是否有权限请求服务器,如果有,它才能请求服务器,然后把请求地址连同入参一起发送到服务器,然后服务器会根据地址和入参来返回出参。也就是说,服务器是先接受header信息进行判断该请求是否有权限请求,判断有权限后,才会接受请求地址和入参的。

    41.为什么要做接口测试:

    大家都知道,接口其实就是前端页面或APP等调用与后端做交互用的,所以好多人都会问,我功能测试都测好了,为什么还要测接口呢?OK,在回答这个问题之前,先举个栗子:
      比如测试用户注册功能,规定用户名为6~18个字符,包含字母(区分大小写)、数字、下划线。首先功能测试时肯定会对用户名规则进行测试时,比如输入20个字符、输入特殊字符等,但这些可能只是在前端做了校验,后端可能没做校验,如果有人通过抓包绕过前端校验直接发送到后端怎么办呢?试想一下,如果用户名和密码未在后端做校验,而有人又绕过前端校验的话,那用户名和密码不就可以随便输了吗?如果是登录可能会通过SQL注入等手段来随意登录,甚至可以获取管理员权限,那这样不是很恐怖?
    所以,接口测试的必要性就体现出来了:
    ①、可以发现很多在页面上操作发现不了的bug
    ②、检查系统的异常处理能力
    ③、检查系统的安全性、稳定性
    ④、前端随便变,接口测好了,后端不用变

    42.GET请求和POST请求的区别:

    1、GET使用URL或Cookie传参。而POST将数据放在BODY中。
    2、GET的URL会有长度上的限制,则POST的数据则可以非常大。
    3、POST比GET安全,因为数据在地址栏上不可见。
    4、一般get请求用来获取数据,post请求用来发送数据。
    其实上面这几点,只有最后一点说的是比较靠谱的,第一点post请求也可以把数据放到url里面,get请求其实也没长度限制,post请求看起来参数是隐式的,稍微安全那么一些些,但是那只是对于小白用户来说的,就算post请求,你通过抓包也是可以抓到参数的。所以上面这些面试的时候你说出来就行了。

    43.常见状态码:

    每发出一个http请求之后,都会有一个响应,http本身会有一个状态码,来标示这个请求是否成功,常见的状态码有以下几种:
    1、2XX 2开头的都表示这个请求发送成功,最常见的就是200,就代表这个请求是ok的,服务器也返回了。
    2、3XX 3开头的代表重定向,最常见的是302,把这个请求重定向到别的地方了,
    3、4XX 400代表客户端发送的请求有语法错误,401代表访问的页面没有授权,403表示没有权限访问这个页面,404代表没有这个页面
    4、5xx: 代表服务器有异常,500代表服务器内部异常;503服务器当前不能处理客户端的请求,一段时间后可能恢复正常;504代表服务器端超时,没返回结果。

    44.通用接口用例设计

    ①、通过性验证:首先肯定要保证这个接口功能是好使的,也就是正常的通过性测试,按照接口文档上的参数,正常传入,是否可以返回正确的结果。
    ②、参数组合:现在有一个操作商品的接口,有个字段type,传1的时候代表修改商品,商品id、商品名称、价格有一个是必传的,type传2的时候是删除商品,商品id是必传的,这样就要测参数组合了,type传1的时候,只传商品名称能不能修改成功,id、名称、价格都传的时候能不能修改成功。
    ③、接口安全:
    1、绕过验证,比如说购买了一个商品,它的价格是300元,那我在提交订单时候,我把这个商品的价格改成3元,后端有没有做验证,更狠点,我把钱改成-3,是不是我的余额还要增加?
    2、绕过身份授权,比如说修改商品信息接口,那必须得是卖家才能修改,那我传一个普通用户,能不能修改成功,我传一个其他的卖家能不能修改成功
    3、参数是否加密,比如说我登陆的接口,用户名和密码是不是加密,如果不加密的话,别人拦截到你的请求,就能获取到你的信息了,加密规则是否容易破解。
    4、密码安全规则,密码的复杂程度校验
    ④、异常验证:
      所谓异常验证,也就是我不按照你接口文档上的要求输入参数,来验证接口对异常情况的校验。比如说必填的参数不填,输入整数类型的,传入字符串类型,长度是10的,传11,总之就是你说怎么来,我就不怎么来,其实也就这三种,必传非必传、参数类型、入参长度。

    45.根据业务逻辑来设计用例

    根据业务逻辑来设计的话,就是根据自己系统的业务来设计用例,这个每个公司的业务不一样,就得具体的看自己公司的业务了,其实这也和功能测试设计用例是  一样的。
    举个例子,拿bbs来说,bbs的需求是这样的:
    1、登录失败5次,就需要等待15分钟之后再登录
    2、新注册的用户需要过了实习期才能发帖
    3、删除帖子扣除积分
    4、…
    像这样的你就要把这些测试点列出来,然后再去造数据测试对应的测试点。

    46.用什么工具测

    接口测试的工具很多,比如 postman、RESTClient、jmeter、loadrunner、SoapUI等,本人首推的测试工具是postman和jmeter

    47.接口测试用例模板 (可根据项目实际情况设计增减)

    1、项目 测试针对哪个项目
    2、模块 哪个功能模块
    3、用例id
    4、接口名称
    5、用例标题 测试用途概括
    6、请求方式 GET/POST
    7、请求url URL地址
    8、请求参数
    9、前置条件 执行当前请求依赖的条件,不满足就不能正确执行
    10、结果验证 预期结果
    11、请求报文 可以不写
    12、返回报文  一定要写,这里应该是你请求返回的真实结果
    13、测试结果 通过/失败
    14、测试人员

    48.接口测试注意事项

    测试的时候这几个方面:
    改变请求参数,看响应结果是否和接口文档一致
    查看参数是否有敏感信息(比如个人账户信息,资金信息)
    查看是否对关键参数进行加密处理(密码信息)
    所有列表页接口必须考虑排序值
    接口返回的图片地址能否打开,图片尺寸是否符合需求;
    接口有翻页时,页码与页数的异常值测试;
    当输出参数有联动性时,需要校验返回两参数的实际结果是否都符合需求
    每个接口入参的默认值、异常类型、非空校验
    入参支持多个值时,要考虑传的值的个数多的情况下,接口会不会报错

    49.浏览器输入 url 按回车背后经历了哪些?

    在 PC 浏览器的地址栏输入一串 URL,然后按 Enter 键这个页面渲染出来,这
    个过程中都发生了什么事?
    1、首先,在浏览器地址栏中输入 url,先解析 url,检测 url 地址是否合法
    2、浏览器先查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有,会直接在
    屏幕中显示页面内容。若没有,则跳到第三步操作。
    浏览器缓存:浏览器会记录 DNS 一段时间,因此,只是第一个地方解析 DNS 请求;
    操作系统缓存:如果在浏览器缓存中不包含这个记录,则会使系统调用操作系统,
    获取操作系统的记录(保存最近的 DNS 查询缓存);
    路由器缓存:如果上述两个步骤均不能成功获取 DNS 记录,继续搜索路由器缓存;
    ISP 缓存:若上述均失败,继续向 ISP 搜索。
    3、在发送 http 请求前,需要域名解析(DNS 解析),解析获取相应的 IP 地址。
    4、浏览器向服务器发起 tcp 连接,与浏览器建立 tcp 三次握手。
    5、握手成功后,浏览器向服务器发送 http 请求,请求数据包。
    6、服务器处理收到的请求,将数据返回至浏览器
    7、浏览器收到 HTTP 响应
    8、浏览器解码响应,如果响应可以缓存,则存入缓存。
    9、 浏览器发送请求获取嵌入在 HTML 中的资源(html,css,javascript,图片,
    音乐······),对于未知类型,会弹出对话框。
    10、 浏览器发送异步请求。
    11、页面全部渲染结束。

    50.cookies 机制和 session 机制的区别

    cookies 机制和 session 机制的区别,这个也是经常会问的
    cookies 数据保存在客户端,session 数据保存在服务器端;
    cookies 可以减轻服务器压力,但是不安全,容易进行 cookies 欺骗;
    session 较安全,但占用服务器资源

    51.http 协议请求方式

    http 协议有哪几种请求方式?
    GET, POST 和 HEAD 方、OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。

    52.报文格式

    HTTP 请求报文与响应报文格式
    请求报文包含三部分:
    a、请求行:包含请求方法、URI、HTTP 版本信息
    b、请求头部(headers)字段
    c、请求内容实体(body)
    响应报文包含三部分:
    a、状态行:包含 HTTP 版本、状态码、状态码的原因短语
    b、响应头部(headers)字段
    c、响应内容(body)实体

    53.什么是 DNS?

    域名解析服务。将主机名转换为 IP 地址。如将 http://www.cnblogs.com/主机
    名转换为 IP 地址:211.137.51.78

    54.什么是 Http 协议无状态协议?怎么解决 Http 协议无状态协议?

    (1)、无状态协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需
    要前面的信息
    (2)、无状态协议解决办法: 通过 1、Cookie 2、通过 Session 会话保存。

    55.webService 接口是如何测试的

    webService 接口用 SoapUI
    在这里插入图片描述

    56.在手工接口测试或者自动化接口测试的过程中,上下游接口有数据依赖如何处理?

    用一个全局变量来处理依赖的数据,比如登录后返回 token,其它接口都需要这
    个 token,那就用全局变量来传 token 参数
    依赖第三方

    57.json 和字典 dict 的区别?

    现在自动化培训烂大街,是个人都能说的上几个框架,面试如果问框架相关问题,
    求职者只需一瓶 82 年的雪碧,会吹的让你怀疑人生!
    所以面试官为了更清楚的知道你是停留在表面上的花拳绣腿还是有扎实的基础,
    就不会问框架这种东西了。基本上问几个数据类型的基础就知道有没货了。
    那么 json 和字典到底有什么区别呢?初学者连 python 的基础数据类型都没搞清
    楚,直接撸框架,有的人学了几个月可能都迷迷糊糊的,以为 json 就是字典。
    这个是肯定不对的。
    首先 python 里面的基础数据类型有:int、str、 float、list、bool、tuple、
    dict、set 这几种类型,里面没 json 这种数据类型。
    JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格
    式。它基于 ECMAScript (欧洲计算机协会制定的 js 规范)的一个子集,采用完
    全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得
    JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生
    成,并有效地提升网络传输效率。
    由于你的代码是 python 写的(也有可能是 php,java,c,ruby 等语言),但是后
    端接口是 java 写的(也有可能是其它语言),不同的 语言数据类型是不一样的
    (就好比中国的语言和美国的语言数据类型也不一样,中国的一般说一只羊,一
    头牛,美国都是 a /an 这种单位),所以就导致你提交的数据,别的开发语言
    无法识别,这就需要规范传输的数据(传输的数据都是一个字符串),大家都遵
    循一个规范,按一个标 准的格式去传输,于是就有就 json 这种国际化规范的数
    据类型。
    json 本质上还是字符串,只是按 key:value 这种键值对的格式来的字符串

    import json 
    # a 是字典 dict 
    a = {"a": 1, "b": 2, "c": True} 
    # b 是 json 
    b = '{"a": 1, "b": 2, "c": true}' 
    print(type(a)) 
    print(json.dumps(a)) # a 转 json 
    运行结果 
    <class 'dict'> 
    {"a": 1, "b": 2, "c": true} 
    <class 'str'> 
    {'a': 1, 'b': 2, 'c': True} 
    

    58.测试的数据你放在哪?

    测试数据到底该怎么放,这个是面试官最喜欢问的一个题了,似乎仁者见仁智者
    见智,没有标准的答案,有的人说放 excel,也有的说放.py 脚本,也有的说放
    ini 配置文件,
    还有放到 json,yaml 文件,txt 文件,甚至有的放数据库,五花八门,一百个
    做自动化的小伙伴有 100 个放的地方。
    这里总结下测试的数据到底该怎么放?
    首先测试的数据是分很多种的,有登录的账户数据,也有注册的账户数据,还有
    接口的参数,还有邮箱配置的数据等等等等,所以这个题不能一概而论给答死了。
    要不然就是给自己挖坑。
    以下两个大忌不能回答:

    • 测试的数据是不能写死到代码里面的,这个是原则问题,也是写代码的大 忌(你要是回答写在代码里面,估计就是回去等通知了)
    • 测试数据放到.py 的开头,这种其实很方便,对于少量的,固定不变的数 据其实是可以放的,但是面试时候,千万不能这样说,面试官喜欢装逼的 方法

    测试数据存放总结:
    1.对于账号密码,这种管全局的参数,可以用命令行参数,单独抽出来,写的配 置文件里(如 ini)
    2.对于一些一次性消耗的数据,比如注册,每次注册不一样的数,可以用随机函 数生成
    3.对于一个接口有多组测试的参数,可以参数化,数据放 yaml,text,json,excel 都可以
    4.对于可以反复使用的数据,比如订单的各种状态需要造数据的情况,可以放到 数据库,每次数据初始化,用完后再清理
    5.对于邮箱配置的一些参数,可以用 ini 配置文件
    6.对于全部是独立的接口项目,可以用数据驱动方式,用 excel/csv 管理测试的 接口数据
    7.对于少量的静态数据,比如一个接口的测试数据,也就 2-3 组,可以写到 py 脚本的开头,十年八年都不会变更的
    总之不同的测试数据,可以用不同的文件管理

    59.什么是数据驱动,如何参数化?

    参数化和数据驱动的概念这个肯定要知道的,参数化的思想是代码用例写好了后,
    不需要改代码,只需维护测试数据就可以了,并且根据不同的测试数据生成多个
    用例
    python 里面用 unittest 框架
    import unittest
    import ddt

    测试数据

    datas = [ {"user": "admin", "psw": "123", "result": "true"}, 
    {"user": "admin1", "psw": "1234", "result": "true"}, 
    {"user": "admin2", "psw": "1234", "result": "true"}, 
    {"user": "admin3", "psw": "1234", "result": "true"}, 
    {"user": "admin4", "psw": "1234", "result": "true"}, 
    {"user": "admin5", "psw": "1234", "result": "true"}, 
    {"user": "admin6", "psw": "1234", "result": "true"}, 
    {"user": "admin7", "psw": "1234", "result": "true"}, 
    {"user": "admin8", "psw": "1234", "result": "true"}, 
    {"user": "admin9", "psw": "1234", "result": "true"}, 
    {"user": "admin10", "psw": "1234", "result": "true"}, 
    {"user": "admin11", "psw": "1234", "result": "true"}] 
    @ddt.ddt 
    class Test(unittest.TestCase): 
    @ddt.data(*datas) 
    def test_(self, d): 
    """测码学院:{0}""" 
    print("测试数据:%s" % d) 
    if __name__ == "__main__": 
    unittest.main() 
    unittest 框架还有一个 paramunittest 也可以实现 
    import unittest 
    import paramunittest 
    import time 
    # python3.6 
    # 作者:测码学院
    @paramunittest.parametrized( 
    {"user": "admin", "psw": "123", "result": "true"}, 
    {"user": "admin1", "psw": "1234", "result": "true"}, 
    {"user": "admin2", "psw": "1234", "result": "true"}, 
    {"user": "admin3", "psw": "1234", "result": "true"}, 
    {"user": "admin4", "psw": "1234", "result": "true"}, 
    {"user": "admin5", "psw": "1234", "result": "true"}, 
    {"user": "admin6", "psw": "1234", "result": "true"}, 
    {"user": "admin7", "psw": "1234", "result": "true"}, 
    {"user": "admin8", "psw": "1234", "result": "true"}, 
    {"user": "admin9", "psw": "1234", "result": "true"}, 
    {"user": "admin10", "psw": "1234", "result": "true"}, 
    {"user": "admin11", "psw": "1234", "result": "true"}, 
    ) 
    class TestDemo(unittest.TestCase): 
    def setParameters(self, user, psw, result): 
    '''这里注意了,user, psw, result 三个参数和前面定义的字典一一 
    对应''' 
    self.user = user 
    self.user = psw 
    self.result = result 
    def testcase(self): 
    print("开始执行用例:--------------") 
    time.sleep(0.5) 
    print("输入用户名:%s" % self.user) 
    print("输入密码:%s" % self.user) 
    print("期望结果:%s " % self.result) 
    time.sleep(0.5) 
    self.assertTrue(self.result == "true") 
    if __name__ == "__main__": 
    unittest.main(verbosity=2) 
    如果用的是 pytest 框架,也能实现参数化 
    # content of test_canshu1.py 
    # coding:utf-8 
    import pytest 
    @pytest.mark.parametrize("test_input,expected", 
    [ ("3+5", 8), 
    ("2+4", 6), 
    ("6 * 9", 42), 
    ]) 
    def test_eval(test_input, expected): 
    assert eval(test_input) == expected 
    if __name__ == "__main__": 
    pytest.main(["-s", "test_canshu1.py"]) 
    

    pytest 里面还有一个更加强大的功能,获得多个参数化参数的所有组合,可以 堆叠参数化装饰器

    import pytest 
    @pytest.mark.parametrize("x", [0, 1]) 
    @pytest.mark.parametrize("y", [2, 3]) 
    def test_foo(x, y): 
    print("测试数据组合:x->%s, y->%s" % (x, y)) 
    if __name__ == "__main__": 
    pytest.main(["-s", "test_canshu1.py"]) 
    

    60.下个接口请求参数依赖上个接口的返回数据

    这个很容易,不同的接口封装成不同的函数或方法,需要的数据 return 出来, 用一个中间变量 a 去接受,后面的接口传 a 就可以了

    61.依赖于登录的接口如何处理

    登录接口依赖 token 的,可以先登录后,token 存到一个 yaml 或者 json,或者 ini 的配置文件里面,后面所有的请求去拿这个数据就可以全局使用了
    如果是 cookies 的参数,可以用 session 自动关联 s=requests.session()
    后面请求用 s.get()和 s.post()就可以自动关联 cookies 了

    62.依赖第三方的接口如何处理

    这个需要自己去搭建一个 mock 服务,模拟接口返回数据,
    moco 是一个开源的框架,在 github 上可以下载到
    https://github.com/dreamhead/moco
    moco 服务搭建需要自己能够熟练掌握,面试会问你具体如何搭建 ,如何模拟返 回的数据,是用的什么格式,如何请求的

    63.不可逆的操作,如何处理,比如删除一个订单这种接口如何测试

    此题考的是造数据的能力,接口的请求数据,很多都是需要依赖前面一个状态的 比如工作流这种,流向不同的人状态不一样,操作权限不一样,测试的时候,每 种状态都要测到,就需要自己会造数据了。
    平常手工测试造数据,直接在数据库改字段状态。那么自动化也是一样,造数据 可以用 python 连数据库了,做增删改查的操作 测试用例前置操作,setUp 做数据准备 后置操作,tearDown 做数据清理

    64.接口产生的垃圾数据如何清理

    跟上面一样,造数据和数据清理,需用 python 连数据库了,做增删改查的操作 测试用例前置操作,setUp 做数据准备
    后置操作,tearDown 做数据清理

    65.一个订单的几种状态如何全部测到?

    如:未处理,处理中,处理失败,处理成功
    跟上面一样,也是考察造数据,修改数据的状态

    66.python 如何连接数据库操作?

    这个就是详细的考察你是如何用 python 连数据库的,并且最好能现场写代码那 种(有的笔试题就是 python 连数据库)
    具体问你用到哪个模块,查询的数据是什么类型?如何删除数据?如何新增数据? 如何修改数据?
    PyMySQL 是在 Python3.x 版本中用于连接 MySQL 服务器的一个库,Python2 中 则使用 mysqldb。

    #!/usr/bin/python3 
    # 查询 EMPLOYEE 表中 salary(工资)字段大于 1000 的所有数据: 
    import pymysql 
    # 打开数据库连接 
    db = pymysql.connect("localhost","testuser","test123","TESTDB" ) 
    # 使用 cursor()方法获取操作游标 
    cursor = db.cursor() 
    # SQL 查询语句 
    sql = "SELECT * FROM EMPLOYEE \ 
    WHERE INCOME > %s" % (1000) 
    try: 
    # 执行 SQL 语句 
    cursor.execute(sql) 
    # 获取所有记录列表 
    results = cursor.fetchall() 
    for row in results: 
    fname = row[0] 
    lname = row[1] 
    age = row[2] 
    sex = row[3] 
    income = row[4] 
    # 打印结果 
    print ("fname=%s,lname=%s,age=%s,sex=%s,income=%s" % \ 
    (fname, lname, age, sex, income )) 
    except: 
    print ("Error: unable to fetch data") 
    # 关闭数据库连接 
    db.close() 
    

    其它的就是运行出报告、代码管理(git)、运行策略和持续集成 jenkins 相关 了,这个所以的自动化但是一样的,后面会单独讲一篇 jenkins 持续集成相关

    67.如果模块请求 http 改为了 https,测试方案应该如何制定、修改?

    分别用 http 还有 https 登录试试。如果用 https 可以正常登录,地址栏显示一把锁头,那么这个网站是
    有部署 SSL 的。如果 http 和 https 都能够正常登录,进一步说明该网站没有设置强制 https 登录,或者说没
    有设置 http 链接自动跳转 https 链接;相反如果用 http 登录,结果跳转到 https 页面,说明网站部署了 SSL,
    而且设置了 http 自动跳转 https。

    68.常用 HTTP 协议调试代理工具有什么?详细说明抓取 HTTPS 协议的 设置过程?

    Fiddler 是一个 http 协议调试代理工具
    打开 Fiddler,进入 Tools-Options-HTTPS,配置允许抓取 HTTPS 连接和解析 HTTPS 流量然后选择要
    解释的来源,设置是否忽略服务证书错误(这些操作做完之后,在浏览器方位 IP:8888,安装证书就可以在
    浏览器抓取 HTTPS 协议了)

    进入 Tools-Options-Connections,保证打开启抓取 HTTPS 连接,然后默认端口按需求是或否需要修改, 然后点选允许远程计算机连接选项
    在这里插入图片描述

    69.描述 TCP/IP 协议的层次结构,以及每一层中重要协议

    在这里插入图片描述

    最后

    晚安,测试打工人们
    另外这次码了2W字,是不是可以摸个几天再更新??
    点击并输入暗号:CSDN??

    在这里插入图片描述

    展开全文
  • 原因是本人修了一周年假,去西安新车并且旅游了一番。作为一个千年屌丝,笔者经常到CSDN写点东西,那么旅游 + 车这个事情肯定也是要写写滴,只不过这边文章肯定就不放技术板块了,放在综合板块。主文章是本人在...

    最近一周都没有更新博客,甚至有几天都没有刷过博客。原因是本人修了一周年假,去西安提新车并且旅游了一番。作为一个千年屌丝,笔者经常到CSDN写点东西,那么旅游 + 提车这个事情肯定也是要写写滴,只不过这边文章肯定就不放技术板块了,放在综合板块。主文章是本人在汽车之家上发的,然后部分转载到CSDN我的博客中。原址在:http://club.autohome.com.cn/bbs/thread-c-3430-57346926-1.html。欢迎各位朋友到汽车之家为我点赞。

    网友说的很有道理:你可以没钱但一定要有目标,并且要行动起来,而且在行动的过程中要做到不忘初心。关注唐呢,是从2015年它刚刚上市开始。那个时候国产SUV在市场上已经很火爆了,H6已经成为了一个神话。但是国产SUV的价格始终有一个天花板价格无法突破,当BYD 唐发布会上公布出旗舰版27.98万的官方指导价时,大家能想象一下我的心情吗?一万匹草泥马从我头上飞过。不过这也算是一种得到关注的方式吧,当时我的看法是,这就像某些二、三线明星为了博得关注,各种花式秀下限的现象一样。那个时候我关注的车型还有昂科威、途观、科帕奇,是的,都是SUV,因为我媳妇做轿车要晕车。但是这些合资SUV的价格,对于预算只有20万出头的我来说,只买得起低配版本。这是很重要的,但又不是最重要的,最重要的是:销售那个NB啊~~

    到了2016年初,这些车都被我pass了(额,不过说句题外话,新版途观这车本身到挺不错的,国产车还有很长的路要走啊)。原因很简单,我中毒了。唐的有点就不说了,常逛论坛的小伙伴都清楚,如果读者不清楚,请自行查看知识普及贴。那么本人换车的计划也越来越明确:就是它了。那时论坛上得到的所谓确切消息是,唐100会在年底前上市,而且在价格不出现太大变动的情况下,修改了几项被用户严重吐槽的问题。那样当然最好,那就等等吧。可是等来等去,放出的消息越来越不可靠,除了最初的几张“唐100路试谍照”以外,基本上没有什么新进展了,这里不得不给BYD的营销部门提提本人建议:如果技术部门或者其它关键部门没有100%的把握,就要做到不放任何官方消息出来,而且应该封杀所有是似而非的传言,否则市场关注度和潜在客户群就会被大量消耗。

    到了2016年9月,本人越来越发现唐100的发售情况不靠谱,看来16年要上市不现实啊。加上明年国家和地方的补贴肯定会减少,所以不必再等了!该出手时就要果断出手。9月份交了定金,就开始联系卖车、买内饰配件、租车位这些琐事。

    额,先来说说卖车吧。本人之前开的是一辆瑞虎,12年购入,平时主要是上下班代步。另外呢,当然还是要自驾游滴。这辆车从成都出发最远去过越南,四年中除了更换过一些消耗性配置外,汽车的三大件没有出过任何问题。实际上倒是我觉得自己有点对不住爱车,记得有一次实在是没法推脱了,把车借给朋友。我那个朋友一天就出了两起交通事故,而且都是他全责,把我的车啊撞得惨不忍赌(还好没伤到内脏)。所以,各位朋友注意啊:车与老婆概不外借这句话绝对是正确的!

    这里写图片描述

    这里写图片描述

    本来自己的想法是,国产二手车可能不好卖,提前一个月挂到网上去就可以差不多在提车时间前后卖出去。结果卖车的进度完全超过我的预计。不到两周就卖出去了,而且价格还算符合预期吧。提车当然是去西安,节约经费还可以去旅游一圈。媳妇在订车之后就开始关注各种游记的帖子。我们两个的分工很明确,提车的过程中她听我的,之后几天各处去玩我就听她的,最后开车回成都。

    这里写图片描述

    找了一个离新益通进的酒店住下,名字叫做π.酒店(三桥店)。价格实惠,房间干净,而且没有臭味。这个很重要,一家好的酒店能让我们第二天有充足的精神办理各种手续。这个酒店离新益通不远,1.2公里步行过去就行了。不过呢西安城西三环周围的扬灰很严重,所以大家注意带个口罩。我和媳妇就是吃了这个亏,结果晚上回到宾馆一看,就连鼻孔都是黑的。哈哈

    这里写图片描述

    这里写图片描述

    小城忙完准备工作,就过来招呼我办手续。由于不需要办贷款,所以在店里的手续办的很快。最主要的步骤当然就是付钱,其它事情都是小城待办,然后需要用到车主签字按手印时就会来找你。在办保险的时候出现一小插曲,由于之前的虎子一直买了盗抢险的,所以买保险的时候自己习惯的就勾上了盗抢险一栏,人保的小姑娘一直暗示我,不需要买盗抢险,原因有两个:1、唐这个车说贵不贵说便宜不便宜,这种车贼一般不会光顾,因为不好处理。他偷便宜的车,可以卖到山区开,也不会被查;偷很贵的车,可以卖到国外。但是这种处于中间价位的车是没下家愿意收的。2、唐有自动寻车功能,而且是内置的,所以要么贼拆除/清洗整个行车电脑或者切断全车电源,否则车主都可以定位到爱车的位置,所以偷了也没用,切断整车电源就更搞笑了——贼只有推着车走——大家可以自行脑补那个画面。

    这里写图片描述

    这里写图片描述

    办完手续,哦,更确切的说应该是等待小城办完手续,就已经是中午了。午饭时间只有一个小时,所以一切从简。下午还有两件事情:1、办税,这才是最关键的,70950的优惠全在这个节骨眼上。2、上户。这里要说说,现在的政策,是不能马上办理转籍的,需要等三个月。也就是说我需要先办西安的拍照,然后三个月后再办理转籍手续。还好,到时候不需要我再跑一次西安。

    整个提车过程需要两天时间,无论你是贷款还是全款。这其中准车主最主要做的一个事情用一个字就可以概括:等!4S店的服务确实很不错,主要是政务部门的速度还应该再提高一点,笔者的建议是,在外检环节再增加几个窗口就好了。接下来的时间就是媳妇带着我玩咯~~~

    这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

    正式开始充电都已经是我们回到成都以后了,在京昆高速上一个充电的地方都没有。看来国家发展充电桩的政策,任重道远啊。好,现在进入正题:耍!有车了,就方便多了,第一站直奔兵马俑。导航显示40多公里,不算远。

    这里写图片描述

    这里写图片描述

    从兵马俑出来已经是下午6点了。由于已经提到车了,所以我和媳妇就不用在西三环住了,这天晚上换到了市区内的酒店,紧挨着大雁塔。如果大家要逛大雁塔,建议晚上去。可以看亚洲最大的喷泉表演。大雁塔不远就是博物馆,那里也是要去的,古城嘛,除了有吃的,最重要的就是要去了解他的历史。博物馆每天放出4000张免费的浏览票,所以大家看到的现象就是:博物馆门口排了很长的队,如果您想拿到票,至少要排1个小时。但是本人觉得,从时间成本来说,这一个小时真的划不来,所以您可以到对面的售票窗口,买一张30元的票,然后直接进去。等你参观完一号展馆,会发现之前排队领取免费参观票的人,还在那里排队。

    这里写图片描述

    博物馆的光线太暗,手机肯定拍不出来效果,本人带了相机,但是博物馆禁用闪光灯。所以相机的效果也不好。大家还是自己去看吧。回民街当然也是要去的,最好也是晚上去。当然回民街和大雁塔还有一些距离,所以如果您要一个晚上游览两处景点,就一定要分配好时间。

    这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

    我们在西安去的最后一个景点,是大明皇宫遗址。额–看着攻略去的。但是,但是,但是,你去了以后才知道——真的是遗址—-时间宽裕,并且有交通工具的情况,是可以去的。否则真的不建议去。那里最主要的功能是市民公园,最核心的区域需要买票进去……

    这里写图片描述

    如果说游览西安是带着享受生活、领略民俗的心情去的,那么游览剑门关就肯定是带着敬畏之情去的——剑阁虽有通天险,可怜后主不思蜀。从西安直接上京昆高速,向汉中方向行驶就可以回成都。经过汉中就到了陕西和四川交接的地方,这里已经穿过了秦岭,也就是说我们已经从中国的北方进入和中国的南方。经汉中到达广元,再向南走10公里左右从剑阁县下高速就可以到达剑门关。

    这里写图片描述

    这里写图片描述

    这里写图片描述

    公元263年,魏伐蜀。姜维、廖化率三万蜀军据守剑阁三月,牵制魏军十三万。魏军绕过剑门关,袭奇江油占领绵阳,后主降,蜀灭。如果大家要自驾游四川,并准备走京昆线,那么剑门关一定是第一站。不然你就算到了成都的武侯祠,也不能完全体会为什么蜀地居民对武侯、平襄侯都是敬仰之情,也不能完全体会“出师未捷身先死,长使英雄泪满襟”这样的佳句。

    展开全文
  • ttf字体文件抽取自己想要的

    万次阅读 2017-04-14 21:31:15
    附2500个常用: 一乙二十丁厂七卜人入八九几儿了力乃刀又三于干亏士工土才寸下大丈与万上小口巾山千乞川亿个勺久凡及夕丸么广亡门义之尸弓己已子卫也女飞刃习叉马乡丰王井开夫天无元专云扎艺木五支厅不太犬区...

    【说明】

    游戏中经常需要用到各种字体,但是网上下载的TTF字体文件最小也有好几兆,要是游戏中需要用到几种字体,那简直不能忍。

    通常情况下,我们会用BMFont代替TTF字体,BMFont也有着很多的优势,但是缺点也很明显,每次修改都要美工的配合,而且不支持字号改变(不考虑缩放)。

    那么有没有办法能把TTF字体文件变小点呢?答案是肯定的,除了用FontCreator那种累死人不偿命的东西之外,这里介绍一个简单实用的工具。

    【介绍】

    sfnttool 是谷歌开源项目 sfntly 内置的工具,他的作用是从一个字体文件中提取指定的文字,导出的字体中将只包含你需要的文字。

    【下载】

    http://download.csdn.net/detail/ldpjay/8822587

    【使用】
    确保你的电脑已经安装了Java环境(能运行Java命令),这是必须的。

    命令行进入到sfnttool所在目录下。(一个小技巧,在当前文件夹里按住Shift再右键,里面有个“在此处打开命令行”。)

    输入下面的命令即可:

    java -jar sfnttool.jar  -s '这是一段测试文字' msyh.ttf msyh_simplify.ttf  
    java -jar sfnttool.jar -h  
    subset [-?|-h|-help] [-b] [-s string] fontfile outfile  
    prototype font subsetter  
            -?,-help        print this help information  
            -s,-string       string to subset  
            -b,-bench        benchmark (run 10000 iterations)  
            -h,-hints        strip hints  
            -w,-woff         output woff format  
            -e,-eot  output eot format  
            -x,-mtx  enable microtype express compression for eot format 

    输出字体在同目录下。

    附2500个常用字:

    一乙二十丁厂七卜人入八九几儿了力乃刀又三于干亏士工土才寸下大丈与万上小口巾山千乞川亿个勺久凡及夕丸么广亡门义之尸弓己已子卫也女飞刃习叉马乡丰王井开夫天无元专云扎艺木五支厅不太犬区历尤友匹车巨牙屯比互切瓦止少日中冈贝内水见午牛手毛气升长仁什片仆化仇币仍仅斤爪反介父从今凶分乏公仓月氏勿欠风丹匀乌凤勾文六方火为斗忆订计户认心尺引丑巴孔队办以允予劝双书幻玉刊示末未击打巧正扑扒功扔去甘世古节本术可丙左厉右石布龙平灭轧东卡北占业旧帅归且旦目叶甲申叮电号田由史只央兄叼叫另叨叹四生失禾丘付仗代仙们仪白仔他斥瓜乎丛令用甩印乐句匆册犯外处冬鸟务包饥主市立闪兰半汁汇头汉宁穴它讨写让礼训必议讯记永司尼民出辽奶奴加召皮边发孕圣对台矛纠母幼丝式刑动扛寺吉扣考托老执巩圾扩扫地扬场耳共芒亚芝朽朴机权过臣再协西压厌在有百存而页匠夸夺灰达列死成夹轨邪划迈毕至此贞师尘尖劣光当早吐吓虫曲团同吊吃因吸吗屿帆岁回岂刚则肉网年朱先丢舌竹迁乔伟传乒乓休伍伏优伐延件任伤价份华仰仿伙伪自血向似后行舟全会杀合兆企众爷伞创肌朵杂危旬旨负各名多争色壮冲冰庄庆亦刘齐交次衣产决充妄闭问闯羊并关米灯州汗污江池汤忙兴宇守宅字安讲军许论农讽设访寻那迅尽导异孙阵阳收阶阴防奸如妇好她妈戏羽观欢买红纤级约纪驰巡寿弄麦形进戒吞远违运扶抚坛技坏扰拒找批扯址走抄坝贡攻赤折抓扮抢孝均抛投坟抗坑坊抖护壳志扭块声把报却劫芽花芹芬苍芳严芦劳克苏杆杠杜材村杏极李杨求更束豆两丽医辰励否还歼来连步坚旱盯呈时吴助县里呆园旷围呀吨足邮男困吵串员听吩吹呜吧吼别岗帐财针钉告我乱利秃秀私每兵估体何但伸作伯伶佣低你住位伴身皂佛近彻役返余希坐谷妥含邻岔肝肚肠龟免狂犹角删条卵岛迎饭饮系言冻状亩况床库疗应冷这序辛弃冶忘闲间闷判灶灿弟汪沙汽沃泛沟没沈沉怀忧快完宋宏牢究穷灾良证启评补初社识诉诊词译君灵即层尿尾迟局改张忌际陆阿陈阻附妙妖妨努忍劲鸡驱纯纱纳纲驳纵纷纸纹纺驴纽奉玩环武青责现表规抹拢拔拣担坦押抽拐拖拍者顶拆拥抵拘势抱垃拉拦拌幸招坡披拨择抬其取苦若茂苹苗英范直茄茎茅林枝杯柜析板松枪构杰述枕丧或画卧事刺枣雨卖矿码厕奔奇奋态欧垄妻轰顷转斩轮软到非叔肯齿些虎虏肾贤尚旺具果味昆国昌畅明易昂典固忠咐呼鸣咏呢岸岩帖罗帜岭凯败贩购图钓制知垂牧物乖刮秆和季委佳侍供使例版侄侦侧凭侨佩货依的迫质欣征往爬彼径所舍金命斧爸采受乳贪念贫肤肺肢肿胀朋股肥服胁周昏鱼兔狐忽狗备饰饱饲变京享店夜庙府底剂郊废净盲放刻育闸闹郑券卷单炒炊炕炎炉沫浅法泄河沾泪油泊沿泡注泻泳泥沸波泼泽治怖性怕怜怪学宝宗定宜审宙官空帘实试郎诗肩房诚衬衫视话诞询该详建肃录隶居届刷屈弦承孟孤陕降限妹姑姐姓始驾参艰线练组细驶织终驻驼绍经贯奏春帮珍玻毒型挂封持项垮挎城挠政赴赵挡挺括拴拾挑指垫挣挤拼挖按挥挪某甚革荐巷带草茧茶荒茫荡荣故胡南药标枯柄栋相查柏柳柱柿栏树要咸威歪研砖厘厚砌砍面耐耍牵残殃轻鸦皆背战点临览竖省削尝是盼眨哄显哑冒映星昨畏趴胃贵界虹虾蚁思蚂虽品咽骂哗咱响哈咬咳哪炭峡罚贱贴骨钞钟钢钥钩卸缸拜看矩怎牲选适秒香种秋科重复竿段便俩贷顺修保促侮俭俗俘信皇泉鬼侵追俊盾待律很须叙剑逃食盆胆胜胞胖脉勉狭狮独狡狱狠贸怨急饶蚀饺饼弯将奖哀亭亮度迹庭疮疯疫疤姿亲音帝施闻阀阁差养美姜叛送类迷前首逆总炼炸炮烂剃洁洪洒浇浊洞测洗活派洽染济洋洲浑浓津恒恢恰恼恨举觉宣室宫宪突穿窃客冠语扁袄祖神祝误诱说诵垦退既屋昼费陡眉孩除险院娃姥姨姻娇怒架贺盈勇怠柔垒绑绒结绕骄绘给络骆绝绞统耕耗艳泰珠班素蚕顽盏匪捞栽捕振载赶起盐捎捏埋捉捆捐损都哲逝捡换挽热恐壶挨耻耽恭莲莫荷获晋恶真框桂档桐株桥桃格校核样根索哥速逗栗配翅辱唇夏础破原套逐烈殊顾轿较顿毙致柴桌虑监紧党晒眠晓鸭晃晌晕蚊哨哭恩唤啊唉罢峰圆贼贿钱钳钻铁铃铅缺氧特牺造乘敌秤租积秧秩称秘透笔笑笋债借值倚倾倒倘俱倡候俯倍倦健臭射躬息徒徐舰舱般航途拿爹爱颂翁脆脂胸胳脏胶脑狸狼逢留皱饿恋桨浆衰高席准座脊症病疾疼疲效离唐资凉站剖竞部旁旅畜阅羞瓶拳粉料益兼烤烘烦烧烛烟递涛浙涝酒涉消浩海涂浴浮流润浪浸涨烫涌悟悄悔悦害宽家宵宴宾窄容宰案请朗诸读扇袜袖袍被祥课谁调冤谅谈谊剥恳展剧屑弱陵陶陷陪娱娘通能难预桑绢绣验继球理捧堵描域掩捷排掉堆推掀授教掏掠培接控探据掘职基著勒黄萌萝菌菜萄菊萍菠营械梦梢梅检梳梯桶救副票戚爽聋袭盛雪辅辆虚雀堂常匙晨睁眯眼悬野啦晚啄距跃略蛇累唱患唯崖崭崇圈铜铲银甜梨犁移笨笼笛符第敏做袋悠偿偶偷您售停偏假得衔盘船斜盒鸽悉欲彩领脚脖脸脱象够猜猪猎猫猛馅馆凑减毫麻痒痕廊康庸鹿盗章竟商族旋望率着盖粘粗粒断剪兽清添淋淹渠渐混渔淘液淡深婆梁渗情惜惭悼惧惕惊惨惯寇寄宿窑密谋谎祸谜逮敢屠弹随蛋隆隐婚婶颈绩绪续骑绳维绵绸绿琴斑替款堪搭塔越趁趋超提堤博揭喜插揪搜煮援裁搁搂搅握揉斯期欺联散惹葬葛董葡敬葱落朝辜葵棒棋植森椅椒棵棍棉棚棕惠惑逼厨厦硬确雁殖裂雄暂雅辈悲紫辉敞赏掌晴暑最量喷晶喇遇喊景践跌跑遗蛙蛛蜓喝喂喘喉幅帽赌赔黑铸铺链销锁锄锅锈锋锐短智毯鹅剩稍程稀税筐等筑策筛筒答筋筝傲傅牌堡集焦傍储奥街惩御循艇舒番释禽腊脾腔鲁猾猴然馋装蛮就痛童阔善羡普粪尊道曾焰港湖渣湿温渴滑湾渡游滋溉愤慌惰愧愉慨割寒富窜窝窗遍裕裤裙谢谣谦属屡强粥疏隔隙絮嫂登缎缓编骗缘瑞魂肆摄摸填搏塌鼓摆携搬摇搞塘摊蒜勤鹊蓝墓幕蓬蓄蒙蒸献禁楚想槐榆楼概赖酬感碍碑碎碰碗碌雷零雾雹输督龄鉴睛睡睬鄙愚暖盟歇暗照跨跳跪路跟遣蛾蜂嗓置罪罩错锡锣锤锦键锯矮辞稠愁筹签简毁舅鼠催傻像躲微愈遥腰腥腹腾腿触解酱痰廉新韵意粮数煎塑慈煤煌满漠源滤滥滔溪溜滚滨粱滩慎誉塞谨福群殿辟障嫌嫁叠缝缠静碧璃墙撇嘉摧截誓境摘摔聚蔽慕暮蔑模榴榜榨歌遭酷酿酸磁愿需弊裳颗嗽蜻蜡蝇蜘赚锹锻舞稳算箩管僚鼻魄貌膜膊膀鲜疑馒裹敲豪膏遮腐瘦辣竭端旗精歉熄熔漆漂漫滴演漏慢寨赛察蜜谱嫩翠熊凳骡缩慧撕撒趣趟撑播撞撤增聪鞋蕉蔬横槽樱橡飘醋醉震霉瞒题暴瞎影踢踏踩踪蝶蝴嘱墨镇靠稻黎稿稼箱箭篇僵躺僻德艘膝膛熟摩颜毅糊遵潜潮懂额慰劈操燕薯薪薄颠橘整融醒餐嘴蹄器赠默镜赞篮邀衡膨雕磨凝辨辩糖糕燃澡激懒壁避缴戴擦鞠藏霜霞瞧蹈螺穗繁辫赢糟糠燥臂翼骤鞭覆蹦镰翻鹰警攀蹲颤瓣爆疆壤耀躁嚼嚷籍魔灌蠢霸露囊罐翎qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890《》?!,。妩媚曳鎏怡廓沧

    字体推荐:
    苹果PingHei三款
    腾祥嘉丽细圆简
    苹方字体
    微软雅黑light
    汉仪旗黑H2312F45

    推荐链接:

    http://www.zhaozi.cn/html/fonts/china/riben/2010-11-18/21992.html
    https://www.zhihu.com/question/20727176
    http://daily.zhihu.com/story/3630127
    http://www.epinv.com/post/5740.html
    https://sanwen8.cn/p/3a5dO2X.html
    https://www.zhihu.com/question/20922638
    http://www.fontke.com/font/10552118/
    https://www.zhihu.com/question/24878730
    http://blog.sina.com.cn/s/blog_62c692fd0100fjtn.html

    展开全文
  • 本文将你解析各种形式自定义字体,绘制点阵图,并通过图像识别提取出关系列表,最终校对后构建正确的对应关系,最终获取到正确的数据。 看到本文,相信以后你对任何形式额字体反爬都能见招拆招。 文章目录深度剖析...

    大家好,我的小小明。
    前面我在《Python处理超强反爬(TSec防火墙+CSS图片背景偏移定位)》一文中讲解如何解析css图片背景偏移的数据,并通过图像识别提取文字。

    本文将带你解析各种形式自定义字体,绘制点阵图,并通过图像识别提取出关系列表,最终校对后构建正确的对应关系,最终获取到正确的数据。

    看到本文,相信以后你对任何形式额字体反爬都能见招拆招。

    深度剖析自定义字体解析

    自定义字体的介绍

    首先,我们必须要清楚自定义字体与普通字体的区别,自定义字体定义了一些特殊的Unicode编码对应的点阵图数据,而普通字体只是定义标准编码的显示形式,所以普通字体渲染的数据可以直接复制出正确的文本,而自定义字体只能复制到对应的Unicode编码。

    那么游览器如何显示出对应的字符呢?那是因为游览器会根据自定义字体的对应关系,渲染对应的点阵图进行显示。

    下面我们以某团购网站为例进行演示。

    这次我分析的页面是深圳休闲娱乐

    image-20210802161659847

    image-20210802162026699

    可以看到自定义字体都存在于svgmtsi标签中,不同的class属性也对应了不同自定义字体文件。

    如果我们取消所有的自定义字体的加载,可以看到网页上对应的位置都会出现乱码:

    image-20210802162640117

    从上图也可以看到,产生自定义字体的位置完全是随机的。

    对于这种情况,我们最好使用可以修改HTML DOM树的库来维持节点的相对顺序,我选择了BeautifulSoup这个库,可惜只支持css选择器。

    不过也好,早期我学编程用Java玩小爬虫的时候就更喜欢css选择器,正好可以找回久违的感觉。

    接下来我们一步步分析页面,首先用python读取页面数据:

    Python加载页面

    import requests
    
    headers = {
        "Connection": "keep-alive",
        "Cache-Control": "max-age=0",
        "Upgrade-Insecure-Requests": "1",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "Accept-Language": "zh-CN,zh;q=0.9"
    }
    session = requests.Session()
    session.headers = headers
    res = session.get("http://www.dianping.com/shenzhen/ch30")
    

    下面我们使用BeautifulSoup解析下载的页面,构建DOM树:

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(res.text, 'html5lib')
    

    关于BeautifulSoup可以查看官方文档:

    (上面两个链接内容一样,目录形式有区别)

    解析顶部导航栏分类和地点列表

    由于现在该团购网站翻第二页就要求登录,咱们也没有打算真的要爬它。所以我通过多下载几个分类链接,来模拟批量下载的效果。

    下面准备解析出下面这些对应的标题:

    image-20210802165248267

    通过xpath查询工具获取到xpath后,就可以转换为css选择器。

    分类列表:

    # //div[@id='classfy']/a/span
    type_list = []
    for a_tag in soup.select("div#classfy > a"):
        type_list.append((a_tag.span.text, a_tag['href']))
    type_list
    
    [('按摩/足疗', 'http://www.dianping.com/shenzhen/ch30/g141'),
     ('KTV', 'http://www.dianping.com/shenzhen/ch30/g135'),
     ('洗浴/汗蒸', 'http://www.dianping.com/shenzhen/ch30/g140'),
     ('酒吧', 'http://www.dianping.com/shenzhen/ch30/g133'),
     ('运动健身', 'http://www.dianping.com/shenzhen/ch30/g2636'),
     ('茶馆', 'http://www.dianping.com/shenzhen/ch30/g134'),
     ('密室', 'http://www.dianping.com/shenzhen/ch30/g2754'),
     ('团建拓展', 'http://www.dianping.com/shenzhen/ch30/g34089'),
     ('采摘/农家乐', 'http://www.dianping.com/shenzhen/ch30/g20038'),
     ('剧本杀', 'http://www.dianping.com/shenzhen/ch30/g50035'),
     ('游戏厅', 'http://www.dianping.com/shenzhen/ch30/g137'),
     ('DIY手工坊', 'http://www.dianping.com/shenzhen/ch30/g144'),
     ('私人影院', 'http://www.dianping.com/shenzhen/ch30/g20041'),
     ('轰趴馆', 'http://www.dianping.com/shenzhen/ch30/g20040'),
     ('网吧/电竞', 'http://www.dianping.com/shenzhen/ch30/g20042'),
     ('VR', 'http://www.dianping.com/shenzhen/ch30/g33857'),
     ('桌面游戏', 'http://www.dianping.com/shenzhen/ch30/g6694'),
     ('棋牌室', 'http://www.dianping.com/shenzhen/ch30/g32732'),
     ('文化艺术', 'http://www.dianping.com/shenzhen/ch30/g142'),
     ('新奇体验', 'http://www.dianping.com/shenzhen/ch30/g34090')]
    

    地点列表:

    # //div[@id='region-nav']/a/span
    area_list = []
    for a_tag in soup.select("div#region-nav > a"):
        area_list.append((a_tag.span.text, a_tag['href']))
    area_list
    
    [('福田区', 'http://www.dianping.com/shenzhen/ch30/r29'),
     ('南山区', 'http://www.dianping.com/shenzhen/ch30/r31'),
     ('罗湖区', 'http://www.dianping.com/shenzhen/ch30/r30'),
     ('盐田区', 'http://www.dianping.com/shenzhen/ch30/r32'),
     ('龙华区', 'http://www.dianping.com/shenzhen/ch30/r12033'),
     ('龙岗区', 'http://www.dianping.com/shenzhen/ch30/r34'),
     ('宝安区', 'http://www.dianping.com/shenzhen/ch30/r33'),
     ('坪山区', 'http://www.dianping.com/shenzhen/ch30/r12035'),
     ('光明区', 'http://www.dianping.com/shenzhen/ch30/r89951'),
     ('南澳大鹏新区', 'http://www.dianping.com/shenzhen/ch30/r12036')]
    

    解析字体对应css的下载URL

    经观察可以发现,定义自定义字体的css文件在链接带有svgtextcss关键字的url中:

    image-20210802170838134

    我们可以从所有的定义css样式的链接中找到含有svgtextcss关键字的链接:

    from urllib import parse
    
    def getUrlFromNode(nodes, tag):
        for node in nodes:
            url = node['href']
            if url.find(tag) != -1:
                return parse.urljoin(base_url, url)
    
    
    def get_css_url(soup):
        css_url = getUrlFromNode(soup.select(
            "head > link[rel=stylesheet]"), "svgtextcss")
        return css_url
    
    
    css_url = get_css_url(soup)
    css_url
    
    'http://s3plus.meituan.net/v1/mss_0a06a471f9514fc79c981b5466f56b91/svgtextcss/18379bbeb1f5bf54c52bb1d8b71d4fb1.css'
    

    解析css获取自定义字体的URL

    格式化定义字体的css文件:

    image-20210802172030864

    可以看到,class定义了使用的字体名称,font-face定义了每个字体名称对应的字体文件。

    虽然现在我们可以看到规律每个class就是加了一个PingFangSC-Regular-的前缀作为字体名称,但是我们无法保证以后该网站依然会这样设计,为了保证以后在这个点上面不需要改代码,我们依然还是解析出每个class对应的font-family,再解析出每个font-family对应的多个字体URL,最终多个字体URL取后缀为.woff格式的URL,建立class属性到woff字体的映射关系。

    下面是完整代码:

    import re
    
    
    def get_url(urls, tag, only_First=True):
        urls = [parse.urljoin(base_url, url)
                for url in urls if tag is None or url.find(tag) != -1]
        if urls and only_First:
            return urls[0]
        return urls
    
    
    def parseCssFontUrl(css_url, tag=None, only_First=True):
        res = session.get(css_url)
        rule = {}
        font_face = {}
        for name, value in re.findall("([^{}]+){([^{}]+)}", res.text):
            name = name.strip()
            for row in value.split(";"):
                if row.find(":") == -1:
                    continue
                k, v = row.split(":")
                k, v = k.strip(), v.strip(' "\'')
                if name == "@font-face":
                    if k == "font-family":
                        font_name = v
                    elif k == "src":
                        font_face.setdefault(font_name, []).extend(
                            re.findall("url\(\"([^()]+)\"\)", v))
                else:
                    rule[name[1:]] = v
        font_urls = {}
        for class_name, tag_name in rule.items():
            font_urls[class_name] = get_url(font_face[tag_name], tag)
        return font_urls
    
    
    font_urls = parseCssFontUrl(css_url, ".woff", only_First=False)
    font_urls
    
    {'shopNum': 'http://s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/89e46c52.woff',
     'tagName': 'http://s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/f8536a55.woff',
     'reviewTag': 'http://s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/0373a060.woff',
     'address': 'http://s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/f8536a55.woff'}
    

    下载字体

    我们可以将上述四个字体都下载下来看看:

    def download_file(url, out_name=None):
        if out_name is None:
            out_name = url[url.rfind("/")+1:]
        with open(out_name, "wb") as f:
            f.write(session.get(url).content)
    
    for class_name, url in font_urls.items():
        download_file(url, f"{class_name}.woff")
    

    下载后得到4个字体文件:

    image-20210802173222464

    想要本地查看字体,我们可以通过FontCreator字体设计工具,百度一下可以直接搜索到下载链接。

    打开后:

    image-20210802174851524

    经过对比发现四个文件的点阵图顺序完全一致,不同的只是编码与点阵图的关系。

    建立自定义字体映射关系

    下面我们需要分析对于指定字体每个被定义的Unicode字符对应的真实字符。由于字体文件中存储的字符的点阵图,本质是图片而不是文本,所以我们无法复制出来。但我们可以考虑通过PIL加载自定义字体,然后将每个被定义的Unicode字符画出相应的点阵图,再进行图像识别,就可以获取相应的文本数据了。

    这里需要使用fontTools工具,可以直接使用pip安装。

    详见:https://github.com/fonttools/fonttools

    以class等于tagName的字体为例,先获取其被定义的Unicode字符列表:

    from fontTools.ttLib import TTFont
    
    tfont = TTFont("tagName.woff")
    # 去掉前2个扩展字符
    uni_list = tfont.getGlyphOrder()[2:]
    print(uni_list[:10], len(uni_list))
    
    ['uniec3e', 'unif3fc', 'uniea1f', 'unie7f7', 'unie258', 'unif5aa', 'unif48c', 'unif088', 'unif588', 'unif82e'] 601
    

    这里打印了前10个Unicode代码点,共有601个自定义字符。

    打印结果也与上面的截图中FontCreator字体设计工具查看的结果一致。

    使用PIL绘图工具,先绘制前5个代码点测试一下:

    from PIL import ImageFont, Image, ImageDraw
    
    font = ImageFont.truetype("tagName.woff", 20)
    for uchar in uni_list[:5]:
        unknown_char = f"\\u{uchar[3:]}".encode().decode("unicode_escape")
        im = Image.new(mode='RGB', size=(22, 20), color="white")
        draw = ImageDraw.Draw(im=im)
        draw.text(xy=(5, -5), text=unknown_char, fill=0, font=font)
        display(im)
    

    绘制结果:

    image-20210802181849772

    可以看到能够正确绘制出相应的点阵图。

    下面再测试每n个代码点为一组一起绘制,减少后面图像识别的次数(这里设置n=25,绘制5组):

    n = 25
    font = ImageFont.truetype("tagName.woff", 20)
    for i in range(0, 5*n, n):
        im = Image.new(mode='RGB', size=(20*n+10, 22), color="white")
        draw = ImageDraw.Draw(im=im)
        unknown_chars = "".join(uni_list[i:i + n]).replace("uni", "\\u")
        unknown_chars = unknown_chars.encode().decode("unicode_escape")
        draw.text(xy=(5, -4), text=unknown_chars, fill=0, font=font)
        display(im)
    

    绘制结果:

    image-20210802181925813

    封装一下,批量获取一个字体文件的全部图片对象:

    from fontTools.ttLib import TTFont
    from PIL import ImageFont, Image, ImageDraw
    
    
    def getCustomFontGroupImgs(font_file, uni_list=None, group_num=25):
        if uni_list is None:
            tfont = TTFont(font_file)
            uni_list = tfont.getGlyphOrder()[2:]
        imgs = []
        font = ImageFont.truetype(font_file, 20)
        for i in range(0, len(uni_list), group_num):
            im = Image.new(mode='RGB', size=(20*group_num+10, 22), color="white")
            draw = ImageDraw.Draw(im=im)
            unknown_chars = "".join(uni_list[i:i + group_num]).replace("uni", "\\u")
            unknown_chars = unknown_chars.encode().decode("unicode_escape")
            draw.text(xy=(5, -4), text=unknown_chars, fill=0, font=font)
            imgs.append(im)
        return imgs
    

    pytesseract默认不支持对中文的识别,需要较多的配置。这次我们直接使用一个最近比较流行的库叫带带弟弟orc来进行图像识别,一行命令即可安装:

    pip install ddddocr
    

    使用示例和参数可以查看:https://pypi.org/project/ddddocr/

    不过该库只支持传图片字节和base64编码,不支持直接传入图片对象,需要二次转换。

    可以定义一个将图片转字节的方法:

    from io import BytesIO
    
    def get_img_bytes(img):
        img_byte = BytesIO()
        im.save(img_byte, format='JPEG')  # format: PNG or JPEG
        return img_byte.getvalue()  # im对象转为二进制流
    

    然后就可以以如下形式进行批量识别:

    from ddddocr import DdddOcr
    
    imgs = getCustomFontGroupImgs('shopNum.woff', group_num=50)
    ocr = DdddOcr()
    result = []
    for im in imgs:
        display(im)
        text = ocr.classification(get_img_bytes(im))
        print(text)
        result.append(text)
    

    效果如下:

    image-20210802193638811

    整体来说准确率还是非常高的。

    我最终还是决定直接继承DdddOcr类,重写识别方法优化算法(以后再考虑自行开发图像识别类):

    from ddddocr import DdddOcr, np
    
    
    class OCR(DdddOcr):
        def __init__(self):
            super().__init__()
    
        def ocr(self, image):
            image = image.resize(
                (int(image.size[0] * (64 / image.size[1])), 64), Image.ANTIALIAS).convert('L')
            image = np.array(image).astype(np.float32)
            image = np.expand_dims(image, axis=0) / 255.
            image = (image - 0.5) / 0.5
            ort_inputs = {'input1': np.array([image])}
            ort_outs = self._DdddOcr__ort_session.run(None, ort_inputs)
            result = []
            last_item = 0
            for item in ort_outs[0][0]:
                if item == 0 or item == last_item:
                    continue
                result.append(self._DdddOcr__charset[item])
                last_item = item
            return ''.join(result)
    

    然后这样调用:

    imgs = getCustomFontGroupImgs('shopNum.woff', group_num=42)
    ocr = OCR()
    result = []
    for im in imgs:
        display(im)
        text = ocr.ocr(im)
        print(text)
        result.append(text)
    

    image-20210803100946648

    可以看到经过继承调整后的代码,识别准确率更高了些。

    最终我们人工校对修改后,得到如下字符集:

    words = '1234567890店中美家馆小车大市公酒行国品发电金心业商司超生装园场食有新限天面工服海华水房饰城乐汽香部利子老艺花专东肉菜学福饭人百餐茶务通味所山区门药银农龙停尚安广鑫一容动南具源兴鲜记时机烤文康信果阳理锅宝达地儿衣特产西批坊州牛佳化五米修爱北养卖建材三会鸡室红站德王光名丽油院堂烧江社合星货型村自科快便日民营和活童明器烟育宾精屋经居庄石顺林尔县手厅销用好客火雅盛体旅之鞋辣作粉包楼校鱼平彩上吧保永万物教吃设医正造丰健点汤网庆技斯洗料配汇木缘加麻联卫川泰色世方寓风幼羊烫来高厂兰阿贝皮全女拉成云维贸道术运都口博河瑞宏京际路祥青镇厨培力惠连马鸿钢训影甲助窗布富牌头四多妆吉苑沙恒隆春干饼氏里二管诚制售嘉长轩杂副清计黄讯太鸭号街交与叉附近层旁对巷栋环省桥湖段乡厦府铺内侧元购前幢滨处向座下澩凤港开关景泉塘放昌线湾政步宁解白田町溪十八古双胜本单同九迎第台玉锦底后七斜期武岭松角纪朝峰六振珠局岗洲横边济井办汉代临弄团外塔杨铁浦字年岛陵原梅进荣友虹央桂沿事津凯莲丁秀柳集紫旗张谷的是不了很还个也这我就在以可到错没去过感次要比觉看得说常真们但最喜哈么别位能较境非为欢然他挺着价那意种想出员两推做排实分间甜度起满给热完格荐喝等其再几只现朋候样直而买于般豆量选奶打每评少算又因情找些份置适什蛋师气你姐棒试总定啊足级整带虾如态且尝主话强当更板知己无酸让入啦式笑赞片酱差像提队走嫩才刚午接重串回晚微周值费性桌拍跟块调糕'
    

    字体文件中的Unicode代码点则与上述字符集字符一一对应。

    由于该网站所有的自定义字体的点阵图都是这个顺序,所以我们不再需要解析其他的字体文件获取这个字符列表。当然这个团购网站以后还打算变态到每个字体文件的点阵图顺序也随机,那我只能说,真狠。那到时候我再考虑升级自己的代码,因为我个人的目标就是没有我解析不了的数据。

    有了点阵图对应的字符集,咱们就可以轻松建立字体文件的映射关系:

    from fontTools.ttLib import TTFont
    
    font_data = TTFont("tagName.woff")
    uni_list = font_data.getGlyphOrder()[2:]
    font_map = dict(zip(map(lambda x: x[3:], uni_list), words))
    

    字体缓存器

    针对该团购网站,由于我们无法保证所有页面用这一个相同的css文件,所以我们需要建立一个css的URL到字体文件URL和字体文件URL到对应字体映射关系的二级缓存:

    from io import BytesIO
    
    url2FontMapCache = {}
    css2FontCache = {}
    
    
    def getFontMapFromURL(font_url):
        "缓存字体URL对应字体映射关系"
        if font_url not in url2FontMapCache:
            font_bytes = BytesIO(session.get(font_url).content)
            font_data = TTFont(font_bytes)
            uni_list = font_data.getGlyphOrder()[2:]
            url2FontMapCache[font_url] = dict(
                zip(map(lambda x: x[3:], uni_list), words))
        return url2FontMapCache[font_url]
    
    
    def getFontMapFromClassName(class_name, css_url):
        "缓存指定css文件对应字体URL"
        if css_url not in css2FontCache:
            css2FontCache[css_url] = parseCssFontUrl(css_url, ".woff")
        font_url = css2FontCache[css_url].get(class_name)
        return getFontMapFromURL(font_url)
    

    可以获取当前页面下,每个自定义字体的映射关系:

    for class_name in font_urls.keys():
        font_map = getFontMapFromClassName(class_name, css_url)
        print(list(font_map.items())[:12])
    

    结果:

    [('e0a7', '1'), ('ebf3', '2'), ('ee9b', '3'), ('e7e4', '4'), ('f5f8', '5'), ('e7a1', '6'), ('ef49', '7'), ('eef7', '8'), ('f7e0', '9'), ('e633', '0'), ('e5de', '店'), ('e67f', '中')]
    [('ec3e', '1'), ('f3fc', '2'), ('ea1f', '3'), ('e7f7', '4'), ('e258', '5'), ('f5aa', '6'), ('f48c', '7'), ('f088', '8'), ('f588', '9'), ('f82e', '0'), ('e7c5', '店'), ('e137', '中')]
    [('e3e0', '1'), ('e85f', '2'), ('f3c8', '3'), ('f3d5', '4'), ('e771', '5'), ('f251', '6'), ('f6f6', '7'), ('e8da', '8'), ('ea58', '9'), ('f8fb', '0'), ('ef9b', '店'), ('f3dd', '中')]
    [('ec3e', '1'), ('f3fc', '2'), ('ea1f', '3'), ('e7f7', '4'), ('e258', '5'), ('f5aa', '6'), ('f48c', '7'), ('f088', '8'), ('f588', '9'), ('f82e', '0'), ('e7c5', '店'), ('e137', '中')]
    

    将所有自定义字体全部替换为正常文字

    有了字体映射关系,我们就可以对页面的自定义字体替换成我们解析好的文本数据。

    首先获取被替换的父节点列表,方便对比:

    b_tags = [svgmtsi.parent for svgmtsi in soup.find_all('svgmtsi')]
    b_tags
    

    image-20210803101837065

    虽然我们现在看到该网站每个svgmtsi标签只存放一个字符,但无法确保以后也依然如此,所以我们的代码现在就考虑一个svgmtsi标签内部存在多个字符的情况。

    执行替换:

    for svgmtsi in soup.find_all('svgmtsi'):
        class_name = svgmtsi['class'][0]
        font_map = getFontMapFromClassName(class_name, css_url)
        chars = []
        for c in svgmtsi.text:
            char = c.encode("unicode_escape").decode()[2:]
            chars.append(font_map[char])
        svgmtsi.replaceWith("".join(chars))
    

    替换后,再查看之前保存的节点:

    b_tags
    

    image-20210803101902441

    提取数据

    将自定义字体替换之后,我们就可以非常丝滑的提取需要的数据了:

    num_rule = re.compile("\d+")
    
    for li_tag in soup.select("div#shop-all-list div.txt"):
        title = li_tag.select_one("div.tit>a>h4").text
        url = li_tag.select_one("div.tit>a")["href"]
        star_class = li_tag.select_one(
            "div.comment>div.nebula_star>div.star_icon>span")["class"]
        star = int(num_rule.findall(" ".join(star_class))[0])//10
    
        comment_tag = li_tag.select_one("div.comment>a.review-num>b")
        comment_num = comment_tag.text if comment_tag else None
    
        mean_price_tag = li_tag.select_one("div.comment>a.mean-price>b")
        mean_price = mean_price_tag.text if mean_price_tag else None
    
        fun_type = li_tag.select_one("div.tag-addr>a:nth-of-type(1)>span.tag").text
        area = li_tag.select_one("div.tag-addr>a:nth-of-type(2)>span.tag").text
        print(title, url, star, comment_num, mean_price, fun_type, area)
    
    轰趴天台·大白之家(南山店) http://www.dianping.com/shop/k1lqueFI6sIOfjnI 5 129 ¥196 轰趴馆 华侨城
    巨鹿搏击俱乐部(车公庙店) http://www.dianping.com/shop/k4tmabQaordrq6Tm 5 238 ¥246 拳击 车公庙
    SWING CAGE 棒球击球笼&冲浪滑板碗池 http://www.dianping.com/shop/l2lUP0rvcLPy4ebm 5 1088 ¥85 体育场馆 科技园
    微醺云深处沉浸式剧场 http://www.dianping.com/shop/HaLzYuXfvUWmPLSz 0 8 None 剧本杀 科技园
    逐见有光Chandelle http://www.dianping.com/shop/H2CSpvtn70y12wNh 5 138 ¥281 DIY手工坊 市中心/会展中心
    cozy cozy银饰DIY手作室(万象城店) http://www.dianping.com/shop/l3mCIs6dSrdL9jo7 5 1270 ¥321 DIY手工坊 万象城
    博哥的小剧场沉浸推理体验馆(南山万象天地店) http://www.dianping.com/shop/H3S4zD55e1gmfb8E 3 18 None 剧本杀 科技园
    FlowLife拓极滑板冲浪俱乐部(蛇口旗舰店) http://www.dianping.com/shop/G9sHgWISxYtBXz79 5 481 ¥217 新奇体验 蛇口
    Doors秘道·独立剧情密室(车公庙分店) http://www.dianping.com/shop/k4O3oDj6BwLtbgD4 5 878 ¥101 密室 车公庙
    御隆茶馆 http://www.dianping.com/shop/H6HEuBttJKlMkaAn 0 3 None 棋牌室 南头
    【十万伏特】手创空间 自由DIY http://www.dianping.com/shop/k5OURy1bNIs7ed7v 5 271 ¥152 DIY手工坊 梅林
    八町桑BATTING SOUND 棒球体验馆 http://www.dianping.com/shop/k9yQRAmYoa3o8cLI 5 734 ¥114 新奇体验 车公庙
    星美棋牌 http://www.dianping.com/shop/l4JRIjqLWi2zeFQd 3 9 None 棋牌室 国贸
    ZUO STUDIO烘焙课程· 茶歇蛋糕订购(南山京基百纳广场... http://www.dianping.com/shop/ER0EyDpjx36ekF0G 5 687 ¥224 DIY手工坊 白石洲
    cozy cozy银饰DIY手作室(南山店) http://www.dianping.com/shop/G7MbwkosLSvS3X1I 5 431 ¥338 DIY手工坊 南头
    

    批量下载

    经过以上测试,我们可以将所有相关方法都封装一下,下面我们下载深圳华南城的所有娱乐相关的团购信息:

    import re
    from bs4 import BeautifulSoup
    import requests
    import pandas as pd
    import random
    import time
    from urllib import parse
    from io import BytesIO
    from fontTools.ttLib import TTFont
    
    url2FontMapCache = {}
    css2FontCache = {}
    words = '1234567890店中美家馆小车大市公酒行国品发电金心业商司超生装园场食有新限天面工服海华水房饰城乐汽香部利子老艺花专东肉菜学福饭人百餐茶务通味所山区门药银农龙停尚安广鑫一容动南具源兴鲜记时机烤文康信果阳理锅宝达地儿衣特产西批坊州牛佳化五米修爱北养卖建材三会鸡室红站德王光名丽油院堂烧江社合星货型村自科快便日民营和活童明器烟育宾精屋经居庄石顺林尔县手厅销用好客火雅盛体旅之鞋辣作粉包楼校鱼平彩上吧保永万物教吃设医正造丰健点汤网庆技斯洗料配汇木缘加麻联卫川泰色世方寓风幼羊烫来高厂兰阿贝皮全女拉成云维贸道术运都口博河瑞宏京际路祥青镇厨培力惠连马鸿钢训影甲助窗布富牌头四多妆吉苑沙恒隆春干饼氏里二管诚制售嘉长轩杂副清计黄讯太鸭号街交与叉附近层旁对巷栋环省桥湖段乡厦府铺内侧元购前幢滨处向座下澩凤港开关景泉塘放昌线湾政步宁解白田町溪十八古双胜本单同九迎第台玉锦底后七斜期武岭松角纪朝峰六振珠局岗洲横边济井办汉代临弄团外塔杨铁浦字年岛陵原梅进荣友虹央桂沿事津凯莲丁秀柳集紫旗张谷的是不了很还个也这我就在以可到错没去过感次要比觉看得说常真们但最喜哈么别位能较境非为欢然他挺着价那意种想出员两推做排实分间甜度起满给热完格荐喝等其再几只现朋候样直而买于般豆量选奶打每评少算又因情找些份置适什蛋师气你姐棒试总定啊足级整带虾如态且尝主话强当更板知己无酸让入啦式笑赞片酱差像提队走嫩才刚午接重串回晚微周值费性桌拍跟块调糕'
    num_rule = re.compile("\d+")
    
    def get_url(urls, tag, only_First=True):
        urls = [parse.urljoin(base_url, url)
                for url in urls if tag is None or url.find(tag) != -1]
        if urls and only_First:
            return urls[0]
        return urls
    
    
    def parseCssFontUrl(css_url, tag=None, only_First=True):
        res = session.get(css_url)
        rule = {}
        font_face = {}
        for name, value in re.findall("([^{}]+){([^{}]+)}", res.text):
            name = name.strip()
            for row in value.split(";"):
                if row.find(":") == -1:
                    continue
                k, v = row.split(":")
                k, v = k.strip(), v.strip(' "\'')
                if name == "@font-face":
                    if k == "font-family":
                        font_name = v
                    elif k == "src":
                        font_face.setdefault(font_name, []).extend(
                            re.findall("url\(\"([^()]+)\"\)", v))
                else:
                    rule[name[1:]] = v
        font_urls = {}
        for class_name, tag_name in rule.items():
            font_urls[class_name] = get_url(font_face[tag_name], tag)
        return font_urls
    
    
    def getFontMapFromURL(font_url):
        "缓存字体URL对应字体映射关系"
        if font_url not in url2FontMapCache:
            font_bytes = BytesIO(session.get(font_url).content)
            font_data = TTFont(font_bytes)
            uni_list = font_data.getGlyphOrder()[2:]
            url2FontMapCache[font_url] = dict(
                zip(map(lambda x: x[3:], uni_list), words))
        return url2FontMapCache[font_url]
    
    
    def getFontMapFromClassName(class_name, css_url):
        "缓存指定css文件对应字体URL"
        if css_url not in css2FontCache:
            css2FontCache[css_url] = parseCssFontUrl(css_url, ".woff")
        font_url = css2FontCache[css_url].get(class_name)
        return getFontMapFromURL(font_url)
    
    
    def parse_data(soup):
        result = []
        for li_tag in soup.select("div#shop-all-list div.txt"):
            title = li_tag.select_one("div.tit>a>h4").text
            url = li_tag.select_one("div.tit>a")["href"]
            star_class = li_tag.select_one(
                "div.comment>div.nebula_star>div.star_icon>span")["class"]
            star = int(num_rule.findall(" ".join(star_class))[0])//10
    
            comment_tag = li_tag.select_one("div.comment>a.review-num>b")
            comment_num = comment_tag.text if comment_tag else None
    
            mean_price_tag = li_tag.select_one("div.comment>a.mean-price>b")
            mean_price = mean_price_tag.text if mean_price_tag else None
    
            fun_type = li_tag.select_one(
                "div.tag-addr>a:nth-of-type(1)>span.tag").text
            area = li_tag.select_one("div.tag-addr>a:nth-of-type(2)>span.tag").text
            result.append((title, star, comment_num,
                          mean_price, fun_type, area, url))
        return result
    
    
    def getUrlFromNode(nodes, tag):
        for node in nodes:
            url = node['href']
            if url.find(tag) != -1:
                return parse.urljoin(base_url, url)
    
    
    def get_css_url(soup):
        css_url = getUrlFromNode(soup.select(
            "head > link[rel=stylesheet]"), "svgtextcss")
        return css_url
    
    
    def fix_text(soup):
        css_url = get_css_url(soup)
        for svgmtsi in soup.find_all('svgmtsi'):
            class_name = svgmtsi['class'][0]
            font_map = getFontMapFromClassName(class_name, css_url)
            chars = []
            for c in svgmtsi.text:
                char = c.encode("unicode_escape").decode()[2:]
                chars.append(font_map[char])
            svgmtsi.replaceWith("".join(chars))
    
    
    headers = {
        "Connection": "keep-alive",
        "Cache-Control": "max-age=0",
        "Upgrade-Insecure-Requests": "1",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "Accept-Language": "zh-CN,zh;q=0.9"
    }
    session = requests.Session()
    session.headers = headers
    base_url = "http://www.dianping.com/shenzhen/ch30"
    res = session.get(base_url)
    
    soup = BeautifulSoup(res.text, 'html5lib')
    type_list = []
    for a_tag in soup.select("div#classfy > a"):
        type_list.append((a_tag.span.text, a_tag['href']+'r91172'))
    
    result = []
    for type_name, url in type_list:
        print(type_name, url)
        res = session.get(url)
        soup = BeautifulSoup(res.text, 'html5lib')
        fix_text(soup)
        result.extend(parse_data(soup))
        time.sleep(random.randint(2, 4))
    
    df = pd.DataFrame(result, columns=["标题", "星级", "评论数", "均价", "娱乐类型", "区域", "链接"])
    df.评论数 = df.评论数.apply(lambda x: int(x) if x else pd.NA)
    df.均价 = df.均价.str[1:].apply(lambda x: int(x) if x else pd.NA)
    df.drop_duplicates(inplace=True)
    df.to_excel("华南城娱乐.xlsx", index=False)
    

    爬取结果(有一定的二次编辑):

    image-20210803104317180

    总结

    整体来说,该团购网站的反爬机制还是挺猛的,费了九牛二虎之力也就只能每个栏目爬一页数据,还没有地址,推荐各位不要去爬了。

    不过本文的目的就是演示把最难的字体反爬给解决掉,希望本文已经达到这个目标,如果后面还有更难的字体反爬网站出现,再继续更深的剖析,见招拆招。

    希望各位小伙伴,在研究完本文后,能够应对任何字体反爬问题。

    展开全文
  • windows server 2003 系统

    千次阅读 2018-08-09 21:30:40
    《系统权》作业 课程名称:《系统权》 【要求】 对Windows Server2003操作系统MS08_067、MS03_026漏洞进行权渗透测试; 对MS03_026漏洞开启远程桌面服务; 操作过程可自由发挥; ...
  • 前面我在《2万硬核剖析网页自定义字体解析》一文中,讲解了通过图像识别来解析自定义字体,但是图像识别的缺点在于准确率并不能达到100%,还需要二次修改。 前面将字体的称为点阵图,其实根据TrueType字体实际采用...
  • 破解大众点评字体反爬

    万次阅读 2019-11-18 10:03:43
    本次爬取大众点评商家信息,包括店铺名,星级,评论数,人均价格,各类评分,地址,电话,营业时间 大众点评采用的是两种字体反爬,分别是字体库和svg偏移,前者就是本次需要破解的内容,打开大众点评任意一个店铺...
  • 制作NGUI动态字体

    千次阅读 2013-11-12 11:03:13
    在ngui中有两种制做字体的方式,一种是bmfont等工具制作字体图集的方法,这种方法呢是动态的,生成的图集有多个就是多少个,要多加一个要重新用工具做一次,很是麻烦。而汉字有太多,我们不可能把所有的汉字都...
  • 5 小琴图Violinplot 小琴图允许可视化一个或多个组的数字变量的分布。它与箱形图非常接近,但可以更深入地了解密度。小琴图特别适用于数据量巨大且无法显示个别观察结果的情况。在seaborn中使用violinplot函数...
  • 这里的RecycleBin代码并不全,我只是把最主要的几个方法了出来。那么我们先来对这几个方法进行简单解读,这对后面分析ListView的工作原理将会有很大的帮助。 fillActiveViews() 这个方法接收两个参数,第一个参数...
  • 在选购电脑配件的时候,小白们对于cpu...注:如果你嫌文章太的话,可以直接看最后的总结部分(想要详细掌握还是建议全文阅读) 一、intelcpu后边数字及字母的意思 1、intel的CPU按系列可以划分为Core(酷睿)、...
  • 这种类型的方法通常会导致更强烈的DNA打断,可能会限制新一代测序技术的能力 。在将序列数据 上传到公共存储库时,DNA提取方法也应作为关键的元数据包含在内 。这允许将方法选择的差异考虑到随后的荟萃分析中...
  • 5万、97 张图总结操作系统核心知识点

    万次阅读 多人点赞 2020-07-14 09:19:25
    通常总线被设计成传送定的字节块,也就是 (word)。中的字节数(字长)是一个基本的系统参数,各个系统中都不尽相同。现在大部分的都是 4 个字节(32 位)或者 8 个字节(64 位)。 I/O 设备(I/O Devices):...
  • [C#] 汉字转拼音,支持多音

    万次阅读 2015-06-04 18:02:41
    支持dotnet core的汉字转拼音,而且支持多音
  • 我是如何从写不出来,到完成二十万书稿的?

    千次阅读 多人点赞 2018-11-20 08:02:17
    两年前,我组建了一个二十人的写作原创群,两年过去了,只剩下我一个人在默默坚持——这个结果令我难过了好一段时间。现在想一想,原因可能是我没有为大家找到一个可以长期坚持的目标。 第二步,强制自己为每一...
  • 本人本科渣渣一个,前两天导师让我设计一个数字滤波器。由于本人基本没有数字信号处理基础,于是只能依靠百度和matlab,折腾...(国标里的要求) 滤波器指标:指标:截止频率Fc = 1Hz,阶数N=2,低通巴特沃斯滤波器
  • Cocos2d-x中图原理之深入分析

    万次阅读 多人点赞 2012-09-01 13:03:40
    红孩儿Cocos2d-X学习园地QQ群:249941957 加群写:Cocos2d-x 本章为我的Cocos2d-x教程一书初稿。望各位看官多建议! 首先感谢CSDN对本博的支持,最近一周,本博的两篇博文HelloWorld和HelloLua深入分析
  • Anaconda详细安装及使用教程(图文)

    万次阅读 多人点赞 2018-08-15 17:48:52
    这里一下,Anaconda 很强大,占用空间也不小啊,2.6GB,差不多是一部高清电影的体积了。不过,为了学习,这点硬盘空间算什么呢。 继续点击 Next> 。   这里来到 Advanced Options 了,所谓的“高级...
  • 大型机最初是指装在非常大的框的铁盒子里的大型计算机 系统;随着芯片的集成度越来越高,小型计算机的计算性能逐渐增强,大型机的市场 占有率面临严峻挑战。 PC 时代(分布式计算):上世纪末、本世纪初开始,伴随...
  • python 繁体与简体互相转换

    万次阅读 2018-07-18 22:35:16
    一、编写新建文件:langconv.py from copy import deepcopy import re try: import psyco psyco.full() except: pass try: from zh_wiki import zh2Hant, zh2Hans except ImportError: from zhtools...
  • 一篇文章你看遍Google I/O 2019大会

    万次阅读 多人点赞 2019-05-13 07:21:24
    Nest Hub Max我感觉实际上就是一个屏幕的智能音箱,当然它的智能程度要远比国产的那些智能音箱要高得多,得益于Google强大的人工智能技术。 演讲人Rick Osterloh在台上演示了Nest Hub Max的很多功能,这里我就不...
  • Ain_电脑所有乱码文字集

    万次阅读 2016-08-31 19:53:47
    姘姙姚姛姜姝姞姟姠姡姢姣姤姥姦姧姨姩姪姫姬姭姮姯姰姱姲姳姴姵姶姷姸姹...存孙孚孛孜孝孞孟孠孡孢季孤孥学孧孨孩孪孫孬孭孮孯孰孱孲孳孴孵孶孷學孹孺孻孼孽孾孿宀宁宂它宄宅宆宇守安宊宋完宍宎宏宐宑宒宓宔宕宖宗官...
  • 套接—Socket

    千次阅读 2016-06-02 20:51:57
    网络编程就不得不大名鼎鼎的套接—Socket一,什么是Socket 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个Socket。Socket的英文原意是“插座”,通常称之为套接,来描述IP...
  • 我深知自己还远远不够,若是有人看到了我的博客,请给我意见吧~ 若是转载 ,请注明出处喵喵丸的Blog。正题 最近需求做一个界面上Text,想到当时用Cocos-2dx开发时,曾经用过的fnt、plist这一套东西,就想找一下在...
  • 后端需要知道的关于redis的事,我保证,基本都在这里了。 此文后续会改为粉丝可见,所以喜欢的请提前关注。 你的点赞和评论是我创作的最大动力,谢谢。
  • 关于下标法的,在做题的时候,真的用到多,这里推荐大家以后做题的时候可以关注一下,我就暂时先讲这么多。 3、考虑能否使用双指针 双指针这个技巧,那就更加常用的,特别是在 链表 和 有序数组 中,例如 ...
  •  2018年万科在一次大会上,挂出了三个“活下去”。网上还有一个段子:2019年可能是过去十年里最差的一年,但是未来十年里最好的一年。我在微信上问了王兴,我问:你认同这个段子吗?他说NO。 我也不悲观,我只...
  • 使用Windows中的字体生成点阵字库

    千次阅读 2018-07-09 09:55:05
     那么点阵的数据存放细节到底是怎么样的呢.其实也十分的简单,举个例子最能说明问题.比如说 16*16 的点阵,也就是说每一行有16个点,由于一个点使用一个比特来表示,如果这个比特的值为1,则表示这个位置有点,如果这个...
  • 读《增长黑客》有感

    万次阅读 2020-06-30 17:46:14
    首先,他们面向用户提供有个人 Facebook 基本资料的博客小挂件,用户可以将小挂件的代码粘贴到自己的公共主页或者博客上,对外展示炫耀。 结果,这个看似不起眼的小挂件每月为 Facebook 带来了数十亿次展示量、...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 101,942
精华内容 40,776
关键字:

带长提的字